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