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