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,
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
39// Rust trait solver is dumb, it overflows when Visit is derived. To bypass this, we need to
40// implement this manually.
41impl 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}