1use 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#[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 DockingManagerMessage:Layout => fn layout(DockingManagerLayoutDescriptor), layout: false
64 );
65 define_constructor!(
66 DockingManagerMessage:AddFloatingWindow => fn add_floating_window(Handle<UiNode>), layout: false
68 );
69 define_constructor!(
70 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 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 ui.send_message(WidgetMessage::remove(
190 root_tile_handle,
191 MessageDirection::ToWidget,
192 ));
193
194 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 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}