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