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);
}
}
}
}