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}