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