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}