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