Skip to main content

fyrox_ui/
selector.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//! Selector is a simple container widget that allows selecting an item from a fixed set of items.
22//! See [`Selector`] docs for more info.
23
24#![warn(missing_docs)]
25
26use crate::{
27    border::BorderBuilder,
28    button::{Button, ButtonBuilder, ButtonMessage},
29    core::{
30        pool::Handle, reflect::prelude::*, type_traits::prelude::*, variable::InheritableVariable,
31        visitor::prelude::*,
32    },
33    define_widget_deref,
34    grid::{Column, GridBuilder, Row},
35    message::{MessageData, MessageDirection, UiMessage},
36    utils::{make_arrow, ArrowDirection},
37    widget::{Widget, WidgetBuilder, WidgetMessage},
38    BuildContext, Control, Thickness, UiNode, UserInterface,
39};
40use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
41
42/// A set of messages that is used by [`Selector`] widget.
43#[derive(Debug, PartialEq, Clone)]
44pub enum SelectorMessage {
45    /// Adds a new item to a selector.
46    AddItem(Handle<UiNode>),
47    /// Removes an item from a selector.
48    RemoveItem(Handle<UiNode>),
49    /// Sets a new set of items of a selector.
50    SetItems {
51        /// A new set of items.
52        items: Vec<Handle<UiNode>>,
53        /// If `true` then all the previous will be deleted before setting the new items.
54        remove_previous: bool,
55    },
56    /// Sets a new current item, or gets the changes from the widget.
57    Current(Option<usize>),
58}
59impl MessageData for SelectorMessage {}
60
61/// Selector is a simple container widget that allows selecting an item from a fixed set of items.
62/// Selector widget shows the currently selected item at the center and two buttons on the sides
63/// that allows selecting either the previous or the next item.
64///
65/// ## Example
66///
67/// The following examples creates a new selector with three items and selects the middle one as
68/// active. The items can be of any type, even mixed types are allowed.
69///
70/// ```rust
71/// # use fyrox_ui::{
72/// #     core::pool::{Handle, HandlesVecExtension},
73/// #     selector::{Selector, SelectorBuilder},
74/// #     text::TextBuilder,
75/// #     widget::WidgetBuilder,
76/// #     BuildContext,
77/// # };
78/// #
79/// fn create_selector(ctx: &mut BuildContext) -> Handle<Selector> {
80///     SelectorBuilder::new(WidgetBuilder::new())
81///         .with_items(
82///             vec![
83///                 TextBuilder::new(WidgetBuilder::new())
84///                     .with_text("Item1")
85///                     .build(ctx),
86///                 TextBuilder::new(WidgetBuilder::new())
87///                     .with_text("Item2")
88///                     .build(ctx),
89///                 TextBuilder::new(WidgetBuilder::new())
90///                     .with_text("Item3")
91///                     .build(ctx),
92///             ]
93///             .to_base(),
94///         )
95///         .with_current_item(1)
96///         .build(ctx)
97/// }
98/// ```
99///
100/// ## Selection
101///
102/// The newly selected item index can be received from a selector by listening to [`SelectorMessage::Current`]
103/// message. To select a new item from code, send the same message with the desired index:
104///
105/// ```rust
106/// # use fyrox_ui::{
107/// #     core::pool::Handle,
108/// #     message::UiMessage,
109/// #     selector::{Selector, SelectorMessage},
110/// #     UserInterface,
111/// # };
112/// #
113/// fn on_ui_message(selector: Handle<Selector>, message: &UiMessage, ui: &UserInterface) {
114///     if let Some(SelectorMessage::Current(Some(index))) = message.data_from(selector) {
115///         println!("The new selection is {index}!");
116///
117///         if *index != 0 {
118///             // The selection can be changed by sending the same message to the widget:
119///             ui.send(selector, SelectorMessage::Current(Some(0)));
120///         }
121///     }
122/// }
123/// ```
124#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider, TypeUuidProvider)]
125#[reflect(derived_type = "UiNode")]
126#[type_uuid(id = "25118853-5c3c-4197-9e4b-2e3b9d92f4d2")]
127pub struct Selector {
128    widget: Widget,
129    items: InheritableVariable<Vec<Handle<UiNode>>>,
130    items_panel: InheritableVariable<Handle<UiNode>>,
131    current: InheritableVariable<Option<usize>>,
132    prev: InheritableVariable<Handle<Button>>,
133    next: InheritableVariable<Handle<Button>>,
134}
135
136impl ConstructorProvider<UiNode, UserInterface> for Selector {
137    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
138        GraphNodeConstructor::new::<Self>()
139            .with_variant("Selector", |ui| {
140                SelectorBuilder::new(WidgetBuilder::new().with_name("Selector"))
141                    .build(&mut ui.build_ctx())
142                    .to_base()
143                    .into()
144            })
145            .with_group("Input")
146    }
147}
148
149define_widget_deref!(Selector);
150
151impl Control for Selector {
152    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
153        self.widget.handle_routed_message(ui, message);
154
155        if let Some(msg) = message.data::<SelectorMessage>() {
156            match msg {
157                SelectorMessage::AddItem(item) => {
158                    ui.send(*item, WidgetMessage::LinkWith(*self.items_panel));
159                    self.items.push(*item);
160                }
161                SelectorMessage::RemoveItem(item) => {
162                    if let Some(position) = self.items.iter().position(|i| i == item) {
163                        ui.send(*item, WidgetMessage::Remove);
164                        self.items.remove(position);
165                    }
166                }
167                SelectorMessage::SetItems {
168                    items,
169                    remove_previous,
170                } => {
171                    if *remove_previous {
172                        for &item in &*self.items {
173                            ui.send(item, WidgetMessage::Remove);
174                        }
175                    }
176
177                    for &item in items {
178                        ui.send(item, WidgetMessage::LinkWith(*self.items_panel));
179                    }
180
181                    self.items.set_value_and_mark_modified(items.clone());
182
183                    for (i, item) in self.items.iter().enumerate() {
184                        ui.send(*item, WidgetMessage::Visibility(*self.current == Some(i)));
185                    }
186                }
187                SelectorMessage::Current(current) => {
188                    if &*self.current != current
189                        && message.direction() == MessageDirection::ToWidget
190                    {
191                        if let Some(current) = *self.current {
192                            if let Some(current_item) = self.items.get(current) {
193                                ui.send(*current_item, WidgetMessage::Visibility(false));
194                            }
195                        }
196
197                        self.current.set_value_and_mark_modified(*current);
198
199                        if let Some(new_current) = *self.current {
200                            if let Some(new_current_item) = self.items.get(new_current) {
201                                ui.send(*new_current_item, WidgetMessage::Visibility(true));
202                            }
203                        }
204
205                        ui.try_send_response(message);
206                    }
207                }
208            }
209        } else if let Some(ButtonMessage::Click) = message.data() {
210            if message.destination() == *self.prev {
211                if let Some(current) = *self.current {
212                    let new_current = current.saturating_sub(1);
213                    ui.send(self.handle, SelectorMessage::Current(Some(new_current)));
214                }
215            } else if message.destination() == *self.next {
216                if let Some(current) = *self.current {
217                    let new_current = current
218                        .saturating_add(1)
219                        .min(self.items.len().saturating_sub(1));
220                    ui.send(self.handle, SelectorMessage::Current(Some(new_current)));
221                }
222            }
223        }
224    }
225}
226
227/// Creates instances of [`Selector`] widgets.
228pub struct SelectorBuilder {
229    widget_builder: WidgetBuilder,
230    items: Vec<Handle<UiNode>>,
231    current: Option<usize>,
232}
233
234impl SelectorBuilder {
235    /// Creates a new builder instance.
236    pub fn new(widget_builder: WidgetBuilder) -> Self {
237        Self {
238            widget_builder,
239            items: Default::default(),
240            current: None,
241        }
242    }
243
244    /// Sets the desired set of items for the selector.
245    pub fn with_items(mut self, items: Vec<Handle<UiNode>>) -> Self {
246        self.items = items;
247        self
248    }
249
250    /// Sets the desired selected item.
251    pub fn with_current_item(mut self, current: usize) -> Self {
252        self.current = Some(current);
253        self
254    }
255
256    /// Builds the selector.
257    pub fn build(self, ctx: &mut BuildContext) -> Handle<Selector> {
258        for (i, item) in self.items.iter().enumerate() {
259            ctx[*item].set_visibility(self.current == Some(i));
260        }
261
262        let prev;
263        let next;
264        let items_panel;
265        let grid = GridBuilder::new(
266            WidgetBuilder::new()
267                .with_child({
268                    prev = ButtonBuilder::new(WidgetBuilder::new().on_column(0))
269                        .with_content(make_arrow(ctx, ArrowDirection::Left, 24.0))
270                        .build(ctx);
271                    prev
272                })
273                .with_child({
274                    items_panel = BorderBuilder::new(
275                        WidgetBuilder::new()
276                            .with_children(self.items.clone())
277                            .on_column(1),
278                    )
279                    .with_stroke_thickness(Thickness::uniform(0.0).into())
280                    .build(ctx);
281                    items_panel
282                })
283                .with_child({
284                    next = ButtonBuilder::new(WidgetBuilder::new().on_column(2))
285                        .with_content(make_arrow(ctx, ArrowDirection::Right, 24.0))
286                        .build(ctx);
287                    next
288                }),
289        )
290        .add_row(Row::auto())
291        .add_column(Column::auto())
292        .add_column(Column::stretch())
293        .add_column(Column::auto())
294        .build(ctx);
295
296        let selector = Selector {
297            widget: self.widget_builder.with_child(grid).build(ctx),
298            items: self.items.into(),
299            items_panel: items_panel.to_base().into(),
300            prev: prev.into(),
301            next: next.into(),
302            current: self.current.into(),
303        };
304
305        ctx.add(selector)
306    }
307}
308
309#[cfg(test)]
310mod test {
311    use crate::selector::SelectorBuilder;
312    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
313
314    #[test]
315    fn test_deletion() {
316        test_widget_deletion(|ctx| SelectorBuilder::new(WidgetBuilder::new()).build(ctx));
317    }
318}