1use crate::{
22 core::{algebra::Vector2, log::Log, pool::Handle, visitor::prelude::*, ImmutableString},
23 dock::{Tile, TileBuilder, TileContent},
24 message::MessageDirection,
25 widget::{WidgetBuilder, WidgetMessage},
26 Orientation, UiNode, UserInterface,
27};
28use fyrox_graph::{BaseSceneGraph, 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_node(*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_node(*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<UiNode>],
140) -> Handle<UiNode> {
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.find_handle(ui.root(), &mut |n| n.name == *window_name);
150
151 if window_handle.is_none() {
152 for other_window_handle in windows.iter().cloned() {
153 if let Some(window_node) = ui.try_get_node(other_window_handle) {
154 if &window_node.name == window_name {
155 return other_window_handle;
156 }
157 }
158 }
159 }
160 window_handle
161}
162
163impl TileDescriptor {
164 pub(super) fn from_tile_handle(handle: Handle<UiNode>, ui: &UserInterface) -> Self {
165 ui.try_get_node(handle)
166 .and_then(|t| t.query_component::<Tile>())
167 .map(|t| Self {
168 content: TileContentDescriptor::from_tile(&t.content, ui),
169 })
170 .unwrap_or_default()
171 }
172
173 fn from_tile_handle_slice(slice: &[Handle<UiNode>; 2], ui: &UserInterface) -> [Box<Self>; 2] {
174 [
175 Box::new(Self::from_tile_handle(slice[0], ui)),
176 Box::new(Self::from_tile_handle(slice[1], ui)),
177 ]
178 }
179
180 pub fn create_tile(
181 &self,
182 ui: &mut UserInterface,
183 windows: &[Handle<UiNode>],
184 ) -> Handle<UiNode> {
185 TileBuilder::new(WidgetBuilder::new())
186 .with_content(match &self.content {
187 TileContentDescriptor::Empty => TileContent::Empty,
188 TileContentDescriptor::Window(window_name) => {
189 let window_handle = find_window(window_name, ui, windows);
190 if window_handle.is_some() {
191 ui.send_message(WidgetMessage::visibility(
192 window_handle,
193 MessageDirection::ToWidget,
194 true,
195 ));
196
197 TileContent::Window(window_handle)
198 } else {
199 TileContent::Empty
200 }
201 }
202 TileContentDescriptor::MultiWindow(MultiWindowDescriptor { index, names }) => {
203 let handles = names
204 .iter()
205 .map(|n| find_window(n, ui, windows))
206 .filter(|h| h.is_some())
207 .collect::<Vec<_>>();
208 match handles.len() {
209 0 => TileContent::Empty,
210 1 => TileContent::Window(handles[0]),
211 _ => TileContent::MultiWindow {
212 index: *index,
213 windows: handles,
214 },
215 }
216 }
217 TileContentDescriptor::SplitTiles(split_tiles) => match split_tiles.orientation {
218 Orientation::Vertical => TileContent::VerticalTiles {
219 splitter: split_tiles.splitter,
220 tiles: [
221 split_tiles.children[0].create_tile(ui, windows),
222 split_tiles.children[1].create_tile(ui, windows),
223 ],
224 },
225 Orientation::Horizontal => TileContent::HorizontalTiles {
226 splitter: split_tiles.splitter,
227 tiles: [
228 split_tiles.children[0].create_tile(ui, windows),
229 split_tiles.children[1].create_tile(ui, windows),
230 ],
231 },
232 },
233 })
234 .build(&mut ui.build_ctx())
235 }
236}
237
238#[derive(Debug, PartialEq, Clone, Visit, Default, Serialize, Deserialize)]
239pub struct FloatingWindowDescriptor {
240 pub name: ImmutableString,
241 pub position: Vector2<f32>,
242 pub size: Vector2<f32>,
243 #[serde(default = "default_is_open")]
244 pub is_open: bool,
245}
246
247fn default_is_open() -> bool {
248 true
249}
250
251#[derive(Debug, PartialEq, Clone, Visit, Default, Serialize, Deserialize)]
252pub struct DockingManagerLayoutDescriptor {
253 pub floating_windows: Vec<FloatingWindowDescriptor>,
254 pub root_tile_descriptor: Option<TileDescriptor>,
255}
256
257impl DockingManagerLayoutDescriptor {
258 pub fn has_window<S: AsRef<str>>(&self, window: S) -> bool {
259 self.root_tile_descriptor
260 .as_ref()
261 .is_some_and(|desc| desc.content.has_window(window.as_ref()))
262 }
263}