Skip to main content

fyrox_ui/
screen.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
21//! Screen is a widget that always has the size of the screen of the UI in which it is used. See
22//! docs for [`Screen`] for more info and usage examples.
23
24#![warn(missing_docs)]
25
26use crate::{
27    core::{
28        algebra::Vector2, math::Rect, pool::Handle, reflect::prelude::*, type_traits::prelude::*,
29        uuid_provider, visitor::prelude::*,
30    },
31    message::UiMessage,
32    widget::{Widget, WidgetBuilder},
33    BuildContext, Control, UiNode, UserInterface,
34};
35
36use fyrox_core::algebra::Matrix3;
37use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
38use std::cell::Cell;
39
40/// Screen is a widget that always has the size of the screen of the UI in which it is used. It is
41/// main use case is to provide automatic layout functionality, that will always provide screen size
42/// to its children widgets. This is needed because the root node of any UI is [`crate::canvas::Canvas`]
43/// which provides infinite bounds as a layout constraint, thus making it impossible for automatic
44/// fitting to the current screen size. For example, Screen widget could be used as a root node for
45/// [`crate::grid::Grid`] widget - in this case, the grid instance will always have the size of the
46/// screen and will automatically shrink or expand when the screen size changes. It is an ideal choice if
47/// you want to have some widgets always centered on screen (for example - crosshair, main menu of
48/// your game, etc.).
49///
50/// ## Example
51///
52/// The following examples create a simple main menu of a game with just two buttons. The buttons
53/// will always be centered in the current screen bounds.
54///
55/// ```rust
56/// # use fyrox_ui::{
57/// #     core::pool::Handle,
58/// #     button::ButtonBuilder,
59/// #     grid::{Column, GridBuilder, Row},
60/// #     screen::ScreenBuilder,
61/// #     stack_panel::StackPanelBuilder,
62/// #     widget::WidgetBuilder,
63/// #     BuildContext, UiNode,
64/// # };
65/// # use fyrox_ui::screen::Screen;
66/// #
67/// fn create_always_centered_game_menu(ctx: &mut BuildContext) -> Handle<Screen> {
68///     // Screen widget will provide current screen size to its Grid widget as a layout constraint,
69///     // thus making it fit to the current screen bounds.
70///     ScreenBuilder::new(
71///         WidgetBuilder::new().with_child(
72///             GridBuilder::new(
73///                 WidgetBuilder::new()
74///                     .with_width(300.0)
75///                     .with_height(400.0)
76///                     .with_child(
77///                         // Buttons will be stacked one on top of another.
78///                         StackPanelBuilder::new(
79///                             WidgetBuilder::new()
80///                                 .on_row(1)
81///                                 .on_column(1)
82///                                 .with_child(
83///                                     ButtonBuilder::new(WidgetBuilder::new())
84///                                         .with_text("New Game")
85///                                         .build(ctx),
86///                                 )
87///                                 .with_child(
88///                                     ButtonBuilder::new(WidgetBuilder::new())
89///                                         .with_text("Exit")
90///                                         .build(ctx),
91///                                 ),
92///                         )
93///                         .build(ctx),
94///                     ),
95///             )
96///             // Split the grid into 3 rows and 3 columns. The center cell contains the stack panel
97///             // instance that basically stacks main menu buttons one on top of another. The center
98///             // cell will also be always centered in screen bounds.
99///             .add_row(Row::stretch())
100///             .add_row(Row::auto())
101///             .add_row(Row::stretch())
102///             .add_column(Column::stretch())
103///             .add_column(Column::auto())
104///             .add_column(Column::stretch())
105///             .build(ctx),
106///         ),
107///     )
108///     .build(ctx)
109/// }
110/// ```
111#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)]
112#[reflect(derived_type = "UiNode")]
113pub struct Screen {
114    /// Base widget of the screen.
115    pub widget: Widget,
116
117    /// Last screen size.
118    #[visit(skip)]
119    #[reflect(hidden)]
120    pub last_screen_size: Cell<Vector2<f32>>,
121
122    /// Last UI scale factor.
123    #[visit(skip)]
124    #[reflect(hidden)]
125    pub last_visual_transform: Matrix3<f32>,
126}
127
128impl ConstructorProvider<UiNode, UserInterface> for Screen {
129    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
130        GraphNodeConstructor::new::<Self>()
131            .with_variant("Screen", |ui| {
132                ScreenBuilder::new(WidgetBuilder::new().with_name("Screen"))
133                    .build(&mut ui.build_ctx())
134                    .to_base()
135                    .into()
136            })
137            .with_group("Layout")
138    }
139}
140
141crate::define_widget_deref!(Screen);
142
143uuid_provider!(Screen = "3bc7649f-a1ba-49be-bc4e-e0624654e40c");
144
145impl Control for Screen {
146    fn measure_override(&self, ui: &UserInterface, _available_size: Vector2<f32>) -> Vector2<f32> {
147        let logical_size = self
148            .visual_transform()
149            .try_inverse()
150            .unwrap_or_default()
151            .transform_vector(&ui.screen_size());
152
153        for &child in self.children.iter() {
154            ui.measure_node(child, logical_size);
155        }
156
157        logical_size
158    }
159
160    fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
161        let final_rect = Rect::new(0.0, 0.0, final_size.x, final_size.y);
162
163        for &child in self.children.iter() {
164            ui.arrange_node(child, &final_rect);
165        }
166
167        final_size
168    }
169
170    fn update(&mut self, _dt: f32, ui: &mut UserInterface) {
171        if self.last_screen_size.get() != ui.screen_size
172            || self.last_visual_transform != self.visual_transform
173        {
174            self.invalidate_layout();
175            self.last_screen_size.set(ui.screen_size);
176            self.last_visual_transform = self.visual_transform;
177        }
178    }
179
180    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
181        self.widget.handle_routed_message(ui, message);
182    }
183}
184
185/// Screen builder creates instances of [`Screen`] widgets and adds them to the user interface.
186pub struct ScreenBuilder {
187    widget_builder: WidgetBuilder,
188}
189
190impl ScreenBuilder {
191    /// Creates a new instance of the screen builder.
192    pub fn new(widget_builder: WidgetBuilder) -> Self {
193        Self { widget_builder }
194    }
195
196    /// Finishes building a [`Screen`] widget instance and adds it to the user interface, returning a
197    /// handle to the instance.
198    pub fn build(self, ctx: &mut BuildContext) -> Handle<Screen> {
199        let screen = Screen {
200            last_visual_transform: Matrix3::default(),
201            widget: self.widget_builder.with_need_update(true).build(ctx),
202            last_screen_size: Cell::new(Default::default()),
203        };
204        ctx.add(screen)
205    }
206}
207
208#[cfg(test)]
209mod test {
210    use crate::screen::ScreenBuilder;
211    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
212
213    #[test]
214    fn test_deletion() {
215        test_widget_deletion(|ctx| ScreenBuilder::new(WidgetBuilder::new()).build(ctx));
216    }
217}