1use std::sync::atomic::{AtomicBool, Ordering};
2use std::sync::{Arc, OnceLock};
3
4use chrono::DateTime;
5use gpui3::{px, relative, rems, view, Context, Size, View};
6
7use crate::prelude::*;
8use crate::{
9 hello_world_rust_editor_with_status_example, random_players_with_call_status, theme, v_stack,
10 AssistantPanel, ChatMessage, ChatPanel, CollabPanel, EditorPane, Label, LanguageSelector,
11 Livestream, Pane, PaneGroup, Panel, PanelAllowedSides, PanelSide, ProjectPanel, SplitDirection,
12 StatusBar, Terminal, TitleBar, Toast, ToastOrigin,
13};
14
15pub struct WorkspaceState {
16 pub show_project_panel: Arc<AtomicBool>,
17 pub show_collab_panel: Arc<AtomicBool>,
18 pub show_chat_panel: Arc<AtomicBool>,
19 pub show_assistant_panel: Arc<AtomicBool>,
20 pub show_terminal: Arc<AtomicBool>,
21 pub show_language_selector: Arc<AtomicBool>,
22}
23
24impl WorkspaceState {
25 fn toggle_value(current_value: &AtomicBool) {
26 let value = current_value.load(Ordering::SeqCst);
27
28 current_value
29 .compare_exchange(value, !value, Ordering::SeqCst, Ordering::SeqCst)
30 .unwrap();
31 }
32
33 pub fn is_project_panel_open(&self) -> bool {
34 self.show_project_panel.load(Ordering::SeqCst)
35 }
36
37 pub fn toggle_project_panel(&self) {
38 Self::toggle_value(&self.show_project_panel);
39
40 self.show_collab_panel.store(false, Ordering::SeqCst);
41 }
42
43 pub fn is_collab_panel_open(&self) -> bool {
44 self.show_collab_panel.load(Ordering::SeqCst)
45 }
46
47 pub fn toggle_collab_panel(&self) {
48 Self::toggle_value(&self.show_collab_panel);
49
50 self.show_project_panel.store(false, Ordering::SeqCst);
51 }
52
53 pub fn is_terminal_open(&self) -> bool {
54 self.show_terminal.load(Ordering::SeqCst)
55 }
56
57 pub fn toggle_terminal(&self) {
58 Self::toggle_value(&self.show_terminal);
59 }
60
61 pub fn is_chat_panel_open(&self) -> bool {
62 self.show_chat_panel.load(Ordering::SeqCst)
63 }
64
65 pub fn toggle_chat_panel(&self) {
66 Self::toggle_value(&self.show_chat_panel);
67
68 self.show_assistant_panel.store(false, Ordering::SeqCst);
69 }
70
71 pub fn is_assistant_panel_open(&self) -> bool {
72 self.show_assistant_panel.load(Ordering::SeqCst)
73 }
74
75 pub fn toggle_assistant_panel(&self) {
76 Self::toggle_value(&self.show_assistant_panel);
77
78 self.show_chat_panel.store(false, Ordering::SeqCst);
79 }
80
81 pub fn is_language_selector_open(&self) -> bool {
82 self.show_language_selector.load(Ordering::SeqCst)
83 }
84
85 pub fn toggle_language_selector(&self) {
86 Self::toggle_value(&self.show_language_selector);
87 }
88}
89
90/// HACK: This is just a temporary way to start hooking up interactivity until
91/// I can get an explainer on how we should actually be managing state.
92static WORKSPACE_STATE: OnceLock<WorkspaceState> = OnceLock::new();
93
94pub fn get_workspace_state() -> &'static WorkspaceState {
95 let state = WORKSPACE_STATE.get_or_init(|| WorkspaceState {
96 show_project_panel: Arc::new(AtomicBool::new(true)),
97 show_collab_panel: Arc::new(AtomicBool::new(false)),
98 show_chat_panel: Arc::new(AtomicBool::new(true)),
99 show_assistant_panel: Arc::new(AtomicBool::new(false)),
100 show_terminal: Arc::new(AtomicBool::new(true)),
101 show_language_selector: Arc::new(AtomicBool::new(false)),
102 });
103
104 state
105}
106
107// #[derive(Element)]
108#[derive(Clone)]
109pub struct Workspace {
110 show_project_panel: bool,
111 show_collab_panel: bool,
112 left_panel_scroll_state: ScrollState,
113 right_panel_scroll_state: ScrollState,
114 tab_bar_scroll_state: ScrollState,
115 bottom_panel_scroll_state: ScrollState,
116}
117
118fn workspace(cx: &mut WindowContext) -> View<Workspace> {
119 view(cx.entity(|cx| Workspace::new()), Workspace::render)
120}
121
122impl Workspace {
123 pub fn new() -> Self {
124 Self {
125 show_project_panel: true,
126 show_collab_panel: false,
127 left_panel_scroll_state: ScrollState::default(),
128 right_panel_scroll_state: ScrollState::default(),
129 tab_bar_scroll_state: ScrollState::default(),
130 bottom_panel_scroll_state: ScrollState::default(),
131 }
132 }
133
134 pub fn is_project_panel_open(&self) -> bool {
135 dbg!(self.show_project_panel)
136 }
137
138 pub fn toggle_project_panel(&mut self, cx: &mut ViewContext<Self>) {
139 self.show_project_panel = !self.show_project_panel;
140
141 self.show_collab_panel = false;
142
143 dbg!(self.show_project_panel);
144
145 cx.notify();
146 }
147
148 pub fn is_collab_panel_open(&self) -> bool {
149 self.show_collab_panel
150 }
151
152 pub fn toggle_collab_panel(&mut self) {
153 self.show_collab_panel = !self.show_collab_panel;
154
155 self.show_project_panel = false;
156 }
157
158 pub fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<ViewState = Self> {
159 let theme = theme(cx).clone();
160
161 let workspace_state = get_workspace_state();
162
163 let temp_size = rems(36.).into();
164
165 let root_group = PaneGroup::new_groups(
166 vec![
167 PaneGroup::new_panes(
168 vec![
169 Pane::new(
170 ScrollState::default(),
171 Size {
172 width: relative(1.).into(),
173 height: temp_size,
174 },
175 |_, payload| {
176 let theme = payload.downcast_ref::<Arc<Theme>>().unwrap();
177
178 vec![EditorPane::new(hello_world_rust_editor_with_status_example(
179 &theme,
180 ))
181 .into_any()]
182 },
183 Box::new(theme.clone()),
184 ),
185 Pane::new(
186 ScrollState::default(),
187 Size {
188 width: relative(1.).into(),
189 height: temp_size,
190 },
191 |_, _| vec![Terminal::new().into_any()],
192 Box::new(()),
193 ),
194 ],
195 SplitDirection::Vertical,
196 ),
197 PaneGroup::new_panes(
198 vec![Pane::new(
199 ScrollState::default(),
200 Size {
201 width: relative(1.).into(),
202 height: relative(1.).into(),
203 },
204 |_, payload| {
205 let theme = payload.downcast_ref::<Arc<Theme>>().unwrap();
206
207 vec![EditorPane::new(hello_world_rust_editor_with_status_example(
208 &theme,
209 ))
210 .into_any()]
211 },
212 Box::new(theme.clone()),
213 )],
214 SplitDirection::Vertical,
215 ),
216 ],
217 SplitDirection::Horizontal,
218 );
219
220 div()
221 .relative()
222 .size_full()
223 .flex()
224 .flex_col()
225 .font("Zed Sans Extended")
226 .gap_0()
227 .justify_start()
228 .items_start()
229 .text_color(theme.lowest.base.default.foreground)
230 .fill(theme.lowest.base.default.background)
231 .child(TitleBar::new(cx).set_livestream(Some(Livestream {
232 players: random_players_with_call_status(7),
233 channel: Some("gpui2-ui".to_string()),
234 })))
235 .child(
236 div()
237 .flex_1()
238 .w_full()
239 .flex()
240 .flex_row()
241 .overflow_hidden()
242 .border_t()
243 .border_b()
244 .border_color(theme.lowest.base.default.border)
245 .children(
246 Some(
247 Panel::new(self.left_panel_scroll_state.clone())
248 .side(PanelSide::Left)
249 .child(ProjectPanel::new(ScrollState::default())),
250 )
251 .filter(|_| workspace_state.is_project_panel_open()),
252 )
253 .children(
254 Some(
255 Panel::new(self.left_panel_scroll_state.clone())
256 .child(CollabPanel::new(ScrollState::default()))
257 .side(PanelSide::Left),
258 )
259 .filter(|_| workspace_state.is_collab_panel_open()),
260 )
261 .child(
262 v_stack()
263 .flex_1()
264 .h_full()
265 .child(
266 div()
267 .flex()
268 .flex_1()
269 // CSS Hack: Flex 1 has to have a set height to properly fill the space
270 // Or it will give you a height of 0
271 // Marshall: We may not need this anymore with `gpui3`. It seems to render
272 // fine without it.
273 .h_px()
274 .child(root_group),
275 )
276 .children(
277 Some(
278 Panel::new(self.bottom_panel_scroll_state.clone())
279 .child(Terminal::new())
280 .allowed_sides(PanelAllowedSides::BottomOnly)
281 .side(PanelSide::Bottom),
282 )
283 .filter(|_| workspace_state.show_terminal.load(Ordering::SeqCst)),
284 ),
285 )
286 .children(
287 Some(
288 Panel::new(self.right_panel_scroll_state.clone())
289 .side(PanelSide::Right)
290 .child(ChatPanel::new(ScrollState::default()).messages(vec![
291 ChatMessage::new(
292 "osiewicz".to_string(),
293 "is this thing on?".to_string(),
294 DateTime::parse_from_rfc3339("2023-09-27T15:40:52.707Z")
295 .unwrap()
296 .naive_local(),
297 ),
298 ChatMessage::new(
299 "maxdeviant".to_string(),
300 "Reading you loud and clear!".to_string(),
301 DateTime::parse_from_rfc3339("2023-09-28T15:40:52.707Z")
302 .unwrap()
303 .naive_local(),
304 ),
305 ])),
306 )
307 .filter(|_| workspace_state.is_chat_panel_open()),
308 )
309 .children(
310 Some(
311 Panel::new(self.right_panel_scroll_state.clone())
312 .child(AssistantPanel::new()),
313 )
314 .filter(|_| workspace_state.is_assistant_panel_open()),
315 ),
316 )
317 .child(StatusBar::new())
318 .children(
319 Some(
320 div()
321 .absolute()
322 .top(px(50.))
323 .left(px(640.))
324 .z_index(999)
325 .child(LanguageSelector::new()),
326 )
327 .filter(|_| workspace_state.is_language_selector_open()),
328 )
329 .child(Toast::new(
330 ToastOrigin::Bottom,
331 |_, _| vec![Label::new("A toast").into_any()],
332 Box::new(()),
333 ))
334 .child(Toast::new(
335 ToastOrigin::BottomRight,
336 |_, _| vec![Label::new("Another toast").into_any()],
337 Box::new(()),
338 ))
339 }
340}
341
342#[cfg(feature = "stories")]
343pub use stories::*;
344
345#[cfg(feature = "stories")]
346mod stories {
347 use super::*;
348
349 pub struct WorkspaceStory {
350 workspace: View<Workspace>,
351 }
352
353 pub fn workspace_story(cx: &mut WindowContext) -> View<WorkspaceStory> {
354 view(
355 cx.entity(|cx| WorkspaceStory {
356 workspace: workspace(cx),
357 }),
358 |view, cx| view.workspace.clone(),
359 )
360 }
361}