fyrox_ui/dock/
config.rs

1// Copyright (c) 2019-present Dmitry Stepanov and Fyrox Engine contributors.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21use 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
38// Rust trait solver is dumb, it overflows when Visit is derived. To bypass this, we need to
39// implement this manually.
40impl 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}