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(
248 self.left_panel_scroll_state.clone(),
249 |_, payload| {
250 vec![ProjectPanel::new(ScrollState::default()).into_any()]
251 },
252 Box::new(()),
253 )
254 .side(PanelSide::Left),
255 )
256 .filter(|_| workspace_state.is_project_panel_open()),
257 )
258 .children(
259 Some(
260 Panel::new(
261 self.left_panel_scroll_state.clone(),
262 |_, payload| {
263 vec![CollabPanel::new(ScrollState::default()).into_any()]
264 },
265 Box::new(()),
266 )
267 .side(PanelSide::Left),
268 )
269 .filter(|_| workspace_state.is_collab_panel_open()),
270 )
271 .child(
272 v_stack()
273 .flex_1()
274 .h_full()
275 .child(
276 div()
277 .flex()
278 .flex_1()
279 // CSS Hack: Flex 1 has to have a set height to properly fill the space
280 // Or it will give you a height of 0
281 // Marshall: We may not need this anymore with `gpui3`. It seems to render
282 // fine without it.
283 .h_px()
284 .child(root_group),
285 )
286 .children(
287 Some(
288 Panel::new(
289 self.bottom_panel_scroll_state.clone(),
290 |_, _| vec![Terminal::new().into_any()],
291 Box::new(()),
292 )
293 .allowed_sides(PanelAllowedSides::BottomOnly)
294 .side(PanelSide::Bottom),
295 )
296 .filter(|_| workspace_state.show_terminal.load(Ordering::SeqCst)),
297 ),
298 )
299 .children(
300 Some(
301 Panel::new(
302 self.right_panel_scroll_state.clone(),
303 |_, payload| {
304 vec![ChatPanel::new(ScrollState::default())
305 .with_messages(vec![
306 ChatMessage::new(
307 "osiewicz".to_string(),
308 "is this thing on?".to_string(),
309 DateTime::parse_from_rfc3339(
310 "2023-09-27T15:40:52.707Z",
311 )
312 .unwrap()
313 .naive_local(),
314 ),
315 ChatMessage::new(
316 "maxdeviant".to_string(),
317 "Reading you loud and clear!".to_string(),
318 DateTime::parse_from_rfc3339(
319 "2023-09-28T15:40:52.707Z",
320 )
321 .unwrap()
322 .naive_local(),
323 ),
324 ])
325 .into_any()]
326 },
327 Box::new(()),
328 )
329 .side(PanelSide::Right),
330 )
331 .filter(|_| workspace_state.is_chat_panel_open()),
332 )
333 .children(
334 Some(Panel::new(
335 self.right_panel_scroll_state.clone(),
336 |_, _| vec![AssistantPanel::new().into_any()],
337 Box::new(()),
338 ))
339 .filter(|_| workspace_state.is_assistant_panel_open()),
340 ),
341 )
342 .child(StatusBar::new())
343 .children(
344 Some(
345 div()
346 .absolute()
347 .top(px(50.))
348 .left(px(640.))
349 .z_index(999)
350 .child(LanguageSelector::new()),
351 )
352 .filter(|_| workspace_state.is_language_selector_open()),
353 )
354 .child(Toast::new(
355 ToastOrigin::Bottom,
356 |_, _| vec![Label::new("A toast").into_any()],
357 Box::new(()),
358 ))
359 .child(Toast::new(
360 ToastOrigin::BottomRight,
361 |_, _| vec![Label::new("Another toast").into_any()],
362 Box::new(()),
363 ))
364 }
365}
366
367#[cfg(feature = "stories")]
368pub use stories::*;
369
370#[cfg(feature = "stories")]
371mod stories {
372 use super::*;
373
374 pub struct WorkspaceStory {
375 workspace: View<Workspace>,
376 }
377
378 pub fn workspace_story(cx: &mut WindowContext) -> View<WorkspaceStory> {
379 view(
380 cx.entity(|cx| WorkspaceStory {
381 workspace: workspace(cx),
382 }),
383 |view, cx| view.workspace.clone(),
384 )
385 }
386}