Skip to main content

fyrox_ui/
dropdown_menu.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//! A simple widget that opens a popup when clicked. It could be used to create dropdown menus that
22//! consolidates content of a group.
23
24use crate::popup::Popup;
25use crate::{
26    core::{pool::Handle, reflect::prelude::*, type_traits::prelude::*, visitor::prelude::*},
27    message::{MouseButton, UiMessage},
28    popup::{Placement, PopupBuilder, PopupMessage},
29    widget::{Widget, WidgetBuilder, WidgetMessage},
30    BuildContext, Control, UiNode, UserInterface,
31};
32use fyrox_core::pool::ObjectOrVariant;
33use std::sync::mpsc::Sender;
34
35/// A simple widget that opens a popup when clicked. It could be used to create dropdown menus that
36/// consolidates content of a group.
37#[derive(Default, Clone, Visit, Reflect, Debug, TypeUuidProvider, ComponentProvider)]
38#[type_uuid(id = "c0a4c51b-f041-453b-a89d-7ceb5394e321")]
39#[reflect(derived_type = "UiNode")]
40pub struct DropdownMenu {
41    /// Base widget of the dropdown menu.
42    pub widget: Widget,
43    /// A handle of the inner popup, that stores the content of the menu.
44    pub popup: Handle<Popup>,
45}
46
47crate::define_widget_deref!(DropdownMenu);
48
49impl Control for DropdownMenu {
50    fn on_remove(&self, sender: &Sender<UiMessage>) {
51        sender
52            .send(UiMessage::for_widget(self.popup, WidgetMessage::Remove))
53            .unwrap()
54    }
55
56    fn handle_routed_message(&mut self, ui: &mut UserInterface, message: &mut UiMessage) {
57        self.widget.handle_routed_message(ui, message);
58
59        if let Some(WidgetMessage::MouseDown { button, .. }) = message.data() {
60            if *button == MouseButton::Left {
61                ui.send(
62                    self.popup,
63                    PopupMessage::Placement(Placement::LeftBottom(self.handle)),
64                );
65                ui.send(self.popup, PopupMessage::Open);
66            }
67        }
68    }
69}
70
71/// Dropdown menu builder creates new [`DropdownMenu`] widget instances and adds them to the
72/// user interface.
73pub struct DropdownMenuBuilder {
74    widget_builder: WidgetBuilder,
75    header: Handle<UiNode>,
76    content: Handle<UiNode>,
77}
78
79impl DropdownMenuBuilder {
80    /// Creates new builder instance.
81    pub fn new(widget_builder: WidgetBuilder) -> Self {
82        Self {
83            widget_builder,
84            header: Handle::NONE,
85            content: Handle::NONE,
86        }
87    }
88
89    /// Sets the desired header.
90    pub fn with_header(mut self, header: Handle<UiNode>) -> Self {
91        self.header = header;
92        self
93    }
94
95    /// Sets the content of the menu.
96    pub fn with_content(mut self, content: Handle<impl ObjectOrVariant<UiNode>>) -> Self {
97        self.content = content.to_base();
98        self
99    }
100
101    /// Finishes dropdown menu widget building and adds the instance to the user interface and
102    /// returns its handle.
103    pub fn build(self, ctx: &mut BuildContext) -> Handle<DropdownMenu> {
104        let popup = PopupBuilder::new(WidgetBuilder::new())
105            .stays_open(false)
106            .with_content(self.content)
107            .build(ctx);
108
109        let dropdown_menu = DropdownMenu {
110            widget: self.widget_builder.with_child(self.header).build(ctx),
111            popup,
112        };
113        ctx.add(dropdown_menu)
114    }
115}
116
117#[cfg(test)]
118mod test {
119    use crate::dropdown_menu::DropdownMenuBuilder;
120    use crate::{test::test_widget_deletion, widget::WidgetBuilder};
121
122    #[test]
123    fn test_deletion() {
124        test_widget_deletion(|ctx| DropdownMenuBuilder::new(WidgetBuilder::new()).build(ctx));
125    }
126}