1use chrono::DateTime;
2use gpui3::{px, relative, rems, view, Context, Size, View};
3
4use crate::{
5 hello_world_rust_editor_with_status_example, theme, v_stack, AssistantPanel, Button,
6 ChatMessage, ChatPanel, CollabPanel, EditorPane, Label, LanguageSelector, Pane, PaneGroup,
7 Panel, PanelAllowedSides, PanelSide, ProjectPanel, SplitDirection, StatusBar, Terminal,
8 TitleBar, Toast, ToastOrigin,
9};
10use crate::{prelude::*, NotificationToast};
11
12#[derive(Clone)]
13pub struct Workspace {
14 title_bar: View<TitleBar>,
15 show_project_panel: bool,
16 show_collab_panel: bool,
17 show_chat_panel: bool,
18 show_assistant_panel: bool,
19 show_terminal: bool,
20 show_language_selector: bool,
21 left_panel_scroll_state: ScrollState,
22 right_panel_scroll_state: ScrollState,
23 tab_bar_scroll_state: ScrollState,
24 bottom_panel_scroll_state: ScrollState,
25}
26
27impl Workspace {
28 pub fn new(cx: &mut ViewContext<Self>) -> Self {
29 Self {
30 title_bar: TitleBar::view(cx),
31 show_project_panel: true,
32 show_collab_panel: false,
33 show_chat_panel: true,
34 show_assistant_panel: false,
35 show_terminal: true,
36 show_language_selector: false,
37 left_panel_scroll_state: ScrollState::default(),
38 right_panel_scroll_state: ScrollState::default(),
39 tab_bar_scroll_state: ScrollState::default(),
40 bottom_panel_scroll_state: ScrollState::default(),
41 }
42 }
43
44 pub fn is_project_panel_open(&self) -> bool {
45 self.show_project_panel
46 }
47
48 pub fn toggle_project_panel(&mut self, cx: &mut ViewContext<Self>) {
49 self.show_project_panel = !self.show_project_panel;
50
51 self.show_collab_panel = false;
52
53 cx.notify();
54 }
55
56 pub fn is_collab_panel_open(&self) -> bool {
57 self.show_collab_panel
58 }
59
60 pub fn toggle_collab_panel(&mut self) {
61 self.show_collab_panel = !self.show_collab_panel;
62
63 self.show_project_panel = false;
64 }
65
66 pub fn is_terminal_open(&self) -> bool {
67 self.show_terminal
68 }
69
70 pub fn toggle_terminal(&mut self, cx: &mut ViewContext<Self>) {
71 self.show_terminal = !self.show_terminal;
72
73 cx.notify();
74 }
75
76 pub fn is_chat_panel_open(&self) -> bool {
77 self.show_chat_panel
78 }
79
80 pub fn toggle_chat_panel(&mut self, cx: &mut ViewContext<Self>) {
81 self.show_chat_panel = !self.show_chat_panel;
82
83 self.show_assistant_panel = false;
84
85 cx.notify();
86 }
87
88 pub fn is_assistant_panel_open(&self) -> bool {
89 self.show_assistant_panel
90 }
91
92 pub fn toggle_assistant_panel(&mut self, cx: &mut ViewContext<Self>) {
93 self.show_assistant_panel = !self.show_assistant_panel;
94
95 self.show_chat_panel = false;
96
97 cx.notify();
98 }
99
100 pub fn is_language_selector_open(&self) -> bool {
101 self.show_language_selector
102 }
103
104 pub fn toggle_language_selector(&mut self, cx: &mut ViewContext<Self>) {
105 self.show_language_selector = !self.show_language_selector;
106
107 cx.notify();
108 }
109
110 pub fn view(cx: &mut WindowContext) -> View<Self> {
111 view(cx.entity(|cx| Self::new(cx)), Self::render)
112 }
113
114 pub fn render(&mut self, cx: &mut ViewContext<Self>) -> impl Element<ViewState = Self> {
115 let theme = theme(cx).clone();
116
117 let temp_size = rems(36.).into();
118
119 let root_group = PaneGroup::new_groups(
120 vec![
121 PaneGroup::new_panes(
122 vec![
123 Pane::new(
124 ScrollState::default(),
125 Size {
126 width: relative(1.).into(),
127 height: temp_size,
128 },
129 )
130 .child(EditorPane::new(
131 hello_world_rust_editor_with_status_example(&theme),
132 )),
133 Pane::new(
134 ScrollState::default(),
135 Size {
136 width: relative(1.).into(),
137 height: temp_size,
138 },
139 )
140 .child(Terminal::new()),
141 ],
142 SplitDirection::Vertical,
143 ),
144 PaneGroup::new_panes(
145 vec![Pane::new(
146 ScrollState::default(),
147 Size {
148 width: relative(1.).into(),
149 height: relative(1.).into(),
150 },
151 )
152 .child(EditorPane::new(
153 hello_world_rust_editor_with_status_example(&theme),
154 ))],
155 SplitDirection::Vertical,
156 ),
157 ],
158 SplitDirection::Horizontal,
159 );
160
161 div()
162 .relative()
163 .size_full()
164 .flex()
165 .flex_col()
166 .font("Zed Sans Extended")
167 .gap_0()
168 .justify_start()
169 .items_start()
170 .text_color(theme.lowest.base.default.foreground)
171 .fill(theme.lowest.base.default.background)
172 .child(self.title_bar.clone())
173 .child(
174 div()
175 .flex_1()
176 .w_full()
177 .flex()
178 .flex_row()
179 .overflow_hidden()
180 .border_t()
181 .border_b()
182 .border_color(theme.lowest.base.default.border)
183 .children(
184 Some(
185 Panel::new(self.left_panel_scroll_state.clone())
186 .side(PanelSide::Left)
187 .child(ProjectPanel::new(ScrollState::default())),
188 )
189 .filter(|_| self.is_project_panel_open()),
190 )
191 .children(
192 Some(
193 Panel::new(self.left_panel_scroll_state.clone())
194 .child(CollabPanel::new(ScrollState::default()))
195 .side(PanelSide::Left),
196 )
197 .filter(|_| self.is_collab_panel_open()),
198 )
199 .child(
200 v_stack()
201 .flex_1()
202 .h_full()
203 .child(
204 div()
205 .flex()
206 .flex_1()
207 // CSS Hack: Flex 1 has to have a set height to properly fill the space
208 // Or it will give you a height of 0
209 // Marshall: We may not need this anymore with `gpui3`. It seems to render
210 // fine without it.
211 .h_px()
212 .child(root_group),
213 )
214 .children(
215 Some(
216 Panel::new(self.bottom_panel_scroll_state.clone())
217 .child(Terminal::new())
218 .allowed_sides(PanelAllowedSides::BottomOnly)
219 .side(PanelSide::Bottom),
220 )
221 .filter(|_| self.is_terminal_open()),
222 ),
223 )
224 .children(
225 Some(
226 Panel::new(self.right_panel_scroll_state.clone())
227 .side(PanelSide::Right)
228 .child(ChatPanel::new(ScrollState::default()).messages(vec![
229 ChatMessage::new(
230 "osiewicz".to_string(),
231 "is this thing on?".to_string(),
232 DateTime::parse_from_rfc3339("2023-09-27T15:40:52.707Z")
233 .unwrap()
234 .naive_local(),
235 ),
236 ChatMessage::new(
237 "maxdeviant".to_string(),
238 "Reading you loud and clear!".to_string(),
239 DateTime::parse_from_rfc3339("2023-09-28T15:40:52.707Z")
240 .unwrap()
241 .naive_local(),
242 ),
243 ])),
244 )
245 .filter(|_| self.is_chat_panel_open()),
246 )
247 .children(
248 Some(
249 Panel::new(self.right_panel_scroll_state.clone())
250 .child(AssistantPanel::new()),
251 )
252 .filter(|_| self.is_assistant_panel_open()),
253 ),
254 )
255 .child(StatusBar::new())
256 .children(
257 Some(
258 div()
259 .absolute()
260 .top(px(50.))
261 .left(px(640.))
262 .z_index(999)
263 .child(LanguageSelector::new()),
264 )
265 .filter(|_| self.is_language_selector_open()),
266 )
267 .child(Toast::new(ToastOrigin::Bottom).child(Label::new("A toast")))
268 // .child(Toast::new(ToastOrigin::BottomRight).child(Label::new("Another toast")))
269 .child(NotificationToast::new(
270 "Can't pull changes from origin",
271 "Your local branch is behind the remote branch. Please pull the latest changes before pushing.",
272 Button::new("Stash & Switch").variant(ButtonVariant::Filled),
273 ).secondary_action(Button::new("Cancel")))
274 }
275}
276
277#[cfg(feature = "stories")]
278pub use stories::*;
279
280#[cfg(feature = "stories")]
281mod stories {
282 use super::*;
283
284 pub struct WorkspaceStory {
285 workspace: View<Workspace>,
286 }
287
288 impl WorkspaceStory {
289 pub fn view(cx: &mut WindowContext) -> View<Self> {
290 view(
291 cx.entity(|cx| Self {
292 workspace: Workspace::view(cx),
293 }),
294 |view, cx| view.workspace.clone(),
295 )
296 }
297 }
298}