Skip to main content

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::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
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(*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}