use crate::{
core::{
log::Log, pool::Handle, reflect::prelude::*, type_traits::prelude::*, uuid_provider,
visitor::prelude::*,
},
dock::config::{DockingManagerLayoutDescriptor, FloatingWindowDescriptor, TileDescriptor},
message::UiMessage,
widget::{Widget, WidgetBuilder, WidgetMessage},
window::WindowMessage,
BuildContext, Control, UiNode, UserInterface,
};
use fyrox_core::pool::HandlesArrayExtension;
use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
use fyrox_graph::SceneGraph;
use std::cell::RefCell;
pub mod config;
mod tile;
use crate::message::MessageData;
use crate::window::{Window, WindowAlignment};
pub use tile::*;
#[derive(Debug, Clone, PartialEq)]
pub enum DockingManagerMessage {
Layout(DockingManagerLayoutDescriptor),
AddFloatingWindow(Handle<Window>),
RemoveFloatingWindow(Handle<Window>),
}
impl MessageData for DockingManagerMessage {}
#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)]
#[reflect(derived_type = "UiNode")]
pub struct DockingManager {
pub widget: Widget,
pub floating_windows: RefCell<Vec<Handle<Window>>>,
}
impl ConstructorProvider<UiNode, UserInterface> for DockingManager {
fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
GraphNodeConstructor::new::<Self>()
.with_variant("Docking Manager", |ui| {
DockingManagerBuilder::new(WidgetBuilder::new().with_name("Docking Manager"))
.build(&mut ui.build_ctx())
.to_base()
.into()
})
.with_group("Layout")
}
}
crate::define_widget_deref!(DockingManager);
uuid_provider!(DockingManager = "b04299f7-3f6b-45f1-89a6-0dce4ad929e1");
impl Control for DockingManager {
fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
self.widget.handle_routed_message(ui, message);
if let Some(msg) = message.data_for(self.handle) {
match msg {
DockingManagerMessage::Layout(layout_descriptor) => {
self.set_layout(layout_descriptor, ui);
}
DockingManagerMessage::AddFloatingWindow(window) => {
self.add_floating_window(*window)
}
DockingManagerMessage::RemoveFloatingWindow(window) => {
self.remove_floating_window(*window)
}
}
}
}
fn preview_message(&self, _ui: &UserInterface, message: &mut UiMessage) {
if let Some(WidgetMessage::LinkWith(_)) = message.data::<WidgetMessage>() {
let pos = self
.floating_windows
.borrow()
.iter()
.position(|&i| message.destination() == i);
if let Some(pos) = pos {
self.floating_windows.borrow_mut().remove(pos);
}
}
}
}
impl DockingManager {
pub fn layout(&self, ui: &UserInterface) -> DockingManagerLayoutDescriptor {
DockingManagerLayoutDescriptor {
floating_windows: self
.floating_windows
.borrow()
.iter()
.filter_map(|h| {
ui.try_get(*h).ok().map(|w| FloatingWindowDescriptor {
name: w.name.clone(),
position: w.actual_local_position(),
size: w.actual_local_size(),
is_open: w.is_globally_visible(),
})
})
.collect::<Vec<_>>(),
root_tile_descriptor: self
.children()
.first()
.map(|c| TileDescriptor::from_tile_handle(c.to_variant(), ui)),
}
}
fn set_layout(
&mut self,
layout_descriptor: &DockingManagerLayoutDescriptor,
ui: &mut UserInterface,
) {
if let Some(root_tile_handle) = self.children.first().cloned() {
let mut windows = Vec::new();
let mut stack = vec![root_tile_handle];
while let Some(tile_handle) = stack.pop() {
if let Ok(tile) = ui.try_get_of_type::<Tile>(tile_handle) {
match tile.content {
TileContent::Window(window) => {
if ui.is_valid_handle(window) {
ui.unlink_node(window);
windows.push(window);
}
}
TileContent::MultiWindow {
windows: ref tile_windows,
..
} => {
for w in tile_windows.clone() {
ui.unlink_node(w);
windows.push(w);
}
}
TileContent::VerticalTiles { tiles, .. }
| TileContent::HorizontalTiles { tiles, .. } => {
stack.extend_from_slice(&tiles.to_base());
}
TileContent::Empty => (),
}
}
}
ui.send(root_tile_handle, WidgetMessage::Remove);
if let Some(root_tile_descriptor) = layout_descriptor.root_tile_descriptor.as_ref() {
let root_tile = root_tile_descriptor.create_tile(ui, &windows);
ui.send(root_tile, WidgetMessage::LinkWith(self.handle));
}
self.floating_windows.borrow_mut().clear();
for floating_window_desc in layout_descriptor.floating_windows.iter() {
if floating_window_desc.name.is_empty() {
Log::warn(
"Floating window name is empty, wrong widget will be used as a \
floating window. Assign a unique name to the floating window used in a docking \
manager!",
);
}
let floating_window = ui
.find_handle(ui.root(), &mut |n| {
n.has_component::<Window>() && n.name == floating_window_desc.name
})
.to_variant();
if floating_window.is_some() {
self.floating_windows.borrow_mut().push(floating_window);
if floating_window_desc.is_open {
ui.send(
floating_window,
WindowMessage::Open {
alignment: WindowAlignment::None,
modal: false,
focus_content: false,
},
);
} else {
ui.send(floating_window, WindowMessage::Close);
}
ui.send(
floating_window,
WidgetMessage::DesiredPosition(floating_window_desc.position),
);
if floating_window_desc.size.x != 0.0 {
ui.send(
floating_window,
WidgetMessage::Width(floating_window_desc.size.x),
);
}
if floating_window_desc.size.y != 0.0 {
ui.send(
floating_window,
WidgetMessage::Height(floating_window_desc.size.y),
);
}
}
}
}
}
fn add_floating_window(&mut self, window: Handle<Window>) {
let mut windows = self.floating_windows.borrow_mut();
if !windows.contains(&window) {
windows.push(window);
}
}
fn remove_floating_window(&mut self, window: Handle<Window>) {
let mut windows = self.floating_windows.borrow_mut();
if let Some(position) = windows.iter().position(|&w| w == window) {
windows.remove(position);
}
}
}
pub struct DockingManagerBuilder {
widget_builder: WidgetBuilder,
floating_windows: Vec<Handle<Window>>,
}
impl DockingManagerBuilder {
pub fn new(widget_builder: WidgetBuilder) -> Self {
Self {
widget_builder,
floating_windows: Default::default(),
}
}
pub fn with_floating_windows(mut self, windows: Vec<Handle<Window>>) -> Self {
self.floating_windows = windows;
self
}
pub fn build(self, ctx: &mut BuildContext) -> Handle<DockingManager> {
let docking_manager = DockingManager {
widget: self.widget_builder.with_preview_messages(true).build(ctx),
floating_windows: RefCell::new(self.floating_windows),
};
ctx.add(docking_manager)
}
}
#[cfg(test)]
mod test {
use crate::dock::DockingManagerBuilder;
use crate::{test::test_widget_deletion, widget::WidgetBuilder};
#[test]
fn test_deletion() {
test_widget_deletion(|ctx| DockingManagerBuilder::new(WidgetBuilder::new()).build(ctx));
}
}