fyrox_ui/stack_panel.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//! Stack panel orders its children widgets linearly; either top-to-bottom or left-to-right. See [`StackPanel`] docs for
22//! 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 visitor::prelude::*,
30 },
31 define_constructor,
32 message::{MessageDirection, UiMessage},
33 widget::{Widget, WidgetBuilder},
34 BuildContext, Control, Orientation, UiNode, UserInterface,
35};
36use fyrox_core::uuid_provider;
37use fyrox_core::variable::InheritableVariable;
38use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
39use fyrox_graph::BaseSceneGraph;
40use std::ops::{Deref, DerefMut};
41
42/// A set of possible [`StackPanel`] widget messages.
43#[derive(Debug, Clone, PartialEq, Eq)]
44pub enum StackPanelMessage {
45 /// The message is used to change orientation of the stack panel.
46 Orientation(Orientation),
47}
48
49impl StackPanelMessage {
50 define_constructor!(
51 /// Creates [`StackPanelMessage::Orientation`] message.
52 StackPanelMessage:Orientation => fn orientation(Orientation), layout: false
53 );
54}
55
56/// Stack Panels are one of several methods to position multiple widgets in relation to each other. A Stack Panel Widget
57/// orders its children widgets linearly, aka in a stack of widgets, based on the order the widgets were added as children.
58/// So the first widget added will be at the top or left most position, while each additional widget will descend from top to
59/// bottom or continue from left most to right most. The below example code places 3 text widgets into a vertical stack:
60///
61/// ```rust,no_run
62/// # use fyrox_ui::{
63/// # UiNode, core::pool::Handle,
64/// # BuildContext,
65/// # widget::WidgetBuilder,
66/// # text::TextBuilder,
67/// # stack_panel::StackPanelBuilder,
68/// # };
69/// fn create_stack_panel(ctx: &mut BuildContext) -> Handle<UiNode> {
70/// StackPanelBuilder::new(
71/// WidgetBuilder::new()
72/// .with_child(
73/// TextBuilder::new(WidgetBuilder::new())
74/// .with_text("Top")
75/// .build(ctx)
76/// )
77/// .with_child(
78/// TextBuilder::new(WidgetBuilder::new())
79/// .with_text("Middle")
80/// .build(ctx)
81/// )
82/// .with_child(
83/// TextBuilder::new(WidgetBuilder::new())
84/// .with_text("Bottom")
85/// .build(ctx)
86/// )
87/// )
88/// .build(ctx)
89///
90/// }
91/// ```
92///
93/// As you can see from the example, creating a Stack Panel uses the standard method for creating widgets. Create a new
94/// [`StackPanelBuilder`] and provide it with a new [`WidgetBuilder`]. Adding widgets to the stack is done by adding children to
95/// the StackBuilder's [`WidgetBuilder`].
96///
97/// ## Stack Panel Orientation
98///
99/// As has been indicated, stack panels can be oriented to order its children either Vertical (from top to bottom), or
100/// Horizontal (left to most). This is done using the [`StackPanelBuilder::with_orientation`] function providing it
101/// with a [`Orientation`] enum value. **By default** all stack panels has [`Orientation::Vertical`].
102///
103/// ```rust,no_run
104/// # use fyrox_ui::{
105/// # Orientation,
106/// # BuildContext,
107/// # widget::WidgetBuilder,
108/// # stack_panel::StackPanelBuilder,
109/// # };
110///
111/// # fn build(ctx: &mut BuildContext) {
112/// StackPanelBuilder::new(
113/// WidgetBuilder::new()
114/// )
115/// .with_orientation(Orientation::Horizontal)
116/// .build(ctx);
117/// # }
118/// ```
119#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider)]
120pub struct StackPanel {
121 /// Base widget of the stack panel.
122 pub widget: Widget,
123 /// Current orientation of the stack panel.
124 pub orientation: InheritableVariable<Orientation>,
125}
126
127impl ConstructorProvider<UiNode, UserInterface> for StackPanel {
128 fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
129 GraphNodeConstructor::new::<Self>()
130 .with_variant("Stack Panel", |ui| {
131 StackPanelBuilder::new(WidgetBuilder::new().with_name("Stack Panel"))
132 .build(&mut ui.build_ctx())
133 .into()
134 })
135 .with_group("Layout")
136 }
137}
138
139crate::define_widget_deref!(StackPanel);
140
141uuid_provider!(StackPanel = "d868f554-a2c5-4280-abfc-396d10a0e1ed");
142
143impl Control for StackPanel {
144 fn measure_override(&self, ui: &UserInterface, available_size: Vector2<f32>) -> Vector2<f32> {
145 let mut child_constraint = Vector2::new(f32::INFINITY, f32::INFINITY);
146
147 match *self.orientation {
148 Orientation::Vertical => {
149 child_constraint.x = available_size.x;
150
151 if !self.widget.width().is_nan() {
152 child_constraint.x = self.widget.width();
153 }
154
155 child_constraint.x = child_constraint.x.clamp(self.min_width(), self.max_width());
156 }
157 Orientation::Horizontal => {
158 child_constraint.y = available_size.y;
159
160 if !self.widget.height().is_nan() {
161 child_constraint.y = self.widget.height();
162 }
163
164 child_constraint.y = child_constraint
165 .y
166 .clamp(self.min_height(), self.max_height());
167 }
168 }
169
170 let mut measured_size = Vector2::default();
171
172 for child_handle in self.widget.children() {
173 ui.measure_node(*child_handle, child_constraint);
174
175 let child = ui.node(*child_handle);
176 let desired = child.desired_size();
177 match *self.orientation {
178 Orientation::Vertical => {
179 if desired.x > measured_size.x {
180 measured_size.x = desired.x;
181 }
182 measured_size.y += desired.y;
183 }
184 Orientation::Horizontal => {
185 measured_size.x += desired.x;
186 if desired.y > measured_size.y {
187 measured_size.y = desired.y;
188 }
189 }
190 }
191 }
192
193 measured_size
194 }
195
196 fn arrange_override(&self, ui: &UserInterface, final_size: Vector2<f32>) -> Vector2<f32> {
197 let mut width = final_size.x;
198 let mut height = final_size.y;
199
200 match *self.orientation {
201 Orientation::Vertical => height = 0.0,
202 Orientation::Horizontal => width = 0.0,
203 }
204
205 for child_handle in self.widget.children() {
206 let child = ui.node(*child_handle);
207 match *self.orientation {
208 Orientation::Vertical => {
209 let child_bounds = Rect::new(
210 0.0,
211 height,
212 width.max(child.desired_size().x),
213 child.desired_size().y,
214 );
215 ui.arrange_node(*child_handle, &child_bounds);
216 width = width.max(child.desired_size().x);
217 height += child.desired_size().y;
218 }
219 Orientation::Horizontal => {
220 let child_bounds = Rect::new(
221 width,
222 0.0,
223 child.desired_size().x,
224 height.max(child.desired_size().y),
225 );
226 ui.arrange_node(*child_handle, &child_bounds);
227 width += child.desired_size().x;
228 height = height.max(child.desired_size().y);
229 }
230 }
231 }
232
233 match *self.orientation {
234 Orientation::Vertical => {
235 height = height.max(final_size.y);
236 }
237 Orientation::Horizontal => {
238 width = width.max(final_size.x);
239 }
240 }
241
242 Vector2::new(width, height)
243 }
244
245 fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
246 self.widget.handle_routed_message(ui, message);
247
248 if message.destination() == self.handle && message.direction() == MessageDirection::ToWidget
249 {
250 if let Some(StackPanelMessage::Orientation(orientation)) = message.data() {
251 if *orientation != *self.orientation {
252 self.orientation.set_value_and_mark_modified(*orientation);
253 self.invalidate_layout();
254 }
255 }
256 }
257 }
258}
259
260/// Stack panel builders creates [`StackPanel`] widgets and registers them in the user interface.
261pub struct StackPanelBuilder {
262 widget_builder: WidgetBuilder,
263 orientation: Option<Orientation>,
264}
265
266impl StackPanelBuilder {
267 /// Creates new stack panel builder with the base widget builder.
268 pub fn new(widget_builder: WidgetBuilder) -> Self {
269 Self {
270 widget_builder,
271 orientation: None,
272 }
273 }
274
275 /// Sets the desired orientation of the stack panel.
276 pub fn with_orientation(mut self, orientation: Orientation) -> Self {
277 self.orientation = Some(orientation);
278 self
279 }
280
281 /// Finishes stack panel building.
282 pub fn build_stack_panel(self, ctx: &BuildContext) -> StackPanel {
283 StackPanel {
284 widget: self.widget_builder.build(ctx),
285 orientation: self.orientation.unwrap_or(Orientation::Vertical).into(),
286 }
287 }
288
289 /// Finishes stack panel building and wraps the result in a UI node.
290 pub fn build_node(self, ctx: &BuildContext) -> UiNode {
291 UiNode::new(self.build_stack_panel(ctx))
292 }
293
294 /// Finishes stack panel building and adds the new stack panel widget instance to the user interface and
295 /// returns its handle.
296 pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
297 ctx.add_node(self.build_node(ctx))
298 }
299}
300
301#[cfg(test)]
302mod test {
303 use crate::stack_panel::StackPanelBuilder;
304 use crate::{test::test_widget_deletion, widget::WidgetBuilder};
305
306 #[test]
307 fn test_deletion() {
308 test_widget_deletion(|ctx| StackPanelBuilder::new(WidgetBuilder::new()).build(ctx));
309 }
310}