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}