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
21use crate::{
22    border::BorderBuilder,
23    button::{ButtonBuilder, ButtonMessage},
24    core::{
25        pool::Handle, reflect::prelude::*, type_traits::prelude::*, variable::InheritableVariable,
26        visitor::prelude::*,
27    },
28    define_constructor, define_widget_deref,
29    grid::{Column, GridBuilder, Row},
30    message::{MessageDirection, UiMessage},
31    utils::{make_arrow, ArrowDirection},
32    widget::{Widget, WidgetBuilder, WidgetMessage},
33    BuildContext, Control, Thickness, UiNode, UserInterface,
34};
35use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
36use std::ops::{Deref, DerefMut};
37
38#[derive(Debug, PartialEq, Clone)]
39pub enum SelectorMessage {
40    AddItem(Handle<UiNode>),
41    RemoveItem(Handle<UiNode>),
42    SetItems {
43        items: Vec<Handle<UiNode>>,
44        remove_previous: bool,
45    },
46    Current(Option<usize>),
47}
48
49impl SelectorMessage {
50    define_constructor!(
51        /// Creates [`SelectorMessage::AddItem`] message.
52        SelectorMessage:AddItem => fn add_item(Handle<UiNode>), layout: false
53    );
54    define_constructor!(
55        /// Creates [`SelectorMessage::RemoveItem`] message.
56        SelectorMessage:RemoveItem => fn remove_item(Handle<UiNode>), layout: false
57    );
58    define_constructor!(
59        /// Creates [`SelectorMessage::SetItems`] message.
60        SelectorMessage:SetItems => fn set_items(items: Vec<Handle<UiNode>>, remove_previous: bool), layout: false
61    );
62    define_constructor!(
63        /// Creates [`SelectorMessage::Current`] message.
64        SelectorMessage:Current => fn current(Option<usize>), layout: false
65    );
66}
67
68#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider, TypeUuidProvider)]
69#[type_uuid(id = "25118853-5c3c-4197-9e4b-2e3b9d92f4d2")]
70pub struct Selector {
71    widget: Widget,
72    items: InheritableVariable<Vec<Handle<UiNode>>>,
73    items_panel: InheritableVariable<Handle<UiNode>>,
74    current: InheritableVariable<Option<usize>>,
75    prev: InheritableVariable<Handle<UiNode>>,
76    next: InheritableVariable<Handle<UiNode>>,
77}
78
79impl ConstructorProvider<UiNode, UserInterface> for Selector {
80    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
81        GraphNodeConstructor::new::<Self>()
82            .with_variant("Selector", |ui| {
83                SelectorBuilder::new(WidgetBuilder::new().with_name("Selector"))
84                    .build(&mut ui.build_ctx())
85                    .into()
86            })
87            .with_group("Input")
88    }
89}
90
91define_widget_deref!(Selector);
92
93impl Control for Selector {
94    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
95        self.widget.handle_routed_message(ui, message);
96
97        if let Some(msg) = message.data::<SelectorMessage>() {
98            match msg {
99                SelectorMessage::AddItem(item) => {
100                    ui.send_message(WidgetMessage::link(
101                        *item,
102                        MessageDirection::ToWidget,
103                        *self.items_panel,
104                    ));
105                    self.items.push(*item);
106                }
107                SelectorMessage::RemoveItem(item) => {
108                    if let Some(position) = self.items.iter().position(|i| i == item) {
109                        ui.send_message(WidgetMessage::remove(*item, MessageDirection::ToWidget));
110
111                        self.items.remove(position);
112                    }
113                }
114                SelectorMessage::SetItems {
115                    items,
116                    remove_previous,
117                } => {
118                    if *remove_previous {
119                        for &item in &*self.items {
120                            ui.send_message(WidgetMessage::remove(
121                                item,
122                                MessageDirection::ToWidget,
123                            ));
124                        }
125                    }
126
127                    for &item in items {
128                        ui.send_message(WidgetMessage::link(
129                            item,
130                            MessageDirection::ToWidget,
131                            *self.items_panel,
132                        ));
133                    }
134
135                    self.items.set_value_and_mark_modified(items.clone());
136
137                    for (i, item) in self.items.iter().enumerate() {
138                        ui.send_message(WidgetMessage::visibility(
139                            *item,
140                            MessageDirection::ToWidget,
141                            self.current.map_or(false, |current| current == i),
142                        ));
143                    }
144                }
145                SelectorMessage::Current(current) => {
146                    if &*self.current != current
147                        && message.direction() == MessageDirection::ToWidget
148                    {
149                        if let Some(current) = *self.current {
150                            if let Some(current_item) = self.items.get(current) {
151                                ui.send_message(WidgetMessage::visibility(
152                                    *current_item,
153                                    MessageDirection::ToWidget,
154                                    false,
155                                ));
156                            }
157                        }
158
159                        self.current.set_value_and_mark_modified(*current);
160
161                        if let Some(new_current) = *self.current {
162                            if let Some(new_current_item) = self.items.get(new_current) {
163                                ui.send_message(WidgetMessage::visibility(
164                                    *new_current_item,
165                                    MessageDirection::ToWidget,
166                                    true,
167                                ));
168                            }
169                        }
170
171                        ui.send_message(message.reverse());
172                    }
173                }
174            }
175        } else if let Some(ButtonMessage::Click) = message.data() {
176            if message.destination() == *self.prev {
177                if let Some(current) = *self.current {
178                    let new_current = current.saturating_sub(1);
179                    ui.send_message(SelectorMessage::current(
180                        self.handle,
181                        MessageDirection::ToWidget,
182                        Some(new_current),
183                    ));
184                }
185            } else if message.destination() == *self.next {
186                if let Some(current) = *self.current {
187                    let new_current = current
188                        .saturating_add(1)
189                        .min(self.items.len().saturating_sub(1));
190                    ui.send_message(SelectorMessage::current(
191                        self.handle,
192                        MessageDirection::ToWidget,
193                        Some(new_current),
194                    ));
195                }
196            }
197        }
198    }
199}
200
201pub struct SelectorBuilder {
202    widget_builder: WidgetBuilder,
203    items: Vec<Handle<UiNode>>,
204    current: Option<usize>,
205}
206
207impl SelectorBuilder {
208    pub fn new(widget_builder: WidgetBuilder) -> Self {
209        Self {
210            widget_builder,
211            items: Default::default(),
212            current: None,
213        }
214    }
215
216    pub fn build(self, ctx: &mut BuildContext) -> Handle<UiNode> {
217        for (i, item) in self.items.iter().enumerate() {
218            ctx[*item].set_visibility(self.current == Some(i));
219        }
220
221        let prev;
222        let next;
223        let items_panel;
224        let grid = GridBuilder::new(
225            WidgetBuilder::new()
226                .with_child({
227                    prev = ButtonBuilder::new(WidgetBuilder::new().on_column(0))
228                        .with_content(make_arrow(ctx, ArrowDirection::Left, 24.0))
229                        .build(ctx);
230                    prev
231                })
232                .with_child({
233                    items_panel = BorderBuilder::new(
234                        WidgetBuilder::new()
235                            .with_children(self.items.clone())
236                            .on_column(1),
237                    )
238                    .with_stroke_thickness(Thickness::uniform(0.0).into())
239                    .build(ctx);
240                    items_panel
241                })
242                .with_child({
243                    next = ButtonBuilder::new(WidgetBuilder::new().on_column(2))
244                        .with_content(make_arrow(ctx, ArrowDirection::Right, 24.0))
245                        .build(ctx);
246                    next
247                }),
248        )
249        .add_row(Row::auto())
250        .add_column(Column::auto())
251        .add_column(Column::stretch())
252        .add_column(Column::auto())
253        .build(ctx);
254
255        let selector = Selector {
256            widget: self.widget_builder.with_child(grid).build(ctx),
257            items: self.items.into(),
258            items_panel: items_panel.into(),
259            prev: prev.into(),
260            next: next.into(),
261            current: self.current.into(),
262        };
263
264        ctx.add_node(UiNode::new(selector))
265    }
266}
267
268#[cfg(test)]
269mod test {
270    use crate::selector::SelectorBuilder;
271    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
272
273    #[test]
274    fn test_deletion() {
275        test_widget_deletion(|ctx| SelectorBuilder::new(WidgetBuilder::new()).build(ctx));
276    }
277}