workspace.rs

  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}