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