Skip to main content

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