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
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_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};
35
36use crate::button::Button;
37use crate::message::MessageData;
38use fyrox_graph::constructor::{ConstructorProvider, GraphNodeConstructor};
39
40#[derive(Debug, PartialEq, Clone)]
41pub enum SelectorMessage {
42    AddItem(Handle<UiNode>),
43    RemoveItem(Handle<UiNode>),
44    SetItems {
45        items: Vec<Handle<UiNode>>,
46        remove_previous: bool,
47    },
48    Current(Option<usize>),
49}
50impl MessageData for SelectorMessage {}
51
52#[derive(Default, Clone, Visit, Reflect, Debug, ComponentProvider, TypeUuidProvider)]
53#[reflect(derived_type = "UiNode")]
54#[type_uuid(id = "25118853-5c3c-4197-9e4b-2e3b9d92f4d2")]
55pub struct Selector {
56    widget: Widget,
57    items: InheritableVariable<Vec<Handle<UiNode>>>,
58    items_panel: InheritableVariable<Handle<UiNode>>,
59    current: InheritableVariable<Option<usize>>,
60    prev: InheritableVariable<Handle<Button>>,
61    next: InheritableVariable<Handle<Button>>,
62}
63
64impl ConstructorProvider<UiNode, UserInterface> for Selector {
65    fn constructor() -> GraphNodeConstructor<UiNode, UserInterface> {
66        GraphNodeConstructor::new::<Self>()
67            .with_variant("Selector", |ui| {
68                SelectorBuilder::new(WidgetBuilder::new().with_name("Selector"))
69                    .build(&mut ui.build_ctx())
70                    .to_base()
71                    .into()
72            })
73            .with_group("Input")
74    }
75}
76
77define_widget_deref!(Selector);
78
79impl Control for Selector {
80    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
81        self.widget.handle_routed_message(ui, message);
82
83        if let Some(msg) = message.data::<SelectorMessage>() {
84            match msg {
85                SelectorMessage::AddItem(item) => {
86                    ui.send(*item, WidgetMessage::LinkWith(*self.items_panel));
87                    self.items.push(*item);
88                }
89                SelectorMessage::RemoveItem(item) => {
90                    if let Some(position) = self.items.iter().position(|i| i == item) {
91                        ui.send(*item, WidgetMessage::Remove);
92                        self.items.remove(position);
93                    }
94                }
95                SelectorMessage::SetItems {
96                    items,
97                    remove_previous,
98                } => {
99                    if *remove_previous {
100                        for &item in &*self.items {
101                            ui.send(item, WidgetMessage::Remove);
102                        }
103                    }
104
105                    for &item in items {
106                        ui.send(item, WidgetMessage::LinkWith(*self.items_panel));
107                    }
108
109                    self.items.set_value_and_mark_modified(items.clone());
110
111                    for (i, item) in self.items.iter().enumerate() {
112                        ui.send(*item, WidgetMessage::Visibility(*self.current == Some(i)));
113                    }
114                }
115                SelectorMessage::Current(current) => {
116                    if &*self.current != current
117                        && message.direction() == MessageDirection::ToWidget
118                    {
119                        if let Some(current) = *self.current {
120                            if let Some(current_item) = self.items.get(current) {
121                                ui.send(*current_item, WidgetMessage::Visibility(false));
122                            }
123                        }
124
125                        self.current.set_value_and_mark_modified(*current);
126
127                        if let Some(new_current) = *self.current {
128                            if let Some(new_current_item) = self.items.get(new_current) {
129                                ui.send(*new_current_item, WidgetMessage::Visibility(true));
130                            }
131                        }
132
133                        ui.send_message(message.reverse());
134                    }
135                }
136            }
137        } else if let Some(ButtonMessage::Click) = message.data() {
138            if message.destination() == *self.prev {
139                if let Some(current) = *self.current {
140                    let new_current = current.saturating_sub(1);
141                    ui.send(self.handle, SelectorMessage::Current(Some(new_current)));
142                }
143            } else if message.destination() == *self.next {
144                if let Some(current) = *self.current {
145                    let new_current = current
146                        .saturating_add(1)
147                        .min(self.items.len().saturating_sub(1));
148                    ui.send(self.handle, SelectorMessage::Current(Some(new_current)));
149                }
150            }
151        }
152    }
153}
154
155pub struct SelectorBuilder {
156    widget_builder: WidgetBuilder,
157    items: Vec<Handle<UiNode>>,
158    current: Option<usize>,
159}
160
161impl SelectorBuilder {
162    pub fn new(widget_builder: WidgetBuilder) -> Self {
163        Self {
164            widget_builder,
165            items: Default::default(),
166            current: None,
167        }
168    }
169
170    pub fn build(self, ctx: &mut BuildContext) -> Handle<Selector> {
171        for (i, item) in self.items.iter().enumerate() {
172            ctx[*item].set_visibility(self.current == Some(i));
173        }
174
175        let prev;
176        let next;
177        let items_panel;
178        let grid = GridBuilder::new(
179            WidgetBuilder::new()
180                .with_child({
181                    prev = ButtonBuilder::new(WidgetBuilder::new().on_column(0))
182                        .with_content(make_arrow(ctx, ArrowDirection::Left, 24.0))
183                        .build(ctx);
184                    prev
185                })
186                .with_child({
187                    items_panel = BorderBuilder::new(
188                        WidgetBuilder::new()
189                            .with_children(self.items.clone())
190                            .on_column(1),
191                    )
192                    .with_stroke_thickness(Thickness::uniform(0.0).into())
193                    .build(ctx);
194                    items_panel
195                })
196                .with_child({
197                    next = ButtonBuilder::new(WidgetBuilder::new().on_column(2))
198                        .with_content(make_arrow(ctx, ArrowDirection::Right, 24.0))
199                        .build(ctx);
200                    next
201                }),
202        )
203        .add_row(Row::auto())
204        .add_column(Column::auto())
205        .add_column(Column::stretch())
206        .add_column(Column::auto())
207        .build(ctx);
208
209        let selector = Selector {
210            widget: self.widget_builder.with_child(grid).build(ctx),
211            items: self.items.into(),
212            items_panel: items_panel.to_base().into(),
213            prev: prev.into(),
214            next: next.into(),
215            current: self.current.into(),
216        };
217
218        ctx.add(selector)
219    }
220}
221
222#[cfg(test)]
223mod test {
224    use crate::selector::SelectorBuilder;
225    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
226
227    #[test]
228    fn test_deletion() {
229        test_widget_deletion(|ctx| SelectorBuilder::new(WidgetBuilder::new()).build(ctx));
230    }
231}