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 )
176 .child(EditorPane::new(
177 hello_world_rust_editor_with_status_example(&theme),
178 )),
179 Pane::new(
180 ScrollState::default(),
181 Size {
182 width: relative(1.).into(),
183 height: temp_size,
184 },
185 )
186 .child(Terminal::new()),
187 ],
188 SplitDirection::Vertical,
189 ),
190 PaneGroup::new_panes(
191 vec![Pane::new(
192 ScrollState::default(),
193 Size {
194 width: relative(1.).into(),
195 height: relative(1.).into(),
196 },
197 )
198 .child(EditorPane::new(
199 hello_world_rust_editor_with_status_example(&theme),
200 ))],
201 SplitDirection::Vertical,
202 ),
203 ],
204 SplitDirection::Horizontal,
205 );
206
207 div()
208 .relative()
209 .size_full()
210 .flex()
211 .flex_col()
212 .font("Zed Sans Extended")
213 .gap_0()
214 .justify_start()
215 .items_start()
216 .text_color(theme.lowest.base.default.foreground)
217 .fill(theme.lowest.base.default.background)
218 .child(TitleBar::new(cx).set_livestream(Some(Livestream {
219 players: random_players_with_call_status(7),
220 channel: Some("gpui2-ui".to_string()),
221 })))
222 .child(
223 div()
224 .flex_1()
225 .w_full()
226 .flex()
227 .flex_row()
228 .overflow_hidden()
229 .border_t()
230 .border_b()
231 .border_color(theme.lowest.base.default.border)
232 .children(
233 Some(
234 Panel::new(self.left_panel_scroll_state.clone())
235 .side(PanelSide::Left)
236 .child(ProjectPanel::new(ScrollState::default())),
237 )
238 .filter(|_| workspace_state.is_project_panel_open()),
239 )
240 .children(
241 Some(
242 Panel::new(self.left_panel_scroll_state.clone())
243 .child(CollabPanel::new(ScrollState::default()))
244 .side(PanelSide::Left),
245 )
246 .filter(|_| workspace_state.is_collab_panel_open()),
247 )
248 .child(
249 v_stack()
250 .flex_1()
251 .h_full()
252 .child(
253 div()
254 .flex()
255 .flex_1()
256 // CSS Hack: Flex 1 has to have a set height to properly fill the space
257 // Or it will give you a height of 0
258 // Marshall: We may not need this anymore with `gpui3`. It seems to render
259 // fine without it.
260 .h_px()
261 .child(root_group),
262 )
263 .children(
264 Some(
265 Panel::new(self.bottom_panel_scroll_state.clone())
266 .child(Terminal::new())
267 .allowed_sides(PanelAllowedSides::BottomOnly)
268 .side(PanelSide::Bottom),
269 )
270 .filter(|_| workspace_state.show_terminal.load(Ordering::SeqCst)),
271 ),
272 )
273 .children(
274 Some(
275 Panel::new(self.right_panel_scroll_state.clone())
276 .side(PanelSide::Right)
277 .child(ChatPanel::new(ScrollState::default()).messages(vec![
278 ChatMessage::new(
279 "osiewicz".to_string(),
280 "is this thing on?".to_string(),
281 DateTime::parse_from_rfc3339("2023-09-27T15:40:52.707Z")
282 .unwrap()
283 .naive_local(),
284 ),
285 ChatMessage::new(
286 "maxdeviant".to_string(),
287 "Reading you loud and clear!".to_string(),
288 DateTime::parse_from_rfc3339("2023-09-28T15:40:52.707Z")
289 .unwrap()
290 .naive_local(),
291 ),
292 ])),
293 )
294 .filter(|_| workspace_state.is_chat_panel_open()),
295 )
296 .children(
297 Some(
298 Panel::new(self.right_panel_scroll_state.clone())
299 .child(AssistantPanel::new()),
300 )
301 .filter(|_| workspace_state.is_assistant_panel_open()),
302 ),
303 )
304 .child(StatusBar::new())
305 .children(
306 Some(
307 div()
308 .absolute()
309 .top(px(50.))
310 .left(px(640.))
311 .z_index(999)
312 .child(LanguageSelector::new()),
313 )
314 .filter(|_| workspace_state.is_language_selector_open()),
315 )
316 .child(Toast::new(ToastOrigin::Bottom).child(Label::new("A toast")))
317 .child(Toast::new(ToastOrigin::BottomRight).child(Label::new("Another toast")))
318 }
319}
320
321#[cfg(feature = "stories")]
322pub use stories::*;
323
324#[cfg(feature = "stories")]
325mod stories {
326 use super::*;
327
328 pub struct WorkspaceStory {
329 workspace: View<Workspace>,
330 }
331
332 impl WorkspaceStory {
333 pub fn view(cx: &mut WindowContext) -> View<Self> {
334 view(
335 cx.entity(|cx| Self {
336 workspace: workspace(cx),
337 }),
338 |view, cx| view.workspace.clone(),
339 )
340 }
341 }
342}