rg3d_ui/
dock.rs

1//! Docking manager allows you to dock windows and hold them in-place.
2//!
3//! # Notes
4//!
5//! Docking manager can hold any types of UI elements, but dragging works only
6//! for windows.
7
8use crate::{
9    border::BorderBuilder,
10    brush::Brush,
11    core::{algebra::Vector2, color::Color, math::Rect, pool::Handle},
12    define_constructor,
13    grid::{Column, GridBuilder, Row},
14    message::{CursorIcon, MessageDirection, UiMessage},
15    widget::{Widget, WidgetBuilder, WidgetMessage},
16    window::{Window, WindowMessage},
17    BuildContext, Control, NodeHandleMapping, Thickness, UiNode, UserInterface,
18};
19use std::{
20    any::{Any, TypeId},
21    cell::{Cell, RefCell},
22    ops::{Deref, DerefMut},
23};
24
25#[derive(Debug, Clone, PartialEq)]
26pub enum TileMessage {
27    Content(TileContent),
28    /// Internal. Do not use.
29    Split {
30        window: Handle<UiNode>,
31        direction: SplitDirection,
32        first: bool,
33    },
34}
35
36impl TileMessage {
37    define_constructor!(TileMessage:Content => fn content(TileContent), layout: false);
38    define_constructor!(TileMessage:Split => fn split(window: Handle<UiNode>,
39        direction: SplitDirection,
40        first: bool), layout: false);
41}
42
43#[derive(Debug, PartialEq, Clone)]
44pub enum TileContent {
45    Empty,
46    Window(Handle<UiNode>),
47    VerticalTiles {
48        splitter: f32,
49        /// Docking system requires tiles to be handles to Tile instances.
50        /// However any node handle is acceptable, but in this case docking
51        /// will most likely not work.
52        tiles: [Handle<UiNode>; 2],
53    },
54    HorizontalTiles {
55        splitter: f32,
56        /// Docking system requires tiles to be handles to Tile instances.
57        /// However any node handle is acceptable, but in this case docking
58        /// will most likely not work.
59        tiles: [Handle<UiNode>; 2],
60    },
61}
62
63impl TileContent {
64    pub fn is_empty(&self) -> bool {
65        matches!(self, TileContent::Empty)
66    }
67}
68
69#[derive(Clone)]
70pub struct Tile {
71    widget: Widget,
72    left_anchor: Handle<UiNode>,
73    right_anchor: Handle<UiNode>,
74    top_anchor: Handle<UiNode>,
75    bottom_anchor: Handle<UiNode>,
76    center_anchor: Handle<UiNode>,
77    content: TileContent,
78    splitter: Handle<UiNode>,
79    dragging_splitter: bool,
80    drop_anchor: Cell<Handle<UiNode>>,
81}
82
83crate::define_widget_deref!(Tile);
84
85impl Control for Tile {
86    fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
87        if type_id == TypeId::of::<Self>() {
88            Some(self)
89        } else {
90            None
91        }
92    }
93
94    fn resolve(&mut self, node_map: &NodeHandleMapping) {
95        node_map.resolve_cell(&mut self.drop_anchor);
96        node_map.resolve(&mut self.splitter);
97        node_map.resolve(&mut self.center_anchor);
98        node_map.resolve(&mut self.bottom_anchor);
99        node_map.resolve(&mut self.top_anchor);
100        node_map.resolve(&mut self.right_anchor);
101        node_map.resolve(&mut self.left_anchor);
102        match &mut self.content {
103            TileContent::Empty => {}
104            TileContent::Window(window) => node_map.resolve(window),
105            TileContent::VerticalTiles { tiles, .. }
106            | TileContent::HorizontalTiles { tiles, .. } => {
107                for tile in tiles {
108                    node_map.resolve(tile);
109                }
110            }
111        }
112    }
113
114    fn measure_override(&self, ui: &UserInterface, available_size: Vector2<f32>) -> Vector2<f32> {
115        for &child_handle in self.children() {
116            // Determine available size for each child by its kind:
117            // - Every child not in content of tile just takes whole available size.
118            // - Every content's child uses specific available measure size.
119            // This is a bit weird, but it is how it works.
120            let available_size = match self.content {
121                TileContent::VerticalTiles {
122                    splitter,
123                    ref tiles,
124                } => {
125                    if tiles[0] == child_handle {
126                        Vector2::new(available_size.x, available_size.y * splitter)
127                    } else if tiles[1] == child_handle {
128                        Vector2::new(available_size.x, available_size.y * (1.0 - splitter))
129                    } else {
130                        available_size
131                    }
132                }
133                TileContent::HorizontalTiles {
134                    splitter,
135                    ref tiles,
136                } => {
137                    if tiles[0] == child_handle {
138                        Vector2::new(available_size.x * splitter, available_size.y)
139                    } else if tiles[1] == child_handle {
140                        Vector2::new(available_size.x * (1.0 - splitter), available_size.y)
141                    } else {
142                        available_size
143                    }
144                }
145                _ => available_size,
146            };
147
148            ui.measure_node(child_handle, available_size);
149        }
150
151        available_size
152    }
153
154    fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
155        let splitter_size = ui.node(self.splitter).desired_size();
156
157        for &child_handle in self.children() {
158            let full_bounds = Rect::new(0.0, 0.0, final_size.x, final_size.y);
159
160            let bounds = match self.content {
161                TileContent::VerticalTiles {
162                    splitter,
163                    ref tiles,
164                } => {
165                    if tiles[0] == child_handle {
166                        Rect::new(
167                            0.0,
168                            0.0,
169                            final_size.x,
170                            final_size.y * splitter - splitter_size.y * 0.5,
171                        )
172                    } else if tiles[1] == child_handle {
173                        Rect::new(
174                            0.0,
175                            final_size.y * splitter + splitter_size.y * 0.5,
176                            final_size.x,
177                            final_size.y * (1.0 - splitter) - splitter_size.y,
178                        )
179                    } else if self.splitter == child_handle {
180                        Rect::new(
181                            0.0,
182                            final_size.y * splitter - splitter_size.y * 0.5,
183                            final_size.x,
184                            splitter_size.y,
185                        )
186                    } else {
187                        full_bounds
188                    }
189                }
190                TileContent::HorizontalTiles {
191                    splitter,
192                    ref tiles,
193                } => {
194                    if tiles[0] == child_handle {
195                        Rect::new(
196                            0.0,
197                            0.0,
198                            final_size.x * splitter - splitter_size.x * 0.5,
199                            final_size.y,
200                        )
201                    } else if tiles[1] == child_handle {
202                        Rect::new(
203                            final_size.x * splitter + splitter_size.x * 0.5,
204                            0.0,
205                            final_size.x * (1.0 - splitter) - splitter_size.x * 0.5,
206                            final_size.y,
207                        )
208                    } else if self.splitter == child_handle {
209                        Rect::new(
210                            final_size.x * splitter - splitter_size.x * 0.5,
211                            0.0,
212                            splitter_size.x,
213                            final_size.y,
214                        )
215                    } else {
216                        full_bounds
217                    }
218                }
219                _ => full_bounds,
220            };
221
222            ui.arrange_node(child_handle, &bounds);
223
224            // Main difference between tile arrangement and other arrangement methods in
225            // library is that tile has to explicitly set width of child windows, otherwise
226            // layout will be weird - window will most likely will stay at its previous size.
227            if child_handle != self.splitter {
228                ui.send_message(WidgetMessage::width(
229                    child_handle,
230                    MessageDirection::ToWidget,
231                    bounds.w(),
232                ));
233                ui.send_message(WidgetMessage::height(
234                    child_handle,
235                    MessageDirection::ToWidget,
236                    bounds.h(),
237                ));
238            }
239        }
240
241        final_size
242    }
243
244    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
245        self.widget.handle_routed_message(ui, message);
246
247        if let Some(msg) = message.data::<TileMessage>() {
248            if message.destination() == self.handle() {
249                match msg {
250                    TileMessage::Content(content) => {
251                        self.content = content.clone();
252
253                        match content {
254                            TileContent::Empty => {
255                                ui.send_message(WidgetMessage::visibility(
256                                    self.splitter,
257                                    MessageDirection::ToWidget,
258                                    false,
259                                ));
260                            }
261                            &TileContent::Window(window) => {
262                                ui.send_message(WidgetMessage::link(
263                                    window,
264                                    MessageDirection::ToWidget,
265                                    self.handle(),
266                                ));
267
268                                ui.send_message(WidgetMessage::visibility(
269                                    self.splitter,
270                                    MessageDirection::ToWidget,
271                                    false,
272                                ));
273
274                                ui.send_message(WindowMessage::can_resize(
275                                    window,
276                                    MessageDirection::ToWidget,
277                                    false,
278                                ));
279                            }
280                            TileContent::VerticalTiles { tiles, .. }
281                            | TileContent::HorizontalTiles { tiles, .. } => {
282                                for &tile in tiles {
283                                    ui.send_message(WidgetMessage::link(
284                                        tile,
285                                        MessageDirection::ToWidget,
286                                        self.handle(),
287                                    ));
288                                }
289
290                                ui.send_message(WidgetMessage::visibility(
291                                    self.splitter,
292                                    MessageDirection::ToWidget,
293                                    true,
294                                ));
295
296                                match content {
297                                    TileContent::HorizontalTiles { .. } => {
298                                        ui.send_message(WidgetMessage::width(
299                                            self.splitter,
300                                            MessageDirection::ToWidget,
301                                            DEFAULT_SPLITTER_SIZE,
302                                        ));
303                                        ui.send_message(WidgetMessage::height(
304                                            self.splitter,
305                                            MessageDirection::ToWidget,
306                                            f32::INFINITY,
307                                        ));
308                                    }
309                                    TileContent::VerticalTiles { .. } => {
310                                        ui.send_message(WidgetMessage::width(
311                                            self.splitter,
312                                            MessageDirection::ToWidget,
313                                            f32::INFINITY,
314                                        ));
315                                        ui.send_message(WidgetMessage::height(
316                                            self.splitter,
317                                            MessageDirection::ToWidget,
318                                            DEFAULT_SPLITTER_SIZE,
319                                        ));
320                                    }
321                                    _ => (),
322                                }
323                            }
324                        }
325                    }
326                    &TileMessage::Split {
327                        window,
328                        direction,
329                        first,
330                    } => {
331                        if matches!(self.content, TileContent::Window(_)) {
332                            self.split(ui, window, direction, first);
333                        }
334                    }
335                }
336            }
337        } else if let Some(msg) = message.data::<WidgetMessage>() {
338            match msg {
339                &WidgetMessage::MouseDown { .. } => {
340                    if !message.handled() && message.destination() == self.splitter {
341                        message.set_handled(true);
342                        self.dragging_splitter = true;
343                        ui.capture_mouse(self.splitter);
344                    }
345                }
346                &WidgetMessage::MouseUp { .. } => {
347                    if !message.handled() && message.destination() == self.splitter {
348                        message.set_handled(true);
349                        self.dragging_splitter = false;
350                        ui.release_mouse_capture();
351                    }
352                }
353                &WidgetMessage::MouseMove { pos, .. } => {
354                    if self.dragging_splitter {
355                        let bounds = self.screen_bounds();
356                        match self.content {
357                            TileContent::VerticalTiles {
358                                ref mut splitter, ..
359                            } => {
360                                *splitter = ((pos.y - bounds.y()) / bounds.h()).max(0.0).min(1.0);
361                                self.invalidate_layout();
362                            }
363                            TileContent::HorizontalTiles {
364                                ref mut splitter, ..
365                            } => {
366                                *splitter = ((pos.x - bounds.x()) / bounds.w()).max(0.0).min(1.0);
367                                self.invalidate_layout();
368                            }
369                            _ => (),
370                        }
371                    }
372                }
373                WidgetMessage::Unlink => {
374                    // Check if this tile can be removed: only if it is split and sub-tiles are empty.
375                    match self.content {
376                        TileContent::VerticalTiles { tiles, .. }
377                        | TileContent::HorizontalTiles { tiles, .. } => {
378                            let mut has_empty_sub_tile = false;
379                            for &tile in &tiles {
380                                if let Some(sub_tile) = ui.node(tile).cast::<Tile>() {
381                                    if let TileContent::Empty = sub_tile.content {
382                                        has_empty_sub_tile = true;
383                                        break;
384                                    }
385                                }
386                            }
387                            if has_empty_sub_tile {
388                                for &tile in &tiles {
389                                    if let Some(sub_tile) = ui.node(tile).cast::<Tile>() {
390                                        match sub_tile.content {
391                                            TileContent::Window(sub_tile_wnd) => {
392                                                // If we have only a tile with a window, then detach window and schedule
393                                                // linking with current tile.
394                                                ui.send_message(WidgetMessage::unlink(
395                                                    sub_tile_wnd,
396                                                    MessageDirection::ToWidget,
397                                                ));
398
399                                                ui.send_message(TileMessage::content(
400                                                    self.handle,
401                                                    MessageDirection::ToWidget,
402                                                    TileContent::Window(sub_tile_wnd),
403                                                ));
404                                                // Splitter must be hidden.
405                                                ui.send_message(WidgetMessage::visibility(
406                                                    self.splitter,
407                                                    MessageDirection::ToWidget,
408                                                    false,
409                                                ));
410                                            }
411                                            // In case if we have a split tile (vertically or horizontally) left in current tile
412                                            // (which is split too) we must set content of current tile to content of sub tile.
413                                            TileContent::VerticalTiles {
414                                                splitter,
415                                                tiles: sub_tiles,
416                                            } => {
417                                                for &sub_tile in &sub_tiles {
418                                                    ui.send_message(WidgetMessage::unlink(
419                                                        sub_tile,
420                                                        MessageDirection::ToWidget,
421                                                    ));
422                                                }
423                                                // Transfer sub tiles to current tile.
424                                                ui.send_message(TileMessage::content(
425                                                    self.handle,
426                                                    MessageDirection::ToWidget,
427                                                    TileContent::VerticalTiles {
428                                                        splitter,
429                                                        tiles: sub_tiles,
430                                                    },
431                                                ));
432                                            }
433                                            TileContent::HorizontalTiles {
434                                                splitter,
435                                                tiles: sub_tiles,
436                                            } => {
437                                                for &sub_tile in &sub_tiles {
438                                                    ui.send_message(WidgetMessage::unlink(
439                                                        sub_tile,
440                                                        MessageDirection::ToWidget,
441                                                    ));
442                                                }
443                                                // Transfer sub tiles to current tile.
444                                                ui.send_message(TileMessage::content(
445                                                    self.handle,
446                                                    MessageDirection::ToWidget,
447                                                    TileContent::HorizontalTiles {
448                                                        splitter,
449                                                        tiles: sub_tiles,
450                                                    },
451                                                ));
452                                            }
453                                            _ => {}
454                                        }
455                                    }
456                                }
457
458                                // Destroy tiles.
459                                for &tile in &tiles {
460                                    ui.send_message(WidgetMessage::remove(
461                                        tile,
462                                        MessageDirection::ToWidget,
463                                    ));
464                                }
465                            }
466                        }
467                        _ => (),
468                    }
469                }
470                _ => {}
471            }
472            // We can catch any message from window while it docked.
473        } else if let Some(WindowMessage::Move(_)) = message.data::<WindowMessage>() {
474            // Check if we dragging child window.
475            let content_moved = match self.content {
476                TileContent::Window(window) => window == message.destination(),
477                _ => false,
478            };
479
480            if content_moved {
481                if let Some(window) = ui.node(message.destination()).cast::<Window>() {
482                    if window.drag_delta().norm() > 20.0 {
483                        ui.send_message(TileMessage::content(
484                            self.handle,
485                            MessageDirection::ToWidget,
486                            TileContent::Empty,
487                        ));
488
489                        ui.send_message(WidgetMessage::unlink(
490                            message.destination(),
491                            MessageDirection::ToWidget,
492                        ));
493
494                        ui.send_message(WindowMessage::can_resize(
495                            message.destination(),
496                            MessageDirection::ToWidget,
497                            true,
498                        ));
499
500                        if let Some((_, docking_manager)) =
501                            ui.try_borrow_by_type_up::<DockingManager>(self.parent())
502                        {
503                            docking_manager
504                                .floating_windows
505                                .borrow_mut()
506                                .push(message.destination());
507                        }
508                    }
509                }
510            }
511        }
512    }
513
514    // We have to use preview_message for docking purposes because dragged window detached
515    // from docking manager and handle_routed_message won't receive any messages from window.
516    fn preview_message(&self, ui: &UserInterface, message: &mut UiMessage) {
517        if let Some(WidgetMessage::Unlink) = message.data::<WidgetMessage>() {
518            if let TileContent::Empty | TileContent::Window(_) = self.content {
519                // Show anchors.
520                for &anchor in &self.anchors() {
521                    ui.send_message(WidgetMessage::visibility(
522                        anchor,
523                        MessageDirection::ToWidget,
524                        true,
525                    ));
526                }
527            }
528        } else if let Some(msg) = message.data::<WindowMessage>() {
529            if let Some((_, docking_manager)) =
530                ui.try_borrow_by_type_up::<DockingManager>(self.parent())
531            {
532                // Make sure we are dragging one of floating windows of parent docking manager.
533                if docking_manager
534                    .floating_windows
535                    .borrow_mut()
536                    .contains(&message.destination())
537                {
538                    match msg {
539                        &WindowMessage::Move(_) => {
540                            // Window can be docked only if current tile is not split already.
541                            if let TileContent::Empty | TileContent::Window(_) = self.content {
542                                // When window is being dragged, we should check which tile can accept it.
543                                let pos = ui.cursor_position;
544                                for &anchor in &self.anchors() {
545                                    ui.send_message(WidgetMessage::background(
546                                        anchor,
547                                        MessageDirection::ToWidget,
548                                        Brush::Solid(DEFAULT_ANCHOR_COLOR),
549                                    ))
550                                }
551                                if ui.node(self.left_anchor).screen_bounds().contains(pos) {
552                                    ui.send_message(WidgetMessage::background(
553                                        self.left_anchor,
554                                        MessageDirection::ToWidget,
555                                        Brush::Solid(Color::WHITE),
556                                    ));
557                                    self.drop_anchor.set(self.left_anchor);
558                                } else if ui.node(self.right_anchor).screen_bounds().contains(pos) {
559                                    ui.send_message(WidgetMessage::background(
560                                        self.right_anchor,
561                                        MessageDirection::ToWidget,
562                                        Brush::Solid(Color::WHITE),
563                                    ));
564                                    self.drop_anchor.set(self.right_anchor);
565                                } else if ui.node(self.top_anchor).screen_bounds().contains(pos) {
566                                    ui.send_message(WidgetMessage::background(
567                                        self.top_anchor,
568                                        MessageDirection::ToWidget,
569                                        Brush::Solid(Color::WHITE),
570                                    ));
571                                    self.drop_anchor.set(self.top_anchor);
572                                } else if ui.node(self.bottom_anchor).screen_bounds().contains(pos)
573                                {
574                                    ui.send_message(WidgetMessage::background(
575                                        self.bottom_anchor,
576                                        MessageDirection::ToWidget,
577                                        Brush::Solid(Color::WHITE),
578                                    ));
579                                    self.drop_anchor.set(self.bottom_anchor);
580                                } else if ui.node(self.center_anchor).screen_bounds().contains(pos)
581                                {
582                                    ui.send_message(WidgetMessage::background(
583                                        self.center_anchor,
584                                        MessageDirection::ToWidget,
585                                        Brush::Solid(Color::WHITE),
586                                    ));
587                                    self.drop_anchor.set(self.center_anchor);
588                                } else {
589                                    self.drop_anchor.set(Handle::NONE);
590                                }
591                            }
592                        }
593                        WindowMessage::MoveStart => {
594                            if let TileContent::Empty | TileContent::Window(_) = self.content {
595                                // Show anchors.
596                                for &anchor in &self.anchors() {
597                                    ui.send_message(WidgetMessage::visibility(
598                                        anchor,
599                                        MessageDirection::ToWidget,
600                                        true,
601                                    ));
602                                }
603                            }
604                        }
605                        WindowMessage::MoveEnd => {
606                            // Hide anchors.
607                            for &anchor in &self.anchors() {
608                                ui.send_message(WidgetMessage::visibility(
609                                    anchor,
610                                    MessageDirection::ToWidget,
611                                    false,
612                                ));
613                            }
614
615                            // Drop if has any drop anchor.
616                            if self.drop_anchor.get().is_some() {
617                                match self.content {
618                                    TileContent::Empty => {
619                                        if self.drop_anchor.get() == self.center_anchor {
620                                            ui.send_message(TileMessage::content(
621                                                self.handle,
622                                                MessageDirection::ToWidget,
623                                                TileContent::Window(message.destination()),
624                                            ));
625                                            ui.send_message(WidgetMessage::link(
626                                                message.destination(),
627                                                MessageDirection::ToWidget,
628                                                self.handle,
629                                            ));
630                                        }
631                                    }
632                                    TileContent::Window(_) => {
633                                        if self.drop_anchor.get() == self.left_anchor {
634                                            // Split horizontally, dock to left.
635                                            ui.send_message(TileMessage::split(
636                                                self.handle,
637                                                MessageDirection::ToWidget,
638                                                message.destination(),
639                                                SplitDirection::Horizontal,
640                                                true,
641                                            ));
642                                        } else if self.drop_anchor.get() == self.right_anchor {
643                                            // Split horizontally, dock to right.
644                                            ui.send_message(TileMessage::split(
645                                                self.handle,
646                                                MessageDirection::ToWidget,
647                                                message.destination(),
648                                                SplitDirection::Horizontal,
649                                                false,
650                                            ));
651                                        } else if self.drop_anchor.get() == self.top_anchor {
652                                            // Split vertically, dock to top.
653                                            ui.send_message(TileMessage::split(
654                                                self.handle,
655                                                MessageDirection::ToWidget,
656                                                message.destination(),
657                                                SplitDirection::Vertical,
658                                                true,
659                                            ));
660                                        } else if self.drop_anchor.get() == self.bottom_anchor {
661                                            // Split vertically, dock to bottom.
662                                            ui.send_message(TileMessage::split(
663                                                self.handle,
664                                                MessageDirection::ToWidget,
665                                                message.destination(),
666                                                SplitDirection::Vertical,
667                                                false,
668                                            ));
669                                        }
670                                    }
671                                    // Rest cannot accept windows.
672                                    _ => (),
673                                }
674                            }
675                        }
676                        _ => (),
677                    }
678                }
679            }
680        }
681    }
682}
683
684#[derive(Debug, Clone, Copy, Eq, PartialEq)]
685pub enum SplitDirection {
686    Horizontal,
687    Vertical,
688}
689
690impl Tile {
691    pub fn anchors(&self) -> [Handle<UiNode>; 5] {
692        [
693            self.left_anchor,
694            self.right_anchor,
695            self.top_anchor,
696            self.bottom_anchor,
697            self.center_anchor,
698        ]
699    }
700
701    fn split(
702        &mut self,
703        ui: &mut UserInterface,
704        window: Handle<UiNode>,
705        direction: SplitDirection,
706        first: bool,
707    ) {
708        let existing_content = match self.content {
709            TileContent::Window(existing_window) => existing_window,
710            _ => Handle::NONE,
711        };
712
713        let first_tile = TileBuilder::new(WidgetBuilder::new())
714            .with_content({
715                if first {
716                    TileContent::Window(window)
717                } else {
718                    TileContent::Empty
719                }
720            })
721            .build(&mut ui.build_ctx());
722
723        let second_tile = TileBuilder::new(WidgetBuilder::new())
724            .with_content({
725                if first {
726                    TileContent::Empty
727                } else {
728                    TileContent::Window(window)
729                }
730            })
731            .build(&mut ui.build_ctx());
732
733        if existing_content.is_some() {
734            ui.send_message(TileMessage::content(
735                if first { second_tile } else { first_tile },
736                MessageDirection::ToWidget,
737                TileContent::Window(existing_content),
738            ));
739        }
740
741        ui.send_message(TileMessage::content(
742            self.handle,
743            MessageDirection::ToWidget,
744            match direction {
745                SplitDirection::Horizontal => TileContent::HorizontalTiles {
746                    tiles: [first_tile, second_tile],
747                    splitter: 0.5,
748                },
749                SplitDirection::Vertical => TileContent::VerticalTiles {
750                    tiles: [first_tile, second_tile],
751                    splitter: 0.5,
752                },
753            },
754        ));
755    }
756}
757
758#[derive(Clone)]
759pub struct DockingManager {
760    widget: Widget,
761    floating_windows: RefCell<Vec<Handle<UiNode>>>,
762}
763
764crate::define_widget_deref!(DockingManager);
765
766impl Control for DockingManager {
767    fn query_component(&self, type_id: TypeId) -> Option<&dyn Any> {
768        if type_id == TypeId::of::<Self>() {
769            Some(self)
770        } else {
771            None
772        }
773    }
774
775    fn resolve(&mut self, node_map: &NodeHandleMapping) {
776        node_map.resolve_slice(&mut self.floating_windows.borrow_mut());
777    }
778
779    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
780        self.widget.handle_routed_message(ui, message);
781    }
782
783    fn preview_message(&self, _ui: &UserInterface, message: &mut UiMessage) {
784        if let Some(WidgetMessage::LinkWith(_)) = message.data::<WidgetMessage>() {
785            let pos = self
786                .floating_windows
787                .borrow()
788                .iter()
789                .position(|&i| i == message.destination());
790            if let Some(pos) = pos {
791                self.floating_windows.borrow_mut().remove(pos);
792            }
793        }
794    }
795}
796
797pub struct DockingManagerBuilder {
798    widget_builder: WidgetBuilder,
799    floating_windows: Vec<Handle<UiNode>>,
800}
801
802impl DockingManagerBuilder {
803    pub fn new(widget_builder: WidgetBuilder) -> Self {
804        Self {
805            widget_builder,
806            floating_windows: Default::default(),
807        }
808    }
809
810    pub fn with_floating_windows(mut self, windows: Vec<Handle<UiNode>>) -> Self {
811        self.floating_windows = windows;
812        self
813    }
814
815    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
816        let docking_manager = DockingManager {
817            widget: self.widget_builder.with_preview_messages(true).build(),
818            floating_windows: RefCell::new(self.floating_windows),
819        };
820
821        ctx.add_node(UiNode::new(docking_manager))
822    }
823}
824
825pub struct TileBuilder {
826    widget_builder: WidgetBuilder,
827    content: TileContent,
828}
829
830pub const DEFAULT_SPLITTER_SIZE: f32 = 4.0;
831pub const DEFAULT_ANCHOR_COLOR: Color = Color::opaque(150, 150, 150);
832
833pub fn make_default_anchor(ctx: &mut BuildContext, row: usize, column: usize) -> Handle<UiNode> {
834    let default_anchor_size = 30.0;
835    BorderBuilder::new(
836        WidgetBuilder::new()
837            .with_width(default_anchor_size)
838            .with_height(default_anchor_size)
839            .with_visibility(false)
840            .on_row(row)
841            .on_column(column)
842            .with_draw_on_top(true)
843            .with_background(Brush::Solid(DEFAULT_ANCHOR_COLOR)),
844    )
845    .build(ctx)
846}
847
848impl TileBuilder {
849    pub fn new(widget_builder: WidgetBuilder) -> Self {
850        Self {
851            widget_builder,
852            content: TileContent::Empty,
853        }
854    }
855
856    pub fn with_content(mut self, content: TileContent) -> Self {
857        self.content = content;
858        self
859    }
860
861    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
862        let left_anchor = make_default_anchor(ctx, 2, 1);
863        let right_anchor = make_default_anchor(ctx, 2, 3);
864        let dock_anchor = make_default_anchor(ctx, 2, 2);
865        let top_anchor = make_default_anchor(ctx, 1, 2);
866        let bottom_anchor = make_default_anchor(ctx, 3, 2);
867
868        let grid = GridBuilder::new(
869            WidgetBuilder::new()
870                .with_child(left_anchor)
871                .with_child(dock_anchor)
872                .with_child(right_anchor)
873                .with_child(top_anchor)
874                .with_child(bottom_anchor),
875        )
876        .add_row(Row::stretch())
877        .add_row(Row::auto())
878        .add_row(Row::auto())
879        .add_row(Row::auto())
880        .add_row(Row::stretch())
881        .add_column(Column::stretch())
882        .add_column(Column::auto())
883        .add_column(Column::auto())
884        .add_column(Column::auto())
885        .add_column(Column::stretch())
886        .build(ctx);
887
888        let splitter = BorderBuilder::new(
889            WidgetBuilder::new()
890                .with_width({
891                    if let TileContent::HorizontalTiles { .. } = self.content {
892                        DEFAULT_SPLITTER_SIZE
893                    } else {
894                        f32::INFINITY
895                    }
896                })
897                .with_height({
898                    if let TileContent::VerticalTiles { .. } = self.content {
899                        DEFAULT_SPLITTER_SIZE
900                    } else {
901                        f32::INFINITY
902                    }
903                })
904                .with_visibility(matches!(
905                    self.content,
906                    TileContent::VerticalTiles { .. } | TileContent::HorizontalTiles { .. }
907                ))
908                .with_cursor(match self.content {
909                    TileContent::HorizontalTiles { .. } => Some(CursorIcon::WResize),
910                    TileContent::VerticalTiles { .. } => Some(CursorIcon::NResize),
911                    _ => None,
912                })
913                .with_margin(Thickness::uniform(1.0)),
914        )
915        .build(ctx);
916
917        if let TileContent::Window(window) = self.content {
918            if let Some(window) = ctx[window].cast_mut::<Window>() {
919                // Every docked window must be non-resizable (it means that it cannot be resized by user
920                // and it still can be resized by a proper message).
921                window.set_can_resize(false);
922            }
923        }
924
925        let children = match self.content {
926            TileContent::Window(window) => vec![window],
927            TileContent::VerticalTiles { tiles, .. } => vec![tiles[0], tiles[1]],
928            TileContent::HorizontalTiles { tiles, .. } => vec![tiles[0], tiles[1]],
929            _ => vec![],
930        };
931
932        let tile = Tile {
933            widget: self
934                .widget_builder
935                .with_preview_messages(true)
936                .with_child(grid)
937                .with_child(splitter)
938                .with_children(children)
939                .build(),
940            left_anchor,
941            right_anchor,
942            top_anchor,
943            bottom_anchor,
944            center_anchor: dock_anchor,
945            content: self.content,
946            splitter,
947            dragging_splitter: false,
948            drop_anchor: Default::default(),
949        };
950
951        ctx.add_node(UiNode::new(tile))
952    }
953}