1use std::{
2 any::Any,
3 fmt::{Debug, Formatter},
4 sync::Arc,
5};
6
7use crate::{
8 h_flex,
9 history::{History, HistoryItem},
10 scroll::{Scrollbar, ScrollbarState},
11 v_flex, ActiveTheme, Icon, IconName,
12};
13
14use super::{
15 DockArea, Panel, PanelEvent, PanelInfo, PanelState, PanelView, StackPanel, TabPanel, TileMeta,
16};
17use gpui::{
18 actions, canvas, div, px, size, AnyElement, App, AppContext, Bounds, Context, DismissEvent,
19 DragMoveEvent, Empty, EntityId, EventEmitter, FocusHandle, Focusable, InteractiveElement,
20 IntoElement, MouseButton, MouseDownEvent, MouseUpEvent, ParentElement, Pixels, Point, Render,
21 ScrollHandle, Size, StatefulInteractiveElement, Styled, WeakEntity, Window,
22};
23
24actions!(tiles, [Undo, Redo]);
25
26const MINIMUM_SIZE: Size<Pixels> = size(px(100.), px(100.));
27const DRAG_BAR_HEIGHT: Pixels = px(30.);
28const HANDLE_SIZE: Pixels = px(5.0);
29
30#[derive(Clone, PartialEq, Debug)]
31struct TileChange {
32 tile_id: EntityId,
33 old_bounds: Option<Bounds<Pixels>>,
34 new_bounds: Option<Bounds<Pixels>>,
35 old_order: Option<usize>,
36 new_order: Option<usize>,
37 version: usize,
38}
39
40impl HistoryItem for TileChange {
41 fn version(&self) -> usize {
42 self.version
43 }
44
45 fn set_version(&mut self, version: usize) {
46 self.version = version;
47 }
48}
49
50#[derive(Clone)]
51pub struct DragMoving(EntityId);
52impl Render for DragMoving {
53 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
54 Empty
55 }
56}
57
58#[derive(Clone, PartialEq)]
59enum ResizeSide {
60 Left,
61 Right,
62 Top,
63 Bottom,
64 BottomRight,
65}
66
67#[derive(Clone)]
68pub struct DragResizing(EntityId);
69
70impl Render for DragResizing {
71 fn render(&mut self, _: &mut Window, _: &mut Context<Self>) -> impl IntoElement {
72 Empty
73 }
74}
75
76#[derive(Clone)]
77struct ResizeDrag {
78 side: ResizeSide,
79 last_position: Point<Pixels>,
80 last_bounds: Bounds<Pixels>,
81}
82
83#[derive(Clone)]
85pub struct TileItem {
86 id: EntityId,
87 pub(crate) panel: Arc<dyn PanelView>,
88 bounds: Bounds<Pixels>,
89 z_index: usize,
90}
91
92impl Debug for TileItem {
93 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
94 f.debug_struct("TileItem")
95 .field("bounds", &self.bounds)
96 .field("z_index", &self.z_index)
97 .finish()
98 }
99}
100
101impl TileItem {
102 pub fn new(panel: Arc<dyn PanelView>, bounds: Bounds<Pixels>) -> Self {
103 Self {
104 id: panel.view().entity_id(),
105 panel,
106 bounds,
107 z_index: 0,
108 }
109 }
110
111 pub fn z_index(mut self, z_index: usize) -> Self {
112 self.z_index = z_index;
113 self
114 }
115}
116
117#[derive(Clone, Debug)]
118pub struct AnyDrag {
119 pub value: Arc<dyn Any>,
120}
121
122impl AnyDrag {
123 pub fn new(value: impl Any) -> Self {
124 Self {
125 value: Arc::new(value),
126 }
127 }
128}
129
130pub struct Tiles {
132 focus_handle: FocusHandle,
133 pub(crate) panels: Vec<TileItem>,
134 dragging_id: Option<EntityId>,
135 dragging_initial_mouse: Point<Pixels>,
136 dragging_initial_bounds: Bounds<Pixels>,
137 resizing_id: Option<EntityId>,
138 resizing_drag_data: Option<ResizeDrag>,
139 bounds: Bounds<Pixels>,
140 history: History<TileChange>,
141 scroll_state: ScrollbarState,
142 scroll_handle: ScrollHandle,
143}
144
145impl Panel for Tiles {
146 fn panel_name(&self) -> &'static str {
147 "Tiles"
148 }
149
150 fn title(&self, _window: &Window, _cx: &App) -> AnyElement {
151 "Tiles".into_any_element()
152 }
153
154 fn dump(&self, cx: &App) -> PanelState {
155 let panels = self
156 .panels
157 .iter()
158 .map(|item: &TileItem| item.panel.dump(cx))
159 .collect();
160
161 let metas = self
162 .panels
163 .iter()
164 .map(|item: &TileItem| TileMeta {
165 bounds: item.bounds,
166 z_index: item.z_index,
167 })
168 .collect();
169
170 let mut state = PanelState::new(self);
171 state.panel_name = self.panel_name().to_string();
172 state.children = panels;
173 state.info = PanelInfo::Tiles { metas };
174 state
175 }
176}
177
178#[derive(Clone, Debug)]
179pub struct DragDrop(pub AnyDrag);
180
181impl EventEmitter<DragDrop> for Tiles {}
182
183impl Tiles {
184 pub fn new(_: &mut Window, cx: &mut Context<Self>) -> Self {
185 Self {
186 focus_handle: cx.focus_handle(),
187 panels: vec![],
188 dragging_id: None,
189 dragging_initial_mouse: Point::default(),
190 dragging_initial_bounds: Bounds::default(),
191 resizing_id: None,
192 resizing_drag_data: None,
193 bounds: Bounds::default(),
194 history: History::new().group_interval(std::time::Duration::from_millis(100)),
195 scroll_state: ScrollbarState::default(),
196 scroll_handle: ScrollHandle::default(),
197 }
198 }
199
200 pub fn panels(&self) -> &[TileItem] {
201 &self.panels
202 }
203
204 fn sorted_panels(&self) -> Vec<TileItem> {
205 let mut items: Vec<(usize, TileItem)> = self.panels.iter().cloned().enumerate().collect();
206 items.sort_by(|a, b| a.1.z_index.cmp(&b.1.z_index).then_with(|| a.0.cmp(&b.0)));
207 items.into_iter().map(|(_, item)| item).collect()
208 }
209
210 #[inline]
212 pub(crate) fn index_of(&self, id: &EntityId) -> Option<usize> {
213 self.panels.iter().position(|p| &p.id == id)
214 }
215
216 #[inline]
217 pub(crate) fn panel(&self, id: &EntityId) -> Option<&TileItem> {
218 self.panels.iter().find(|p| &p.id == id)
219 }
220
221 pub fn remove(&mut self, panel: Arc<dyn PanelView>, _: &mut Window, cx: &mut Context<Self>) {
223 if let Some(ix) = self.index_of(&panel.panel_id(cx)) {
224 self.panels.remove(ix);
225
226 cx.emit(PanelEvent::LayoutChanged);
227 }
228 }
229
230 fn update_position(
231 &mut self,
232 mouse_position: Point<Pixels>,
233 _: &mut Window,
234 cx: &mut Context<'_, Self>,
235 ) {
236 let Some(dragging_id) = self.dragging_id else {
237 return;
238 };
239
240 let Some(item) = self.panels.iter_mut().find(|p| p.id == dragging_id) else {
241 return;
242 };
243
244 let previous_bounds = item.bounds;
245 let adjusted_position = mouse_position - self.bounds.origin;
246 let delta = adjusted_position - self.dragging_initial_mouse;
247 let mut new_origin = self.dragging_initial_bounds.origin + delta;
248
249 if new_origin.y < px(0.) {
251 new_origin.y = px(0.);
252 }
253 let min_left = -self.dragging_initial_bounds.size.width + px(64.);
254 if new_origin.x < min_left {
255 new_origin.x = min_left;
256 }
257
258 let final_origin = round_point_to_nearest_ten(new_origin, cx);
259 if final_origin != previous_bounds.origin {
261 item.bounds.origin = final_origin;
262
263 if !self.history.ignore {
265 self.history.push(TileChange {
266 tile_id: item.panel.view().entity_id(),
267 old_bounds: Some(previous_bounds),
268 new_bounds: Some(item.bounds),
269 old_order: None,
270 new_order: None,
271 version: 0,
272 });
273 }
274 }
275
276 cx.notify();
277 }
278
279 fn resize(
280 &mut self,
281 new_x: Option<Pixels>,
282 new_y: Option<Pixels>,
283 new_width: Option<Pixels>,
284 new_height: Option<Pixels>,
285 _: &mut Window,
286 cx: &mut Context<'_, Self>,
287 ) {
288 let Some(resizing_id) = self.resizing_id else {
289 return;
290 };
291 let Some(item) = self.panels.iter_mut().find(|item| item.id == resizing_id) else {
292 return;
293 };
294
295 let previous_bounds = item.bounds;
296 let final_x = if let Some(x) = new_x {
297 round_to_nearest_ten(x, cx)
298 } else {
299 previous_bounds.origin.x
300 };
301 let final_y = if let Some(y) = new_y {
302 round_to_nearest_ten(y, cx)
303 } else {
304 previous_bounds.origin.y
305 };
306 let final_width = if let Some(width) = new_width {
307 round_to_nearest_ten(width, cx)
308 } else {
309 previous_bounds.size.width
310 };
311
312 let final_height = if let Some(height) = new_height {
313 round_to_nearest_ten(height, cx)
314 } else {
315 previous_bounds.size.height
316 };
317
318 if final_width != item.bounds.size.width
320 || final_height != item.bounds.size.height
321 || final_x != item.bounds.origin.x
322 || final_y != item.bounds.origin.y
323 {
324 item.bounds.origin.x = final_x;
325 item.bounds.origin.y = final_y;
326 item.bounds.size.width = final_width;
327 item.bounds.size.height = final_height;
328
329 if !self.history.ignore {
331 self.history.push(TileChange {
332 tile_id: item.panel.view().entity_id(),
333 old_bounds: Some(previous_bounds),
334 new_bounds: Some(item.bounds),
335 old_order: None,
336 new_order: None,
337 version: 0,
338 });
339 }
340 }
341
342 cx.notify();
343 }
344
345 pub fn add_item(
346 &mut self,
347 item: TileItem,
348 dock_area: &WeakEntity<DockArea>,
349 window: &mut Window,
350 cx: &mut Context<Self>,
351 ) {
352 let Ok(tab_panel) = item.panel.view().downcast::<TabPanel>() else {
353 panic!("only allows to add TabPanel type")
354 };
355
356 tab_panel.update(cx, |tab_panel, _| {
357 tab_panel.set_in_tiles(true);
358 });
359
360 self.panels.push(item.clone());
361 window.defer(cx, {
362 let panel = item.panel.clone();
363 let dock_area = dock_area.clone();
364
365 move |window, cx| {
366 _ = dock_area.update(cx, |this, cx| {
368 if let Ok(tab_panel) = panel.view().downcast::<TabPanel>() {
369 this.subscribe_panel(&tab_panel, window, cx);
370 }
371 });
372 }
373 });
374
375 cx.emit(PanelEvent::LayoutChanged);
376 cx.notify();
377 }
378
379 #[inline]
380 fn reset_current_index(&mut self) {
381 self.dragging_id = None;
382 self.resizing_id = None;
383 }
384
385 fn bring_to_front(
387 &mut self,
388 target_id: Option<EntityId>,
389 cx: &mut Context<Self>,
390 ) -> Option<EntityId> {
391 let Some(old_id) = target_id else {
392 return None;
393 };
394
395 let old_ix = self.panels.iter().position(|item| item.id == old_id)?;
396 if old_ix < self.panels.len() {
397 let item = self.panels.remove(old_ix);
398 self.panels.push(item);
399 let new_ix = self.panels.len() - 1;
400 let new_id = self.panels[new_ix].id;
401 self.history.push(TileChange {
402 tile_id: new_id,
403 old_bounds: None,
404 new_bounds: None,
405 old_order: Some(old_ix),
406 new_order: Some(new_ix),
407 version: 0,
408 });
409 cx.notify();
410 return Some(new_id);
411 }
412 None
413 }
414
415 pub fn undo(&mut self, _: &mut Window, cx: &mut Context<Self>) {
417 self.history.ignore = true;
418
419 if let Some(changes) = self.history.undo() {
420 for change in changes {
421 if let Some(index) = self
422 .panels
423 .iter()
424 .position(|item| item.panel.view().entity_id() == change.tile_id)
425 {
426 if let Some(old_bounds) = change.old_bounds {
427 self.panels[index].bounds = old_bounds;
428 }
429 if let Some(old_order) = change.old_order {
430 let item = self.panels.remove(index);
431 self.panels.insert(old_order, item);
432 }
433 }
434 }
435 cx.emit(PanelEvent::LayoutChanged);
436 }
437
438 self.history.ignore = false;
439 cx.notify();
440 }
441
442 pub fn redo(&mut self, _: &mut Window, cx: &mut Context<Self>) {
444 self.history.ignore = true;
445
446 if let Some(changes) = self.history.redo() {
447 for change in changes {
448 if let Some(index) = self
449 .panels
450 .iter()
451 .position(|item| item.panel.view().entity_id() == change.tile_id)
452 {
453 if let Some(new_bounds) = change.new_bounds {
454 self.panels[index].bounds = new_bounds;
455 }
456 if let Some(new_order) = change.new_order {
457 let item = self.panels.remove(index);
458 self.panels.insert(new_order, item);
459 }
460 }
461 }
462 cx.emit(PanelEvent::LayoutChanged);
463 }
464
465 self.history.ignore = false;
466 cx.notify();
467 }
468
469 pub fn active_panel(&self, cx: &App) -> Option<Arc<dyn PanelView>> {
471 self.panels.last().and_then(|item| {
472 if let Ok(tab_panel) = item.panel.view().downcast::<TabPanel>() {
473 tab_panel.read(cx).active_panel(cx)
474 } else if let Ok(_) = item.panel.view().downcast::<StackPanel>() {
475 None
476 } else {
477 Some(item.panel.clone())
478 }
479 })
480 }
481
482 fn render_resize_handles(
484 &mut self,
485 _: &mut Window,
486 cx: &mut Context<Self>,
487 entity_id: EntityId,
488 item: &TileItem,
489 ) -> Vec<AnyElement> {
490 let item_id = item.id;
491 let item_bounds = item.bounds;
492 let handle_offset = -HANDLE_SIZE + px(1.);
493
494 let mut elements = Vec::new();
495
496 elements.push(
498 div()
499 .id("left-resize-handle")
500 .cursor_ew_resize()
501 .absolute()
502 .top_0()
503 .left(handle_offset)
504 .w(HANDLE_SIZE)
505 .h(item_bounds.size.height)
506 .on_mouse_down(
507 MouseButton::Left,
508 cx.listener({
509 move |this, event: &MouseDownEvent, window, cx| {
510 this.on_resize_handle_mouse_down(
511 ResizeSide::Left,
512 item_id,
513 item_bounds,
514 event,
515 window,
516 cx,
517 );
518 }
519 }),
520 )
521 .on_drag(DragResizing(entity_id), |drag, _, _, cx| {
522 cx.stop_propagation();
523 cx.new(|_| drag.clone())
524 })
525 .on_drag_move(cx.listener(
526 move |this, e: &DragMoveEvent<DragResizing>, window, cx| match e.drag(cx) {
527 DragResizing(id) => {
528 if *id != entity_id {
529 return;
530 }
531
532 let Some(ref drag_data) = this.resizing_drag_data else {
533 return;
534 };
535 if drag_data.side != ResizeSide::Left {
536 return;
537 }
538
539 let pos = e.event.position;
540 let delta = drag_data.last_position.x - pos.x;
541 let new_x = (drag_data.last_bounds.origin.x - delta).max(px(0.0));
542 let size_delta = drag_data.last_bounds.origin.x - new_x;
543 let new_width = (drag_data.last_bounds.size.width + size_delta)
544 .max(MINIMUM_SIZE.width);
545 this.resize(Some(new_x), None, Some(new_width), None, window, cx);
546 }
547 },
548 ))
549 .into_any_element(),
550 );
551
552 elements.push(
554 div()
555 .id("right-resize-handle")
556 .cursor_ew_resize()
557 .absolute()
558 .top_0()
559 .right(handle_offset)
560 .w(HANDLE_SIZE)
561 .h(item_bounds.size.height)
562 .on_mouse_down(
563 MouseButton::Left,
564 cx.listener({
565 move |this, event: &MouseDownEvent, window, cx| {
566 this.on_resize_handle_mouse_down(
567 ResizeSide::Right,
568 item_id,
569 item_bounds,
570 event,
571 window,
572 cx,
573 );
574 }
575 }),
576 )
577 .on_drag(DragResizing(entity_id), |drag, _, _, cx| {
578 cx.stop_propagation();
579 cx.new(|_| drag.clone())
580 })
581 .on_drag_move(cx.listener(
582 move |this, e: &DragMoveEvent<DragResizing>, window, cx| match e.drag(cx) {
583 DragResizing(id) => {
584 if *id != entity_id {
585 return;
586 }
587
588 let Some(ref drag_data) = this.resizing_drag_data else {
589 return;
590 };
591
592 if drag_data.side != ResizeSide::Right {
593 return;
594 }
595
596 let pos = e.event.position;
597 let delta = pos.x - drag_data.last_position.x;
598 let new_width =
599 (drag_data.last_bounds.size.width + delta).max(MINIMUM_SIZE.width);
600 this.resize(None, None, Some(new_width), None, window, cx);
601 }
602 },
603 ))
604 .into_any_element(),
605 );
606
607 elements.push(
609 div()
610 .id("top-resize-handle")
611 .cursor_ns_resize()
612 .absolute()
613 .left(px(0.0))
614 .top(handle_offset)
615 .w(item_bounds.size.width)
616 .h(HANDLE_SIZE)
617 .on_mouse_down(
618 MouseButton::Left,
619 cx.listener({
620 move |this, event: &MouseDownEvent, window, cx| {
621 this.on_resize_handle_mouse_down(
622 ResizeSide::Top,
623 item_id,
624 item_bounds,
625 event,
626 window,
627 cx,
628 );
629 }
630 }),
631 )
632 .on_drag(DragResizing(entity_id), |drag, _, _, cx| {
633 cx.stop_propagation();
634 cx.new(|_| drag.clone())
635 })
636 .on_drag_move(cx.listener(
637 move |this, e: &DragMoveEvent<DragResizing>, window, cx| match e.drag(cx) {
638 DragResizing(id) => {
639 if *id != entity_id {
640 return;
641 }
642
643 let Some(ref drag_data) = this.resizing_drag_data else {
644 return;
645 };
646 if drag_data.side != ResizeSide::Top {
647 return;
648 }
649
650 let pos = e.event.position;
651 let delta = drag_data.last_position.y - pos.y;
652 let new_y = (drag_data.last_bounds.origin.y - delta).max(px(0.));
653 let size_delta = drag_data.last_position.y - new_y;
654 let new_height = (drag_data.last_bounds.size.height + size_delta)
655 .max(MINIMUM_SIZE.width);
656 this.resize(None, Some(new_y), None, Some(new_height), window, cx);
657 }
658 },
659 ))
660 .into_any_element(),
661 );
662
663 elements.push(
665 div()
666 .id("bottom-resize-handle")
667 .cursor_ns_resize()
668 .absolute()
669 .left(px(0.0))
670 .bottom(handle_offset)
671 .w(item_bounds.size.width)
672 .h(HANDLE_SIZE)
673 .on_mouse_down(
674 MouseButton::Left,
675 cx.listener({
676 move |this, event: &MouseDownEvent, window, cx| {
677 this.on_resize_handle_mouse_down(
678 ResizeSide::Bottom,
679 item_id,
680 item_bounds,
681 event,
682 window,
683 cx,
684 );
685 }
686 }),
687 )
688 .on_drag(DragResizing(entity_id), |drag, _, _, cx| {
689 cx.stop_propagation();
690 cx.new(|_| drag.clone())
691 })
692 .on_drag_move(cx.listener(
693 move |this, e: &DragMoveEvent<DragResizing>, window, cx| match e.drag(cx) {
694 DragResizing(id) => {
695 if *id != entity_id {
696 return;
697 }
698
699 let Some(ref drag_data) = this.resizing_drag_data else {
700 return;
701 };
702
703 if drag_data.side != ResizeSide::Bottom {
704 return;
705 }
706
707 let pos = e.event.position;
708 let delta = pos.y - drag_data.last_position.y;
709 let new_height =
710 (drag_data.last_bounds.size.height + delta).max(MINIMUM_SIZE.width);
711 this.resize(None, None, None, Some(new_height), window, cx);
712 }
713 },
714 ))
715 .into_any_element(),
716 );
717
718 elements.push(
720 div()
721 .child(
722 Icon::new(IconName::ResizeCorner)
723 .size_3()
724 .absolute()
725 .right(px(1.))
726 .bottom(px(1.))
727 .text_color(cx.theme().muted_foreground.opacity(0.5)),
728 )
729 .child(
730 div()
731 .id("corner-resize-handle")
732 .cursor_nwse_resize()
733 .absolute()
734 .right(handle_offset)
735 .bottom(handle_offset)
736 .size_3()
737 .on_mouse_down(
738 MouseButton::Left,
739 cx.listener({
740 move |this, event: &MouseDownEvent, window, cx| {
741 this.on_resize_handle_mouse_down(
742 ResizeSide::BottomRight,
743 item_id,
744 item_bounds,
745 event,
746 window,
747 cx,
748 );
749 }
750 }),
751 )
752 .on_drag(DragResizing(entity_id), |drag, _, _, cx| {
753 cx.stop_propagation();
754 cx.new(|_| drag.clone())
755 })
756 .on_drag_move(cx.listener(
757 move |this, e: &DragMoveEvent<DragResizing>, window, cx| {
758 match e.drag(cx) {
759 DragResizing(id) => {
760 if *id != entity_id {
761 return;
762 }
763
764 let Some(ref drag_data) = this.resizing_drag_data else {
765 return;
766 };
767
768 if drag_data.side != ResizeSide::BottomRight {
769 return;
770 }
771
772 let pos = e.event.position;
773 let delta_x = pos.x - drag_data.last_position.x;
774 let delta_y = pos.y - drag_data.last_position.y;
775 let new_width = (drag_data.last_bounds.size.width
776 + delta_x)
777 .max(MINIMUM_SIZE.width);
778 let new_height = (drag_data.last_bounds.size.height
779 + delta_y)
780 .max(MINIMUM_SIZE.height);
781 this.resize(
782 None,
783 None,
784 Some(new_width),
785 Some(new_height),
786 window,
787 cx,
788 );
789 }
790 }
791 },
792 )),
793 )
794 .into_any_element(),
795 );
796
797 elements
798 }
799
800 fn on_resize_handle_mouse_down(
801 &mut self,
802 side: ResizeSide,
803 item_id: EntityId,
804 item_bounds: Bounds<Pixels>,
805 event: &MouseDownEvent,
806 _: &mut Window,
807 cx: &mut Context<'_, Self>,
808 ) {
809 let last_position = event.position;
810 self.resizing_id = Some(item_id);
811 self.resizing_drag_data = Some(ResizeDrag {
812 side,
813 last_position,
814 last_bounds: item_bounds,
815 });
816
817 if let Some(new_id) = self.bring_to_front(self.resizing_id, cx) {
818 self.resizing_id = Some(new_id);
819 }
820 cx.stop_propagation();
821 }
822
823 fn render_drag_bar(
825 &mut self,
826 _: &mut Window,
827 cx: &mut Context<Self>,
828 entity_id: EntityId,
829 item: &TileItem,
830 ) -> AnyElement {
831 let item_id = item.id;
832 let item_bounds = item.bounds;
833
834 h_flex()
835 .id("drag-bar")
836 .absolute()
837 .w_full()
838 .h(DRAG_BAR_HEIGHT)
839 .bg(cx.theme().transparent)
840 .on_mouse_down(
841 MouseButton::Left,
842 cx.listener(move |this, event: &MouseDownEvent, _, cx| {
843 let inner_pos = event.position - this.bounds.origin;
844 this.dragging_id = Some(item_id);
845 this.dragging_initial_mouse = inner_pos;
846 this.dragging_initial_bounds = item_bounds;
847
848 if let Some(new_id) = this.bring_to_front(Some(item_id), cx) {
849 this.dragging_id = Some(new_id);
850 }
851 }),
852 )
853 .on_drag(DragMoving(entity_id), |drag, _, _, cx| {
854 cx.stop_propagation();
855 cx.new(|_| drag.clone())
856 })
857 .on_drag_move(
858 cx.listener(move |this, e: &DragMoveEvent<DragMoving>, window, cx| {
859 match e.drag(cx) {
860 DragMoving(id) => {
861 if *id != entity_id {
862 return;
863 }
864 this.update_position(e.event.position, window, cx);
865 }
866 }
867 }),
868 )
869 .into_any_element()
870 }
871
872 fn render_panel(
873 &mut self,
874 item: &TileItem,
875 window: &mut Window,
876 cx: &mut Context<Self>,
877 ) -> impl IntoElement {
878 let entity_id = cx.entity_id();
879 let item_id = item.id;
880 let panel_view = item.panel.view();
881
882 v_flex()
883 .occlude()
884 .bg(cx.theme().background)
885 .border_1()
886 .border_color(cx.theme().border)
887 .absolute()
888 .left(item.bounds.origin.x)
889 .top(item.bounds.origin.y)
890 .w(item.bounds.size.width + px(1.))
892 .h(item.bounds.size.height + px(1.))
893 .rounded(cx.theme().radius)
894 .child(h_flex().overflow_hidden().size_full().child(panel_view))
895 .children(self.render_resize_handles(window, cx, entity_id, &item))
896 .child(self.render_drag_bar(window, cx, entity_id, &item))
897 .on_mouse_down(
898 MouseButton::Left,
899 cx.listener(move |this, _, _, _| {
900 this.dragging_id = Some(item_id);
901 }),
902 )
903 .on_mouse_up(
905 MouseButton::Left,
906 cx.listener(move |this, _, _, cx| {
907 if this.dragging_id == Some(item_id) {
908 this.dragging_id = None;
909 this.bring_to_front(Some(item_id), cx);
910 }
911 }),
912 )
913 }
914
915 fn on_mouse_up(&mut self, _: &mut Window, cx: &mut Context<'_, Tiles>) {
917 if self.dragging_id.is_some()
919 || self.resizing_id.is_some()
920 || self.resizing_drag_data.is_some()
921 {
922 let mut changes_to_push = vec![];
923
924 if let Some(dragging_id) = self.dragging_id {
926 if let Some(item) = self.panel(&dragging_id) {
927 let initial_bounds = self.dragging_initial_bounds;
928 let current_bounds = item.bounds;
929 if initial_bounds.origin != current_bounds.origin
930 || initial_bounds.size != current_bounds.size
931 {
932 changes_to_push.push(TileChange {
933 tile_id: item.panel.view().entity_id(),
934 old_bounds: Some(initial_bounds),
935 new_bounds: Some(current_bounds),
936 old_order: None,
937 new_order: None,
938 version: 0,
939 });
940 }
941 }
942 }
943
944 if let Some(resizing_id) = self.resizing_id {
946 if let Some(drag_data) = &self.resizing_drag_data {
947 if let Some(item) = self.panel(&resizing_id) {
948 let initial_bounds = drag_data.last_bounds;
949 let current_bounds = item.bounds;
950 if initial_bounds.size != current_bounds.size {
951 changes_to_push.push(TileChange {
952 tile_id: item.panel.view().entity_id(),
953 old_bounds: Some(initial_bounds),
954 new_bounds: Some(current_bounds),
955 old_order: None,
956 new_order: None,
957 version: 0,
958 });
959 }
960 }
961 }
962 }
963
964 if !changes_to_push.is_empty() {
966 for change in changes_to_push {
967 self.history.push(change);
968 }
969 }
970
971 self.reset_current_index();
973 self.resizing_drag_data = None;
974 cx.emit(PanelEvent::LayoutChanged);
975 cx.notify();
976 }
977 }
978}
979
980#[inline]
981fn round_to_nearest_ten(value: Pixels, cx: &App) -> Pixels {
982 (value / cx.theme().tile_grid_size).round() * cx.theme().tile_grid_size
983}
984
985#[inline]
986fn round_point_to_nearest_ten(point: Point<Pixels>, cx: &App) -> Point<Pixels> {
987 Point::new(
988 round_to_nearest_ten(point.x, cx),
989 round_to_nearest_ten(point.y, cx),
990 )
991}
992
993impl Focusable for Tiles {
994 fn focus_handle(&self, _cx: &App) -> FocusHandle {
995 self.focus_handle.clone()
996 }
997}
998impl EventEmitter<PanelEvent> for Tiles {}
999impl EventEmitter<DismissEvent> for Tiles {}
1000impl Render for Tiles {
1001 fn render(&mut self, window: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
1002 let view = cx.entity().clone();
1003 let panels = self.sorted_panels();
1004 let scroll_bounds =
1005 self.panels
1006 .iter()
1007 .fold(Bounds::default(), |acc: Bounds<Pixels>, item| Bounds {
1008 origin: Point {
1009 x: acc.origin.x.min(item.bounds.origin.x),
1010 y: acc.origin.y.min(item.bounds.origin.y),
1011 },
1012 size: Size {
1013 width: acc.size.width.max(item.bounds.right()),
1014 height: acc.size.height.max(item.bounds.bottom()),
1015 },
1016 });
1017 let scroll_size = scroll_bounds.size - size(scroll_bounds.origin.x, scroll_bounds.origin.y);
1018
1019 div()
1020 .relative()
1021 .bg(cx.theme().tiles)
1022 .child(
1023 div()
1024 .id("tiles")
1025 .track_scroll(&self.scroll_handle)
1026 .size_full()
1027 .top(-px(1.))
1028 .overflow_scroll()
1029 .children(
1030 panels
1031 .into_iter()
1032 .map(|item| self.render_panel(&item, window, cx)),
1033 )
1034 .child({
1035 canvas(
1036 move |bounds, _, cx| view.update(cx, |r, _| r.bounds = bounds),
1037 |_, _, _, _| {},
1038 )
1039 .absolute()
1040 .size_full()
1041 })
1042 .on_drop(cx.listener(move |_, item: &AnyDrag, _, cx| {
1043 cx.emit(DragDrop(item.clone()));
1044 })),
1045 )
1046 .on_mouse_up(
1047 MouseButton::Left,
1048 cx.listener(move |this, _event: &MouseUpEvent, window, cx| {
1049 this.on_mouse_up(window, cx);
1050 }),
1051 )
1052 .child(
1053 div()
1054 .absolute()
1055 .top_0()
1056 .left_0()
1057 .right_0()
1058 .bottom_0()
1059 .child(
1060 Scrollbar::both(&self.scroll_state, &self.scroll_handle)
1061 .scroll_size(scroll_size),
1062 ),
1063 )
1064 .size_full()
1065 }
1066}