1use std::{ops::Deref, sync::Arc};
4
5use gpui::{
6 App, AppContext, Axis, Context, Element, Empty, Entity, IntoElement, MouseMoveEvent,
7 MouseUpEvent, ParentElement as _, Pixels, Point, Render, Style, StyleRefinement, Styled as _,
8 WeakEntity, Window, div, prelude::FluentBuilder as _, px,
9};
10use serde::{Deserialize, Serialize};
11
12use crate::{
13 StyledExt,
14 resizable::{PANEL_MIN_SIZE, resize_handle},
15};
16
17use super::{DockArea, DockItem, PanelView, TabPanel};
18
19#[derive(Clone)]
20struct ResizePanel;
21
22impl Render for ResizePanel {
23 fn render(&mut self, _window: &mut Window, _cx: &mut Context<Self>) -> impl IntoElement {
24 Empty
25 }
26}
27
28#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
29pub enum DockPlacement {
30 #[serde(rename = "center")]
31 Center,
32 #[serde(rename = "left")]
33 Left,
34 #[serde(rename = "bottom")]
35 Bottom,
36 #[serde(rename = "right")]
37 Right,
38}
39
40impl DockPlacement {
41 fn axis(&self) -> Axis {
42 match self {
43 Self::Left | Self::Right => Axis::Horizontal,
44 Self::Bottom => Axis::Vertical,
45 Self::Center => unreachable!(),
46 }
47 }
48
49 pub fn is_left(&self) -> bool {
50 matches!(self, Self::Left)
51 }
52
53 pub fn is_bottom(&self) -> bool {
54 matches!(self, Self::Bottom)
55 }
56
57 pub fn is_right(&self) -> bool {
58 matches!(self, Self::Right)
59 }
60}
61
62pub struct Dock {
66 pub(super) placement: DockPlacement,
67 dock_area: WeakEntity<DockArea>,
68 pub(crate) panel: DockItem,
69 pub(super) size: Pixels,
71 pub(super) open: bool,
72 pub(super) collapsible: bool,
74
75 resizing: bool,
78}
79
80impl Dock {
81 pub(crate) fn new(
82 dock_area: WeakEntity<DockArea>,
83 placement: DockPlacement,
84 window: &mut Window,
85 cx: &mut Context<Self>,
86 ) -> Self {
87 let panel = cx.new(|cx| {
88 let mut tab = TabPanel::new(None, dock_area.clone(), window, cx);
89 tab.closable = false;
90 tab
91 });
92
93 let panel = DockItem::Tabs {
94 size: None,
95 items: Vec::new(),
96 active_ix: 0,
97 view: panel.clone(),
98 };
99
100 Self::subscribe_panel_events(dock_area.clone(), &panel, window, cx);
101
102 Self {
103 placement,
104 dock_area,
105 panel,
106 open: true,
107 collapsible: true,
108 size: px(200.0),
109 resizing: false,
110 }
111 }
112
113 pub fn left(
114 dock_area: WeakEntity<DockArea>,
115 window: &mut Window,
116 cx: &mut Context<Self>,
117 ) -> Self {
118 Self::new(dock_area, DockPlacement::Left, window, cx)
119 }
120
121 pub fn bottom(
122 dock_area: WeakEntity<DockArea>,
123 window: &mut Window,
124 cx: &mut Context<Self>,
125 ) -> Self {
126 Self::new(dock_area, DockPlacement::Bottom, window, cx)
127 }
128
129 pub fn right(
130 dock_area: WeakEntity<DockArea>,
131 window: &mut Window,
132 cx: &mut Context<Self>,
133 ) -> Self {
134 Self::new(dock_area, DockPlacement::Right, window, cx)
135 }
136
137 pub fn set_collapsible(&mut self, collapsible: bool, _: &mut Window, cx: &mut Context<Self>) {
141 self.collapsible = collapsible;
142 if !collapsible {
143 self.open = true
144 }
145 cx.notify();
146 }
147
148 pub(super) fn from_state(
149 dock_area: WeakEntity<DockArea>,
150 placement: DockPlacement,
151 size: Pixels,
152 panel: DockItem,
153 open: bool,
154 window: &mut Window,
155 cx: &mut Context<Self>,
156 ) -> Self {
157 Self::subscribe_panel_events(dock_area.clone(), &panel, window, cx);
158
159 if !open {
160 match panel.clone() {
161 DockItem::Tabs { view, .. } => {
162 view.update(cx, |panel, cx| {
163 panel.set_collapsed(true, window, cx);
164 });
165 }
166 DockItem::Split { items, .. } => {
167 for item in items {
168 item.set_collapsed(true, window, cx);
169 }
170 }
171 _ => {}
172 }
173 }
174
175 Self {
176 placement,
177 dock_area,
178 panel,
179 open,
180 size,
181 collapsible: true,
182 resizing: false,
183 }
184 }
185
186 fn subscribe_panel_events(
187 dock_area: WeakEntity<DockArea>,
188 panel: &DockItem,
189 window: &mut Window,
190 cx: &mut Context<Self>,
191 ) {
192 match panel {
193 DockItem::Tabs { view, .. } => {
194 window.defer(cx, {
195 let view = view.clone();
196 move |window, cx| {
197 _ = dock_area.update(cx, |this, cx| {
198 this.subscribe_panel(&view, window, cx);
199 });
200 }
201 });
202 }
203 DockItem::Split { items, view, .. } => {
204 for item in items {
205 Self::subscribe_panel_events(dock_area.clone(), item, window, cx);
206 }
207 window.defer(cx, {
208 let view = view.clone();
209 move |window, cx| {
210 _ = dock_area.update(cx, |this, cx| {
211 this.subscribe_panel(&view, window, cx);
212 });
213 }
214 });
215 }
216 DockItem::Tiles { view, .. } => {
217 window.defer(cx, {
218 let view = view.clone();
219 move |window, cx| {
220 _ = dock_area.update(cx, |this, cx| {
221 this.subscribe_panel(&view, window, cx);
222 });
223 }
224 });
225 }
226 DockItem::Panel { .. } => {
227 }
229 }
230 }
231
232 pub fn set_panel(&mut self, panel: DockItem, _: &mut Window, cx: &mut Context<Self>) {
233 self.panel = panel;
234 cx.notify();
235 }
236
237 pub fn is_open(&self) -> bool {
238 self.open
239 }
240
241 pub fn toggle_open(&mut self, window: &mut Window, cx: &mut Context<Self>) {
242 self.set_open(!self.open, window, cx);
243 }
244
245 pub fn size(&self) -> Pixels {
249 self.size
250 }
251
252 pub fn set_size(&mut self, size: Pixels, _: &mut Window, cx: &mut Context<Self>) {
254 self.size = size.max(PANEL_MIN_SIZE);
255 cx.notify();
256 }
257
258 pub fn set_open(&mut self, open: bool, window: &mut Window, cx: &mut Context<Self>) {
260 self.open = open;
261 let item = self.panel.clone();
262 cx.defer_in(window, move |_, window, cx| {
263 item.set_collapsed(!open, window, cx);
264 });
265 cx.notify();
266 }
267
268 pub fn add_panel(
270 &mut self,
271 panel: Arc<dyn PanelView>,
272 window: &mut Window,
273 cx: &mut Context<Self>,
274 ) {
275 self.panel
276 .add_panel(panel, &self.dock_area, None, window, cx);
277 cx.notify();
278 }
279
280 pub fn remove_panel(
282 &mut self,
283 panel: Arc<dyn PanelView>,
284 window: &mut Window,
285 cx: &mut Context<Self>,
286 ) {
287 self.panel.remove_panel(panel, window, cx);
288 cx.notify();
289 }
290
291 fn render_resize_handle(&mut self, _: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
292 let axis = self.placement.axis();
293 let view = cx.entity().clone();
294
295 resize_handle("resize-handle", axis)
296 .placement(self.placement)
297 .on_drag(ResizePanel {}, move |info, _, _, cx| {
298 cx.stop_propagation();
299 view.update(cx, |view, _| {
300 view.resizing = true;
301 });
302 cx.new(|_| info.deref().clone())
303 })
304 }
305 fn resize(&mut self, mouse_position: Point<Pixels>, _: &mut Window, cx: &mut Context<Self>) {
306 if !self.resizing {
307 return;
308 }
309
310 let dock_area = self
311 .dock_area
312 .upgrade()
313 .expect("DockArea is missing")
314 .read(cx);
315 let area_bounds = dock_area.bounds;
316 let mut left_dock_size = px(0.0);
317 let mut right_dock_size = px(0.0);
318
319 if let Some(left_dock) = &dock_area.left_dock {
321 if left_dock.entity_id() != cx.entity().entity_id() {
322 let left_dock_read = left_dock.read(cx);
323 if left_dock_read.is_open() {
324 left_dock_size = left_dock_read.size;
325 }
326 }
327 }
328
329 if let Some(right_dock) = &dock_area.right_dock {
331 if right_dock.entity_id() != cx.entity().entity_id() {
332 let right_dock_read = right_dock.read(cx);
333 if right_dock_read.is_open() {
334 right_dock_size = right_dock_read.size;
335 }
336 }
337 }
338
339 let size = match self.placement {
340 DockPlacement::Left => mouse_position.x - area_bounds.left(),
341 DockPlacement::Right => area_bounds.right() - mouse_position.x,
342 DockPlacement::Bottom => area_bounds.bottom() - mouse_position.y,
343 DockPlacement::Center => unreachable!(),
344 };
345 match self.placement {
346 DockPlacement::Left => {
347 let max_size = area_bounds.size.width - PANEL_MIN_SIZE - right_dock_size;
348 self.size = size.clamp(PANEL_MIN_SIZE, max_size);
349 }
350 DockPlacement::Right => {
351 let max_size = area_bounds.size.width - PANEL_MIN_SIZE - left_dock_size;
352 self.size = size.clamp(PANEL_MIN_SIZE, max_size);
353 }
354 DockPlacement::Bottom => {
355 let max_size = area_bounds.size.height - PANEL_MIN_SIZE;
356 self.size = size.clamp(PANEL_MIN_SIZE, max_size);
357 }
358 DockPlacement::Center => unreachable!(),
359 }
360
361 cx.notify();
362 }
363
364 fn done_resizing(&mut self, _window: &mut Window, _cx: &mut Context<Self>) {
365 self.resizing = false;
366 }
367}
368
369impl Render for Dock {
370 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl gpui::IntoElement {
371 if !self.open && !self.placement.is_bottom() {
372 return div();
373 }
374
375 let cache_style = StyleRefinement::default().absolute().size_full();
376
377 div()
378 .relative()
379 .overflow_hidden()
380 .map(|this| match self.placement {
381 DockPlacement::Left | DockPlacement::Right => this.h_flex().h_full().w(self.size),
382 DockPlacement::Bottom => this.w_full().h(self.size),
383 DockPlacement::Center => unreachable!(),
384 })
385 .when(!self.open && self.placement.is_bottom(), |this| {
387 this.h(px(29.))
388 })
389 .map(|this| match &self.panel {
390 DockItem::Split { view, .. } => this.child(view.clone()),
391 DockItem::Tabs { view, .. } => this.child(view.clone()),
392 DockItem::Panel { view, .. } => this.child(view.clone().view().cached(cache_style)),
393 DockItem::Tiles { .. } => this,
395 })
396 .child(self.render_resize_handle(window, cx))
397 .child(DockElement {
398 view: cx.entity().clone(),
399 })
400 }
401}
402
403struct DockElement {
404 view: Entity<Dock>,
405}
406
407impl IntoElement for DockElement {
408 type Element = Self;
409
410 fn into_element(self) -> Self::Element {
411 self
412 }
413}
414
415impl Element for DockElement {
416 type RequestLayoutState = ();
417 type PrepaintState = ();
418
419 fn id(&self) -> Option<gpui::ElementId> {
420 None
421 }
422
423 fn source_location(&self) -> Option<&'static std::panic::Location<'static>> {
424 None
425 }
426
427 fn request_layout(
428 &mut self,
429 _: Option<&gpui::GlobalElementId>,
430 _: Option<&gpui::InspectorElementId>,
431 window: &mut gpui::Window,
432 cx: &mut App,
433 ) -> (gpui::LayoutId, Self::RequestLayoutState) {
434 (window.request_layout(Style::default(), None, cx), ())
435 }
436
437 fn prepaint(
438 &mut self,
439 _: Option<&gpui::GlobalElementId>,
440 _: Option<&gpui::InspectorElementId>,
441 _: gpui::Bounds<Pixels>,
442 _: &mut Self::RequestLayoutState,
443 _window: &mut gpui::Window,
444 _cx: &mut App,
445 ) -> Self::PrepaintState {
446 ()
447 }
448
449 fn paint(
450 &mut self,
451 _: Option<&gpui::GlobalElementId>,
452 _: Option<&gpui::InspectorElementId>,
453 _: gpui::Bounds<Pixels>,
454 _: &mut Self::RequestLayoutState,
455 _: &mut Self::PrepaintState,
456 window: &mut gpui::Window,
457 cx: &mut App,
458 ) {
459 window.on_mouse_event({
460 let view = self.view.clone();
461 let resizing = view.read(cx).resizing;
462 move |e: &MouseMoveEvent, phase, window, cx| {
463 if !resizing {
464 return;
465 }
466 if !phase.bubble() {
467 return;
468 }
469
470 view.update(cx, |view, cx| view.resize(e.position, window, cx))
471 }
472 });
473
474 window.on_mouse_event({
476 let view = self.view.clone();
477 move |_: &MouseUpEvent, phase, window, cx| {
478 if phase.bubble() {
479 view.update(cx, |view, cx| view.done_resizing(window, cx));
480 }
481 }
482 })
483 }
484}