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