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