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