1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License in the LICENSE-APACHE file or at:
//     https://www.apache.org/licenses/LICENSE-2.0

//! Popup root

use crate::dir::Direction;
use crate::event::{ConfigCx, Event, EventCx, IsUsed, Scroll, Unused, Used};
use crate::{Events, Id, LayoutExt, Widget, WindowId};
use kas_macros::{autoimpl, impl_scope, widget_index};

#[allow(unused)] use crate::event::EventState;

#[derive(Clone, Debug)]
pub(crate) struct PopupDescriptor {
    pub id: Id,
    pub parent: Id,
    pub direction: Direction,
}

impl_scope! {
    /// A popup (e.g. menu or tooltip)
    ///
    /// A pop-up is a box used for things like tool-tips and menus which escapes
    /// the parent's rect. This widget is the root of any popup UI.
    ///
    /// This widget must be excluded from the parent's layout.
    ///
    /// Depending on the platform, the pop-up may be a special window or emulate
    /// this with a layer drawn in an existing window. Both approaches should
    /// exhibit similar behaviour except that the former approach allows the
    /// popup to escape the bounds of the parent window.
    /// NOTE: currently only the emulated approach is implemented.
    ///
    /// A popup receives input data from its parent like any other widget.
    #[autoimpl(Deref, DerefMut using self.inner)]
    #[widget {
        layout = frame!(self.inner, style = kas::theme::FrameStyle::Popup);
    }]
    pub struct Popup<W: Widget> {
        core: widget_core!(),
        direction: Direction,
        #[widget]
        inner: W,
        win_id: Option<WindowId>,
    }

    impl Events for Self {
        type Data = W::Data;

        fn configure(&mut self, cx: &mut ConfigCx) {
            cx.new_access_layer(self.id(), true);
        }

        fn configure_recurse(&mut self, cx: &mut ConfigCx, data: &Self::Data) {
            if self.win_id.is_some() {
                let id = self.make_child_id(widget_index!(self.inner));
                cx.configure(self.inner.as_node(data), id)
            }
        }

        fn update_recurse(&mut self, cx: &mut ConfigCx, data: &Self::Data) {
            if self.win_id.is_some() {
                cx.update(self.inner.as_node(data))
            }
        }

        fn handle_event(&mut self, cx: &mut EventCx, _: &W::Data, event: Event) -> IsUsed {
            match event {
                Event::PressStart { press } => {
                    if press.id.as_ref().map(|id| self.is_ancestor_of(id)).unwrap_or(false) {
                        Unused
                    } else {
                        self.close(cx);
                        Unused
                    }
                }
                Event::PopupClosed(id) => {
                    debug_assert_eq!(Some(id), self.win_id);
                    self.win_id = None;
                    Used
                }
                _ => Unused,
            }
        }

        fn handle_scroll(&mut self, cx: &mut EventCx, _: &Self::Data, _: Scroll) {
            // Scroll of the popup does not affect ancestor nodes
            cx.set_scroll(Scroll::None);
        }
    }

    impl Self {
        /// Construct a popup over a `W: Widget`
        pub fn new(inner: W, direction: Direction) -> Self {
            Popup {
                core: Default::default(),
                direction,
                inner,
                win_id: None,
            }
        }

        /// Get direction
        pub fn direction(&self) -> Direction {
            self.direction
        }

        /// Set direction
        pub fn set_direction(&mut self, direction: Direction) {
            self.direction = direction;
        }

        /// Query whether the popup is open
        pub fn is_open(&self) -> bool {
            self.win_id.is_some()
        }

        /// Open the popup
        ///
        /// The popup is positioned next to the `parent`'s rect in the specified
        /// direction (if this is not possible, the direction may be reversed).
        ///
        /// The `parent` is marked as depressed (pushed down) while the popup is
        /// open.
        ///
        /// Returns `true` when the popup is newly opened. In this case, the
        /// caller may wish to call [`EventState::next_nav_focus`] next.
        pub fn open(&mut self, cx: &mut EventCx, data: &W::Data, parent: Id) -> bool {
            if self.win_id.is_some() {
                return false;
            }

            let id = self.make_child_id(widget_index!(self.inner));
            cx.configure(self.inner.as_node(data), id);

            self.win_id = Some(cx.add_popup(kas::PopupDescriptor {
                id: self.id(),
                parent,
                direction: self.direction,
            }));

            true
        }

        /// Close the popup
        ///
        /// Navigation focus will return to whichever widget had focus before
        /// the popup was open.
        pub fn close(&mut self, cx: &mut EventCx) {
            if let Some(id) = self.win_id {
                cx.close_window(id);
            }
        }
    }
}