workspace.rs

  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}