fyrox_ui/dock/
mod.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21//! Docking manager allows you to dock windows and hold them in-place.
22//!
23//! # Notes
24//!
25//! Docking manager can hold any types of UI elements, but dragging works only
26//! for windows.
27
28use crate::{
29    core::{
30        log::Log, pool::Handle, reflect::prelude::*, type_traits::prelude::*, uuid_provider,
31        visitor::prelude::*,
32    },
33    define_constructor,
34    dock::config::{DockingManagerLayoutDescriptor, FloatingWindowDescriptor, TileDescriptor},
35    message::{MessageDirection, UiMessage},
36    widget::{Widget, WidgetBuilder, WidgetMessage},
37    window::WindowMessage,
38    BuildContext, Control, UiNode, UserInterface,
39};
40use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
41use fyrox_graph::{BaseSceneGraph, SceneGraph};
42use std::{
43    cell::RefCell,
44    ops::{Deref, DerefMut},
45};
46
47pub mod config;
48mod tile;
49
50pub use tile::*;
51
52/// Supported docking manager-specific messages.
53#[derive(Debug, Clone, PartialEq)]
54pub enum DockingManagerMessage {
55    Layout(DockingManagerLayoutDescriptor),
56    AddFloatingWindow(Handle<UiNode>),
57    RemoveFloatingWindow(Handle<UiNode>),
58}
59
60impl DockingManagerMessage {
61    define_constructor!(
62        /// Creates a new [Self::Layout] message.
63        DockingManagerMessage:Layout => fn layout(DockingManagerLayoutDescriptor), layout: false
64    );
65    define_constructor!(
66        /// Creates a new [Self::AddFloatingWindow] message.
67        DockingManagerMessage:AddFloatingWindow => fn add_floating_window(Handle<UiNode>), layout: false
68    );
69    define_constructor!(
70        /// Creates a new [Self::RemoveFloatingWindow] message.
71        DockingManagerMessage:RemoveFloatingWindow => fn remove_floating_window(Handle<UiNode>), layout: false
72    );
73}
74
75#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)]
76pub struct DockingManager {
77    pub widget: Widget,
78    pub floating_windows: RefCell<Vec<Handle<UiNode>>>,
79}
80
81impl ConstructorProvider<UiNode, UserInterface> for DockingManager {
82    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
83        GraphNodeConstructor::new::<Self>()
84            .with_variant("Docking Manager", |ui| {
85                DockingManagerBuilder::new(WidgetBuilder::new().with_name("Docking Manager"))
86                    .build(&mut ui.build_ctx())
87                    .into()
88            })
89            .with_group("Layout")
90    }
91}
92
93crate::define_widget_deref!(DockingManager);
94
95uuid_provider!(DockingManager = "b04299f7-3f6b-45f1-89a6-0dce4ad929e1");
96
97impl Control for DockingManager {
98    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
99        self.widget.handle_routed_message(ui, message);
100
101        if message.destination() == self.handle && message.direction() == MessageDirection::ToWidget
102        {
103            if let Some(msg) = message.data() {
104                match msg {
105                    DockingManagerMessage::Layout(layout_descriptor) => {
106                        self.set_layout(layout_descriptor, ui);
107                    }
108                    DockingManagerMessage::AddFloatingWindow(window) => {
109                        self.add_floating_window(*window)
110                    }
111                    DockingManagerMessage::RemoveFloatingWindow(window) => {
112                        self.remove_floating_window(*window)
113                    }
114                }
115            }
116        }
117    }
118
119    fn preview_message(&self, _ui: &UserInterface, message: &mut UiMessage) {
120        if let Some(WidgetMessage::LinkWith(_)) = message.data::<WidgetMessage>() {
121            let pos = self
122                .floating_windows
123                .borrow()
124                .iter()
125                .position(|&i| i == message.destination());
126            if let Some(pos) = pos {
127                self.floating_windows.borrow_mut().remove(pos);
128            }
129        }
130    }
131}
132
133impl DockingManager {
134    pub fn layout(&self, ui: &UserInterface) -> DockingManagerLayoutDescriptor {
135        DockingManagerLayoutDescriptor {
136            floating_windows: self
137                .floating_windows
138                .borrow()
139                .iter()
140                .filter_map(|h| {
141                    ui.try_get(*h).map(|w| FloatingWindowDescriptor {
142                        name: w.name.clone(),
143                        position: w.actual_local_position(),
144                        size: w.actual_local_size(),
145                        is_open: w.is_globally_visible(),
146                    })
147                })
148                .collect::<Vec<_>>(),
149            root_tile_descriptor: self
150                .children()
151                .first()
152                .map(|c| TileDescriptor::from_tile_handle(*c, ui)),
153        }
154    }
155
156    fn set_layout(
157        &mut self,
158        layout_descriptor: &DockingManagerLayoutDescriptor,
159        ui: &mut UserInterface,
160    ) {
161        if let Some(root_tile_handle) = self.children.first().cloned() {
162            let mut windows = Vec::new();
163            let mut stack = vec![root_tile_handle];
164            while let Some(tile_handle) = stack.pop() {
165                if let Some(tile) = ui
166                    .try_get(tile_handle)
167                    .and_then(|n| n.query_component::<Tile>())
168                {
169                    match tile.content {
170                        TileContent::Window(window) => {
171                            if ui.is_valid_handle(window) {
172                                // Detach the window from the tile, this is needed to prevent
173                                // deletion of the window when the tile is deleted.
174                                ui.unlink_node(window);
175
176                                windows.push(window);
177                            }
178                        }
179                        TileContent::VerticalTiles { tiles, .. }
180                        | TileContent::HorizontalTiles { tiles, .. } => {
181                            stack.extend_from_slice(&tiles);
182                        }
183                        _ => (),
184                    }
185                }
186            }
187
188            // Destroy the root tile with all descendant tiles.
189            ui.send_message(WidgetMessage::remove(
190                root_tile_handle,
191                MessageDirection::ToWidget,
192            ));
193
194            // Re-create the tiles according to the layout and attach it to the docking manager.
195            if let Some(root_tile_descriptor) = layout_descriptor.root_tile_descriptor.as_ref() {
196                let root_tile = root_tile_descriptor.create_tile(ui, &windows);
197                ui.send_message(WidgetMessage::link(
198                    root_tile,
199                    MessageDirection::ToWidget,
200                    self.handle,
201                ));
202            }
203
204            // Restore floating windows.
205            self.floating_windows.borrow_mut().clear();
206            for floating_window_desc in layout_descriptor.floating_windows.iter() {
207                if floating_window_desc.name.is_empty() {
208                    Log::warn(
209                        "Floating window name is empty, wrong widget will be used as a \
210                        floating window. Assign a unique name to the floating window used in a docking \
211                        manager!",
212                    );
213                }
214
215                let floating_window =
216                    ui.find_handle(ui.root(), &mut |n| n.name == floating_window_desc.name);
217                if floating_window.is_some() {
218                    self.floating_windows.borrow_mut().push(floating_window);
219
220                    if floating_window_desc.is_open {
221                        ui.send_message(WindowMessage::open(
222                            floating_window,
223                            MessageDirection::ToWidget,
224                            false,
225                            false,
226                        ));
227                    } else {
228                        ui.send_message(WindowMessage::close(
229                            floating_window,
230                            MessageDirection::ToWidget,
231                        ));
232                    }
233
234                    ui.send_message(WidgetMessage::desired_position(
235                        floating_window,
236                        MessageDirection::ToWidget,
237                        floating_window_desc.position,
238                    ));
239
240                    if floating_window_desc.size.x != 0.0 {
241                        ui.send_message(WidgetMessage::width(
242                            floating_window,
243                            MessageDirection::ToWidget,
244                            floating_window_desc.size.x,
245                        ));
246                    }
247
248                    if floating_window_desc.size.y != 0.0 {
249                        ui.send_message(WidgetMessage::height(
250                            floating_window,
251                            MessageDirection::ToWidget,
252                            floating_window_desc.size.y,
253                        ));
254                    }
255                }
256            }
257        }
258    }
259
260    fn add_floating_window(&mut self, window: Handle<UiNode>) {
261        let mut windows = self.floating_windows.borrow_mut();
262        if !windows.contains(&window) {
263            windows.push(window);
264        }
265    }
266
267    fn remove_floating_window(&mut self, window: Handle<UiNode>) {
268        let mut windows = self.floating_windows.borrow_mut();
269        if let Some(position) = windows.iter().position(|&w| w == window) {
270            windows.remove(position);
271        }
272    }
273}
274
275pub struct DockingManagerBuilder {
276    widget_builder: WidgetBuilder,
277    floating_windows: Vec<Handle<UiNode>>,
278}
279
280impl DockingManagerBuilder {
281    pub fn new(widget_builder: WidgetBuilder) -> Self {
282        Self {
283            widget_builder,
284            floating_windows: Default::default(),
285        }
286    }
287
288    pub fn with_floating_windows(mut self, windows: Vec<Handle<UiNode>>) -> Self {
289        self.floating_windows = windows;
290        self
291    }
292
293    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
294        let docking_manager = DockingManager {
295            widget: self.widget_builder.with_preview_messages(true).build(ctx),
296            floating_windows: RefCell::new(self.floating_windows),
297        };
298
299        ctx.add_node(UiNode::new(docking_manager))
300    }
301}
302
303#[cfg(test)]
304mod test {
305    use crate::dock::DockingManagerBuilder;
306    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
307
308    #[test]
309    fn test_deletion() {
310        test_widget_deletion(|ctx| DockingManagerBuilder::new(WidgetBuilder::new()).build(ctx));
311    }
312}