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