1use crate::{
22 core::{algebra::Vector2, log::Log, pool::Handle, visitor::prelude::*, ImmutableString},
23 dock::{Tile, TileBuilder, TileContent},
24 message::MessageDirection,
25 widget::WidgetBuilder,
26 window::WindowMessage,
27 Orientation, UiNode, UserInterface,
28};
29use fyrox_graph::{BaseSceneGraph, SceneGraph};
30use serde::{Deserialize, Serialize};
31
32#[derive(Debug, PartialEq, Clone, Default, Serialize, Deserialize)]
33pub struct SplitTilesDescriptor {
34 pub splitter: f32,
35 pub orientation: Orientation,
36 pub children: [Box<TileDescriptor>; 2],
37}
38
39impl Visit for SplitTilesDescriptor {
42 fn visit(&mut self, name: &str, visitor: &mut Visitor) -> VisitResult {
43 let mut region = visitor.enter_region(name)?;
44
45 self.splitter.visit("Splitter", &mut region)?;
46 self.orientation.visit("Orientation", &mut region)?;
47 self.children.visit("Children", &mut region)?;
48
49 Ok(())
50 }
51}
52
53#[derive(Debug, PartialEq, Clone, Visit, Default, Serialize, Deserialize)]
54pub enum TileContentDescriptor {
55 #[default]
56 Empty,
57 Window(ImmutableString),
58 SplitTiles(SplitTilesDescriptor),
59}
60
61impl TileContentDescriptor {
62 pub fn from_tile(tile_content: &TileContent, ui: &UserInterface) -> Self {
63 match tile_content {
64 TileContent::Empty => Self::Empty,
65 TileContent::Window(window) => Self::Window(
66 ui.try_get(*window)
67 .map(|w| w.name.clone())
68 .unwrap_or_default(),
69 ),
70 TileContent::VerticalTiles { splitter, tiles } => {
71 Self::SplitTiles(SplitTilesDescriptor {
72 splitter: *splitter,
73 orientation: Orientation::Vertical,
74 children: TileDescriptor::from_tile_handle_slice(tiles, ui),
75 })
76 }
77 TileContent::HorizontalTiles { splitter, tiles } => {
78 Self::SplitTiles(SplitTilesDescriptor {
79 splitter: *splitter,
80 orientation: Orientation::Horizontal,
81 children: TileDescriptor::from_tile_handle_slice(tiles, ui),
82 })
83 }
84 }
85 }
86}
87
88#[derive(Debug, PartialEq, Clone, Visit, Default, Serialize, Deserialize)]
89pub struct TileDescriptor {
90 pub content: TileContentDescriptor,
91}
92
93impl TileContentDescriptor {
94 pub fn has_window(&self, window: &str) -> bool {
95 match self {
96 TileContentDescriptor::Empty => false,
97 TileContentDescriptor::Window(window_name) => window_name.as_str() == window,
98 TileContentDescriptor::SplitTiles(tiles) => {
99 for tile in tiles.children.iter() {
100 if tile.content.has_window(window) {
101 return true;
102 }
103 }
104 false
105 }
106 }
107 }
108}
109
110impl TileDescriptor {
111 pub(super) fn from_tile_handle(handle: Handle<UiNode>, ui: &UserInterface) -> Self {
112 ui.try_get(handle)
113 .and_then(|t| t.query_component::<Tile>())
114 .map(|t| Self {
115 content: TileContentDescriptor::from_tile(&t.content, ui),
116 })
117 .unwrap_or_default()
118 }
119
120 fn from_tile_handle_slice(slice: &[Handle<UiNode>; 2], ui: &UserInterface) -> [Box<Self>; 2] {
121 [
122 Box::new(Self::from_tile_handle(slice[0], ui)),
123 Box::new(Self::from_tile_handle(slice[1], ui)),
124 ]
125 }
126
127 pub fn create_tile(
128 &self,
129 ui: &mut UserInterface,
130 windows: &[Handle<UiNode>],
131 ) -> Handle<UiNode> {
132 TileBuilder::new(WidgetBuilder::new())
133 .with_content(match &self.content {
134 TileContentDescriptor::Empty => TileContent::Empty,
135 TileContentDescriptor::Window(window_name) => {
136 if window_name.is_empty() {
137 Log::warn(
138 "Window name is empty, wrong widget will be used as a \
139 tile content. Assign a unique name to the window used in a docking \
140 manager!",
141 );
142 }
143
144 let mut window_handle =
145 ui.find_handle(ui.root(), &mut |n| n.name == *window_name);
146
147 if window_handle.is_none() {
148 for other_window_handle in windows.iter().cloned() {
149 if let Some(window_node) = ui.try_get(other_window_handle) {
150 if &window_node.name == window_name {
151 window_handle = other_window_handle;
152 }
153 }
154 }
155 }
156
157 if window_handle.is_some() {
158 ui.send_message(WindowMessage::open(
159 window_handle,
160 MessageDirection::ToWidget,
161 false,
162 true,
163 ));
164
165 TileContent::Window(window_handle)
166 } else {
167 TileContent::Empty
168 }
169 }
170 TileContentDescriptor::SplitTiles(split_tiles) => match split_tiles.orientation {
171 Orientation::Vertical => TileContent::VerticalTiles {
172 splitter: split_tiles.splitter,
173 tiles: [
174 split_tiles.children[0].create_tile(ui, windows),
175 split_tiles.children[1].create_tile(ui, windows),
176 ],
177 },
178 Orientation::Horizontal => TileContent::HorizontalTiles {
179 splitter: split_tiles.splitter,
180 tiles: [
181 split_tiles.children[0].create_tile(ui, windows),
182 split_tiles.children[1].create_tile(ui, windows),
183 ],
184 },
185 },
186 })
187 .build(&mut ui.build_ctx())
188 }
189}
190
191#[derive(Debug, PartialEq, Clone, Visit, Default, Serialize, Deserialize)]
192pub struct FloatingWindowDescriptor {
193 pub name: ImmutableString,
194 pub position: Vector2<f32>,
195 pub size: Vector2<f32>,
196 #[serde(default = "default_is_open")]
197 pub is_open: bool,
198}
199
200fn default_is_open() -> bool {
201 true
202}
203
204#[derive(Debug, PartialEq, Clone, Visit, Default, Serialize, Deserialize)]
205pub struct DockingManagerLayoutDescriptor {
206 pub floating_windows: Vec<FloatingWindowDescriptor>,
207 pub root_tile_descriptor: Option<TileDescriptor>,
208}
209
210impl DockingManagerLayoutDescriptor {
211 pub fn has_window<S: AsRef<str>>(&self, window: S) -> bool {
212 self.root_tile_descriptor
213 .as_ref()
214 .is_some_and(|desc| desc.content.has_window(window.as_ref()))
215 }
216}