Skip to main content

iced_cube/overlay/
anchored.rs

1//! Floats a panel next to an anchor element: the one layer popovers, menus,
2//! the combobox and select lists, and the command list's keys are built on.
3//!
4//! [`anchored`] wraps an anchor, such as a trigger button or a text field,
5//! and while it has content draws that content above everything else.
6//! [`place`] is the pure positioning logic: it puts the panel on one
7//! [`Side`] of the anchor, aligned by [`Align`], then flips and shifts it to
8//! stay inside the window.
9//!
10//! Every open panel follows the same rules:
11//!
12//! - Events reach the content first, including panels nested inside it, so
13//!   Escape closes the innermost layer.
14//! - [`on_key`](Anchored::on_key) sees the key presses the content leaves,
15//!   while the panel is open or something in the anchor has focus.
16//! - [`on_dismiss`](Anchored::on_dismiss) is sent at most once per event: on
17//!   a [dismiss chord](Anchored::dismiss_keys), a press outside the panel and
18//!   the anchor, or, with [`dismiss_on_blur`](Anchored::dismiss_on_blur),
19//!   when the anchor loses focus.
20//! - That outside press is captured, so nothing underneath sees it, unless
21//!   the panel [passes it through](Anchored::pass_through).
22//! - Presses on the anchor are left to the anchor, so a trigger can toggle
23//!   the panel itself. Presses inside the panel never reach the widgets
24//!   underneath it.
25
26use iced::advanced::layout::{self, Layout};
27use iced::advanced::overlay;
28use iced::advanced::renderer;
29use iced::advanced::widget::operation::Focusable;
30use iced::advanced::widget::{Id, Operation, Tree, Widget, tree};
31use iced::advanced::{Clipboard, Shell};
32use iced::keyboard::{self, key::Named};
33use iced::widget::container;
34use iced::{
35    Background, Border, Color, Element, Event, Length, Point, Rectangle, Renderer, Shadow, Size,
36    Theme, Vector, mouse, touch,
37};
38
39use crate::keys::{self, Chord};
40use crate::theme::{Tokens, fade, radius};
41
42/// Default space between the anchor and the panel, in logical pixels.
43pub const GAP: f32 = 4.0;
44
45/// Space kept between the panel and the edges of the window.
46pub const MARGIN: f32 = 8.0;
47
48/// The side of the anchor the panel appears on.
49#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
50pub enum Side {
51    Top,
52    #[default]
53    Bottom,
54    Left,
55    Right,
56}
57
58impl Side {
59    pub const ALL: [Side; 4] = [Side::Top, Side::Bottom, Side::Left, Side::Right];
60
61    pub fn opposite(self) -> Side {
62        match self {
63            Side::Top => Side::Bottom,
64            Side::Bottom => Side::Top,
65            Side::Left => Side::Right,
66            Side::Right => Side::Left,
67        }
68    }
69
70    fn is_vertical(self) -> bool {
71        matches!(self, Side::Top | Side::Bottom)
72    }
73}
74
75/// How the panel lines up with the anchor along that side.
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
77pub enum Align {
78    /// Left edges meet (or top edges, on the left and right sides).
79    Start,
80    #[default]
81    Center,
82    /// Right edges meet (or bottom edges, on the left and right sides).
83    End,
84}
85
86impl Align {
87    pub const ALL: [Align; 3] = [Align::Start, Align::Center, Align::End];
88
89    fn mirrored(self) -> Align {
90        match self {
91            Align::Start => Align::End,
92            Align::Center => Align::Center,
93            Align::End => Align::Start,
94        }
95    }
96}
97
98/// Where a panel sits relative to its anchor.
99#[derive(Debug, Clone, Copy, PartialEq)]
100pub struct Placement {
101    pub side: Side,
102    pub align: Align,
103    /// Space between the anchor and the panel.
104    pub gap: f32,
105    /// Shift along the side, away from the aligned edge.
106    pub offset: f32,
107}
108
109impl Default for Placement {
110    fn default() -> Self {
111        Self {
112            side: Side::default(),
113            align: Align::default(),
114            gap: GAP,
115            offset: 0.0,
116        }
117    }
118}
119
120impl Placement {
121    pub fn new(side: Side, align: Align) -> Self {
122        Self {
123            side,
124            align,
125            ..Self::default()
126        }
127    }
128
129    pub fn gap(mut self, gap: f32) -> Self {
130        self.gap = gap;
131        self
132    }
133
134    pub fn offset(mut self, offset: f32) -> Self {
135        self.offset = offset;
136        self
137    }
138}
139
140/// Positions a panel of `size` next to `anchor`, keeping it inside
141/// `viewport` less [`MARGIN`].
142///
143/// The panel moves to the opposite side when it does not fit on the
144/// requested one and does fit there, and to the mirrored alignment when it
145/// would overflow along the side. A panel on the left or right that fits on
146/// neither moves below the anchor, or above it, rather than covering it.
147/// Whatever still overflows is shifted back inside, pinned to the top left
148/// if the panel is larger than the window.
149pub fn place(
150    anchor: Rectangle,
151    size: Size,
152    viewport: Rectangle,
153    placement: Placement,
154) -> Rectangle {
155    let area = viewport.shrink(MARGIN);
156    let side = choose_side(anchor, size, area, placement);
157    // The offset runs along the requested side, so it does not carry over
158    // to a side across it.
159    let offset = if side.is_vertical() == placement.side.is_vertical() {
160        placement.offset
161    } else {
162        0.0
163    };
164
165    let main = match side {
166        Side::Top => anchor.y - placement.gap - size.height,
167        Side::Bottom => anchor.y + anchor.height + placement.gap,
168        Side::Left => anchor.x - placement.gap - size.width,
169        Side::Right => anchor.x + anchor.width + placement.gap,
170    };
171
172    let (start, length, extent, low, high) = if side.is_vertical() {
173        (
174            anchor.x,
175            anchor.width,
176            size.width,
177            area.x,
178            area.x + area.width,
179        )
180    } else {
181        (
182            anchor.y,
183            anchor.height,
184            size.height,
185            area.y,
186            area.y + area.height,
187        )
188    };
189    let cross_at = |align: Align| match align {
190        Align::Start => start + offset,
191        Align::Center => start + (length - extent) / 2.0 + offset,
192        Align::End => start + length - extent - offset,
193    };
194    let fits = |value: f32| value >= low && value + extent <= high;
195    let preferred = cross_at(placement.align);
196    let mirrored = cross_at(placement.align.mirrored());
197    let cross = if !fits(preferred) && fits(mirrored) {
198        mirrored
199    } else {
200        preferred
201    };
202
203    let (x, y) = if side.is_vertical() {
204        (cross, main)
205    } else {
206        (main, cross)
207    };
208    Rectangle::new(
209        Point::new(
210            clamp(x, area.x, area.x + area.width - size.width),
211            clamp(y, area.y, area.y + area.height - size.height),
212        ),
213        size,
214    )
215}
216
217/// The side [`place`] puts the panel on: the requested side, else the
218/// opposite one, else (for the left and right) below or above. With no
219/// side that fits, the roomier of the requested pair wins, keeping the
220/// requested side on a tie.
221fn choose_side(anchor: Rectangle, size: Size, area: Rectangle, placement: Placement) -> Side {
222    let room = |side: Side| room(anchor, area, placement.gap, side);
223    let fits = |side: Side| room(side) >= needed(size, side);
224    let roomier = |side: Side| {
225        if room(side.opposite()) > room(side) {
226            side.opposite()
227        } else {
228            side
229        }
230    };
231
232    let requested = placement.side;
233    let opposite = requested.opposite();
234    if fits(requested) {
235        return requested;
236    }
237    if fits(opposite) {
238        return opposite;
239    }
240    if requested.is_vertical() {
241        return roomier(requested);
242    }
243    [Side::Bottom, Side::Top]
244        .into_iter()
245        .find(|side| fits(*side))
246        .unwrap_or_else(|| roomier(Side::Bottom))
247}
248
249/// The panel's extent across `side`, the length that must fit beside it.
250fn needed(size: Size, side: Side) -> f32 {
251    if side.is_vertical() {
252        size.height
253    } else {
254        size.width
255    }
256}
257
258/// Space between the anchor and the edge of `area` on one side, less the gap.
259fn room(anchor: Rectangle, area: Rectangle, gap: f32, side: Side) -> f32 {
260    match side {
261        Side::Top => anchor.y - gap - area.y,
262        Side::Bottom => area.y + area.height - (anchor.y + anchor.height + gap),
263        Side::Left => anchor.x - gap - area.x,
264        Side::Right => area.x + area.width - (anchor.x + anchor.width + gap),
265    }
266}
267
268/// The tallest a panel may be with [`Anchored::fit_height`]: `None` when
269/// it fits where [`place`] puts it as it is, otherwise the room on the
270/// roomier of above and below, however little, so the panel scrolls rather
271/// than covering its anchor. A panel placed on the left or right is only
272/// limited once it fits beside the anchor on neither side.
273pub fn fitted_height(
274    anchor: Rectangle,
275    size: Size,
276    viewport: Rectangle,
277    placement: Placement,
278) -> Option<f32> {
279    let area = viewport.shrink(MARGIN);
280    let side = placement.side;
281    let beside = |side: Side| room(anchor, area, placement.gap, side) >= size.width;
282    if !side.is_vertical() && (beside(side) || beside(side.opposite())) {
283        return None;
284    }
285    let above = room(anchor, area, placement.gap, Side::Top);
286    let below = room(anchor, area, placement.gap, Side::Bottom);
287    if above >= size.height || below >= size.height {
288        return None;
289    }
290    Some(above.max(below).max(0.0))
291}
292
293/// Keeps `value` within `low..=high`, preferring `low` when the range is empty.
294fn clamp(value: f32, low: f32, high: f32) -> f32 {
295    value.min(high).max(low)
296}
297
298/// The floating surface shared by popovers, menus, lists and dialogs: the
299/// popover colour, a subtle border and a restrained shadow.
300pub fn surface_style(tokens: &Tokens) -> container::Style {
301    container::Style {
302        background: Some(Background::Color(tokens.popover)),
303        text_color: Some(tokens.foreground),
304        border: Border {
305            color: tokens.border,
306            width: 1.0,
307            radius: radius::MD.into(),
308        },
309        shadow: shadow(tokens),
310        snap: true,
311    }
312}
313
314/// The shadow under every floating surface, stronger on a dark page.
315pub fn shadow(tokens: &Tokens) -> Shadow {
316    Shadow {
317        color: fade(Color::BLACK, if tokens.is_dark { 0.5 } else { 0.1 }),
318        offset: Vector::new(0.0, 4.0),
319        blur_radius: 12.0,
320    }
321}
322
323type OnKey<'a, Message> = Box<dyn Fn(&keys::Event) -> Option<Message> + 'a>;
324
325/// How an open panel behaves. Shared with the widgets in this crate that
326/// float a panel of their own.
327pub(crate) struct Behaviour<'a, Message> {
328    pub(crate) placement: Placement,
329    pub(crate) point: Option<Point>,
330    pub(crate) match_width: bool,
331    pub(crate) fit_height: bool,
332    pub(crate) on_dismiss: Option<Message>,
333    pub(crate) dismiss_keys: Vec<Chord>,
334    pub(crate) dismiss_on_anchor_press: bool,
335    pub(crate) dismiss_on_blur: bool,
336    pub(crate) pass_through: bool,
337    /// The panel opens and closes itself through [`State::open`]: a
338    /// dismissal or a press the content captured closes it.
339    pub(crate) closes_itself: bool,
340    pub(crate) on_key: Option<OnKey<'a, Message>>,
341    pub(crate) on_anchor_press: Option<Message>,
342    pub(crate) on_focus: Option<OnFocus<'a, Message>>,
343}
344
345type OnFocus<'a, Message> = Box<dyn Fn(bool) -> Message + 'a>;
346
347impl<Message> Default for Behaviour<'_, Message> {
348    fn default() -> Self {
349        Self {
350            placement: Placement::default(),
351            point: None,
352            match_width: false,
353            fit_height: false,
354            on_dismiss: None,
355            dismiss_keys: vec![Chord::named(Named::Escape)],
356            dismiss_on_anchor_press: false,
357            dismiss_on_blur: false,
358            pass_through: false,
359            closes_itself: false,
360            on_key: None,
361            on_anchor_press: None,
362            on_focus: None,
363        }
364    }
365}
366
367impl<Message> Behaviour<'_, Message> {
368    fn can_dismiss(&self) -> bool {
369        self.on_dismiss.is_some() || self.closes_itself
370    }
371
372    fn watches_focus(&self) -> bool {
373        self.on_key.is_some() || self.dismiss_on_blur || self.on_focus.is_some()
374    }
375
376    /// The message for a change of focus inside the anchor, if it changed
377    /// and anyone listens.
378    fn focus_message(&self, was: bool, is: bool) -> Option<Message> {
379        if was == is {
380            return None;
381        }
382        self.on_focus.as_ref().map(|on_focus| on_focus(is))
383    }
384}
385
386/// What an anchor remembers between events.
387#[derive(Debug, Default)]
388pub(crate) struct State {
389    focused: bool,
390    /// An outside press dismissed the panel, so the anchor gives up focus
391    /// on the next event, without a second dismissal.
392    unfocus: bool,
393    /// Whether a panel that [closes itself](Behaviour::closes_itself) is open.
394    pub(crate) open: bool,
395}
396
397/// An anchor with a panel floating next to it. Convert it into an
398/// [`Element`] to render.
399pub struct Anchored<'a, Message> {
400    anchor: Element<'a, Message>,
401    content: Option<Element<'a, Message>>,
402    behaviour: Behaviour<'a, Message>,
403}
404
405impl<Message> std::fmt::Debug for Anchored<'_, Message> {
406    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
407        let behaviour = &self.behaviour;
408        f.debug_struct("Anchored")
409            .field("open", &self.content.is_some())
410            .field("placement", &behaviour.placement)
411            .field("point", &behaviour.point)
412            .field("match_width", &behaviour.match_width)
413            .field("fit_height", &behaviour.fit_height)
414            .field("dismissable", &behaviour.on_dismiss.is_some())
415            .field("dismiss_keys", &behaviour.dismiss_keys)
416            .field("pass_through", &behaviour.pass_through)
417            .finish_non_exhaustive()
418    }
419}
420
421/// Wraps `anchor`. Nothing floats until [`content`](Anchored::content) is set.
422pub fn anchored<'a, Message>(anchor: impl Into<Element<'a, Message>>) -> Anchored<'a, Message> {
423    Anchored {
424        anchor: anchor.into(),
425        content: None,
426        behaviour: Behaviour::default(),
427    }
428}
429
430impl<'a, Message> Anchored<'a, Message> {
431    /// The floating panel. `None` keeps it closed.
432    pub fn content(mut self, content: Option<Element<'a, Message>>) -> Self {
433        self.content = content;
434        self
435    }
436
437    pub fn placement(mut self, placement: Placement) -> Self {
438        self.behaviour.placement = placement;
439        self
440    }
441
442    /// Anchors the panel to a point, relative to the anchor's top left
443    /// corner, instead of to the anchor's edges.
444    pub fn at(mut self, point: Point) -> Self {
445        self.behaviour.point = Some(point);
446        self
447    }
448
449    /// Makes the panel exactly as wide as the anchor, as a list under a
450    /// field is.
451    pub fn match_width(mut self, match_width: bool) -> Self {
452        self.behaviour.match_width = match_width;
453        self
454    }
455
456    /// When the panel fits on no side of the anchor, limits its height to
457    /// the roomier of above and below instead of shifting it over the
458    /// anchor; see [`fitted_height`]. For content that scrolls, such as a
459    /// long menu.
460    pub fn fit_height(mut self, fit: bool) -> Self {
461        self.behaviour.fit_height = fit;
462        self
463    }
464
465    /// Sent once when the panel is dismissed; see the module docs.
466    pub fn on_dismiss(self, message: Message) -> Self {
467        self.on_dismiss_maybe(Some(message))
468    }
469
470    /// Like [`on_dismiss`](Self::on_dismiss); `None` makes the panel
471    /// undismissable, so presses outside it pass through.
472    pub fn on_dismiss_maybe(mut self, message: Option<Message>) -> Self {
473        self.behaviour.on_dismiss = message;
474        self
475    }
476
477    /// The chords that dismiss the panel when the content and
478    /// [`on_key`](Self::on_key) leave them alone. Defaults to Escape. Pass
479    /// the chords of a component keymap's close action, so unbinding them
480    /// there works.
481    pub fn dismiss_keys(mut self, chords: impl IntoIterator<Item = Chord>) -> Self {
482        self.behaviour.dismiss_keys = chords.into_iter().collect();
483        self
484    }
485
486    /// Also dismiss on presses on the anchor itself, for anchors that do
487    /// not toggle the panel, such as a context menu area. The press still
488    /// reaches the anchor.
489    pub fn dismiss_on_anchor_press(mut self, dismiss: bool) -> Self {
490        self.behaviour.dismiss_on_anchor_press = dismiss;
491        self
492    }
493
494    /// Also dismiss when a focused widget inside the anchor, such as a text
495    /// field, loses focus.
496    pub fn dismiss_on_blur(mut self, dismiss: bool) -> Self {
497        self.behaviour.dismiss_on_blur = dismiss;
498        self
499    }
500
501    /// Lets a press outside the panel reach the widgets underneath after
502    /// dismissing it, for panels that are not modal, such as a popover.
503    pub fn pass_through(mut self, pass_through: bool) -> Self {
504        self.behaviour.pass_through = pass_through;
505        self
506    }
507
508    /// Resolves key presses the content leaves, while the panel is open or
509    /// something in the anchor has focus. Returning a message sends it and
510    /// captures the key.
511    pub fn on_key(mut self, on_key: impl Fn(&keys::Event) -> Option<Message> + 'a) -> Self {
512        self.behaviour.on_key = Some(Box::new(on_key));
513        self
514    }
515
516    /// Sent with `true` when a widget inside the anchor, such as a text
517    /// field, gains focus, and with `false` when it loses it.
518    pub fn on_focus(mut self, on_focus: impl Fn(bool) -> Message + 'a) -> Self {
519        self.behaviour.on_focus = Some(Box::new(on_focus));
520        self
521    }
522
523    /// Sent when the pointer is pressed on the anchor, before the anchor
524    /// sees the press.
525    pub fn on_anchor_press_maybe(mut self, message: Option<Message>) -> Self {
526        self.behaviour.on_anchor_press = message;
527        self
528    }
529}
530
531impl<'a, Message: Clone + 'a> From<Anchored<'a, Message>> for Element<'a, Message> {
532    fn from(anchored: Anchored<'a, Message>) -> Self {
533        Element::new(anchored)
534    }
535}
536
537impl<Message: Clone> Widget<Message, Theme, Renderer> for Anchored<'_, Message> {
538    fn tag(&self) -> tree::Tag {
539        tree::Tag::of::<State>()
540    }
541
542    fn state(&self) -> tree::State {
543        tree::State::new(State::default())
544    }
545
546    fn size(&self) -> Size<Length> {
547        self.anchor.as_widget().size()
548    }
549
550    fn size_hint(&self) -> Size<Length> {
551        self.anchor.as_widget().size_hint()
552    }
553
554    fn children(&self) -> Vec<Tree> {
555        std::iter::once(&self.anchor)
556            .chain(&self.content)
557            .map(Tree::new)
558            .collect()
559    }
560
561    fn diff(&self, tree: &mut Tree) {
562        match &self.content {
563            Some(content) => tree.diff_children(&[&self.anchor, content]),
564            None => tree.diff_children(&[&self.anchor]),
565        }
566    }
567
568    fn layout(
569        &mut self,
570        tree: &mut Tree,
571        renderer: &Renderer,
572        limits: &layout::Limits,
573    ) -> layout::Node {
574        self.anchor
575            .as_widget_mut()
576            .layout(&mut tree.children[0], renderer, limits)
577    }
578
579    fn draw(
580        &self,
581        tree: &Tree,
582        renderer: &mut Renderer,
583        theme: &Theme,
584        style: &renderer::Style,
585        layout: Layout<'_>,
586        cursor: mouse::Cursor,
587        viewport: &Rectangle,
588    ) {
589        self.anchor.as_widget().draw(
590            &tree.children[0],
591            renderer,
592            theme,
593            style,
594            layout,
595            cursor,
596            viewport,
597        );
598    }
599
600    fn operate(
601        &mut self,
602        tree: &mut Tree,
603        layout: Layout<'_>,
604        renderer: &Renderer,
605        operation: &mut dyn Operation,
606    ) {
607        self.anchor
608            .as_widget_mut()
609            .operate(&mut tree.children[0], layout, renderer, operation);
610    }
611
612    fn update(
613        &mut self,
614        tree: &mut Tree,
615        event: &Event,
616        layout: Layout<'_>,
617        cursor: mouse::Cursor,
618        renderer: &Renderer,
619        clipboard: &mut dyn Clipboard,
620        shell: &mut Shell<'_, Message>,
621        viewport: &Rectangle,
622    ) {
623        let Tree {
624            state, children, ..
625        } = tree;
626        let state = state.downcast_mut::<State>();
627        let Some(anchor_tree) = children.first_mut() else {
628            return;
629        };
630        let behaviour = &self.behaviour;
631
632        if state.unfocus {
633            state.unfocus = false;
634            if let Some(message) = behaviour.focus_message(state.focused, false) {
635                shell.publish(message);
636            }
637            state.focused = false;
638            self.anchor
639                .as_widget_mut()
640                .operate(anchor_tree, layout, renderer, &mut Unfocus);
641            shell.request_redraw();
642        }
643
644        if state.focused
645            && let Some(message) = key_message(behaviour, event)
646        {
647            shell.publish(message);
648            shell.capture_event();
649            return;
650        }
651
652        if is_press(event)
653            && cursor.is_over(layout.bounds())
654            && let Some(message) = &behaviour.on_anchor_press
655        {
656            shell.publish(message.clone());
657        }
658
659        self.anchor.as_widget_mut().update(
660            anchor_tree,
661            event,
662            layout,
663            cursor,
664            renderer,
665            clipboard,
666            shell,
667            viewport,
668        );
669
670        if !behaviour.watches_focus() {
671            return;
672        }
673        let mut focus = FindFocus::default();
674        self.anchor
675            .as_widget_mut()
676            .operate(anchor_tree, layout, renderer, &mut focus);
677        if state.focused
678            && !focus.0
679            && behaviour.dismiss_on_blur
680            && let Some(message) = &behaviour.on_dismiss
681        {
682            shell.publish(message.clone());
683        }
684        if let Some(message) = behaviour.focus_message(state.focused, focus.0) {
685            shell.publish(message);
686        }
687        state.focused = focus.0;
688    }
689
690    fn mouse_interaction(
691        &self,
692        tree: &Tree,
693        layout: Layout<'_>,
694        cursor: mouse::Cursor,
695        viewport: &Rectangle,
696        renderer: &Renderer,
697    ) -> mouse::Interaction {
698        self.anchor.as_widget().mouse_interaction(
699            &tree.children[0],
700            layout,
701            cursor,
702            viewport,
703            renderer,
704        )
705    }
706
707    fn overlay<'b>(
708        &'b mut self,
709        tree: &'b mut Tree,
710        layout: Layout<'b>,
711        renderer: &Renderer,
712        viewport: &Rectangle,
713        translation: Vector,
714    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
715        let Self {
716            anchor,
717            content,
718            behaviour,
719        } = self;
720        let Tree {
721            state, children, ..
722        } = tree;
723        let mut trees = children.iter_mut();
724        let anchor_tree = trees.next()?;
725        let trigger = layout.bounds() + translation;
726
727        // While the panel is open the anchor's own overlays, such as its
728        // tooltip, stay hidden.
729        let Some((content, tree)) = content.as_mut().zip(trees.next()) else {
730            return anchor.as_widget_mut().overlay(
731                anchor_tree,
732                layout,
733                renderer,
734                viewport,
735                translation,
736            );
737        };
738        Some(floating(
739            content,
740            tree,
741            state.downcast_mut::<State>(),
742            behaviour,
743            trigger,
744        ))
745    }
746}
747
748/// The overlay drawing a panel for `trigger`, the anchor's bounds in
749/// window coordinates.
750pub(crate) fn floating<'a, 'b, Message: Clone>(
751    content: &'b mut Element<'a, Message>,
752    tree: &'b mut Tree,
753    state: &'b mut State,
754    behaviour: &'b Behaviour<'a, Message>,
755    trigger: Rectangle,
756) -> overlay::Element<'b, Message, Theme, Renderer> {
757    let target = match behaviour.point {
758        Some(point) => Rectangle::new(
759            trigger.position() + Vector::new(point.x, point.y),
760            Size::ZERO,
761        ),
762        None => trigger,
763    };
764    overlay::Element::new(Box::new(Floating {
765        content,
766        tree,
767        state,
768        behaviour,
769        target,
770        trigger,
771    }))
772}
773
774/// The overlay drawing the panel. Its node covers the whole window so the
775/// panel's shadow is never clipped; the panel is its only child.
776struct Floating<'a, 'b, Message> {
777    content: &'b mut Element<'a, Message>,
778    tree: &'b mut Tree,
779    state: &'b mut State,
780    behaviour: &'b Behaviour<'a, Message>,
781    target: Rectangle,
782    trigger: Rectangle,
783}
784
785impl<Message: Clone> Floating<'_, '_, Message> {
786    fn dismiss(&mut self, shell: &mut Shell<'_, Message>) {
787        if self.behaviour.closes_itself {
788            self.state.open = false;
789            shell.request_redraw();
790        }
791        if self.behaviour.dismiss_on_blur {
792            self.state.unfocus = true;
793        }
794        if let Some(message) = &self.behaviour.on_dismiss {
795            shell.publish(message.clone());
796        }
797    }
798}
799
800/// The message [`Behaviour::on_key`] sends for a key press, if any.
801fn key_message<Message>(behaviour: &Behaviour<'_, Message>, event: &Event) -> Option<Message> {
802    let Event::Keyboard(keyboard::Event::KeyPressed { key, modifiers, .. }) = event else {
803        return None;
804    };
805    let on_key = behaviour.on_key.as_ref()?;
806    on_key(&keys::Event {
807        key: key.clone(),
808        modifiers: *modifiers,
809    })
810}
811
812fn is_dismiss_key<Message>(behaviour: &Behaviour<'_, Message>, event: &Event) -> bool {
813    let Event::Keyboard(keyboard::Event::KeyPressed { key, modifiers, .. }) = event else {
814        return false;
815    };
816    behaviour
817        .dismiss_keys
818        .iter()
819        .any(|chord| chord.matches(key, *modifiers))
820}
821
822fn is_press(event: &Event) -> bool {
823    matches!(
824        event,
825        Event::Mouse(mouse::Event::ButtonPressed(_))
826            | Event::Touch(touch::Event::FingerPressed { .. })
827    )
828}
829
830fn is_release(event: &Event) -> bool {
831    matches!(
832        event,
833        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
834            | Event::Touch(touch::Event::FingerLifted { .. })
835    )
836}
837
838/// Where a mouse or touch press landed, if the event is one.
839fn press_position(event: &Event, cursor: mouse::Cursor) -> Option<Point> {
840    match event {
841        Event::Mouse(mouse::Event::ButtonPressed(_)) => cursor.position(),
842        Event::Touch(touch::Event::FingerPressed { position, .. }) => Some(*position),
843        _ => None,
844    }
845}
846
847fn is_pointer_input(event: &Event) -> bool {
848    matches!(
849        event,
850        Event::Mouse(
851            mouse::Event::ButtonPressed(_)
852                | mouse::Event::ButtonReleased(_)
853                | mouse::Event::WheelScrolled { .. }
854        ) | Event::Touch(_)
855    )
856}
857
858impl<Message: Clone> overlay::Overlay<Message, Theme, Renderer> for Floating<'_, '_, Message> {
859    fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node {
860        let viewport = Rectangle::with_size(bounds);
861        let room = viewport.shrink(MARGIN).size();
862        let room = Size::new(room.width.max(0.0), room.height.max(0.0));
863        let match_width = self.behaviour.match_width;
864        let trigger_width = self.trigger.width;
865        let limits = |height: f32| {
866            if match_width {
867                let width = trigger_width.min(room.width);
868                layout::Limits::new(Size::new(width, 0.0), Size::new(width, height))
869            } else {
870                layout::Limits::new(Size::ZERO, Size::new(room.width, height))
871            }
872        };
873        let placement = self.behaviour.placement;
874        let mut node =
875            self.content
876                .as_widget_mut()
877                .layout(self.tree, renderer, &limits(room.height));
878        let fitted = self
879            .behaviour
880            .fit_height
881            .then(|| fitted_height(self.target, node.size(), viewport, placement))
882            .flatten();
883        if let Some(height) = fitted {
884            node = self
885                .content
886                .as_widget_mut()
887                .layout(self.tree, renderer, &limits(height));
888        }
889        let panel = place(self.target, node.size(), viewport, placement);
890        layout::Node::with_children(bounds, vec![node.move_to(panel.position())])
891    }
892
893    fn draw(
894        &self,
895        renderer: &mut Renderer,
896        theme: &Theme,
897        style: &renderer::Style,
898        layout: Layout<'_>,
899        cursor: mouse::Cursor,
900    ) {
901        let Some(panel) = layout.children().next() else {
902            return;
903        };
904        self.content.as_widget().draw(
905            self.tree,
906            renderer,
907            theme,
908            style,
909            panel,
910            cursor,
911            &layout.bounds(),
912        );
913    }
914
915    fn operate(&mut self, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn Operation) {
916        let Some(panel) = layout.children().next() else {
917            return;
918        };
919        self.content
920            .as_widget_mut()
921            .operate(self.tree, panel, renderer, operation);
922    }
923
924    fn update(
925        &mut self,
926        event: &Event,
927        layout: Layout<'_>,
928        cursor: mouse::Cursor,
929        renderer: &Renderer,
930        clipboard: &mut dyn Clipboard,
931        shell: &mut Shell<'_, Message>,
932    ) {
933        let Some(panel) = layout.children().next() else {
934            return;
935        };
936
937        self.content.as_widget_mut().update(
938            self.tree,
939            event,
940            panel,
941            cursor,
942            renderer,
943            clipboard,
944            shell,
945            &layout.bounds(),
946        );
947        if shell.is_event_captured() {
948            // A row took the click, so the choice is made.
949            if self.behaviour.closes_itself && is_release(event) {
950                self.state.open = false;
951                shell.request_redraw();
952            }
953            return;
954        }
955
956        if let Some(message) = key_message(self.behaviour, event) {
957            shell.publish(message);
958            shell.capture_event();
959            return;
960        }
961        if is_dismiss_key(self.behaviour, event) && self.behaviour.can_dismiss() {
962            self.dismiss(shell);
963            shell.capture_event();
964            return;
965        }
966        if !is_pointer_input(event) {
967            return;
968        }
969
970        let bounds = panel.bounds();
971        if cursor.is_over(bounds) {
972            shell.capture_event();
973            return;
974        }
975        let Some(position) = press_position(event, cursor) else {
976            return;
977        };
978        if bounds.contains(position) {
979            shell.capture_event();
980            return;
981        }
982        let on_anchor = self.trigger.contains(position);
983        if (on_anchor && !self.behaviour.dismiss_on_anchor_press) || !self.behaviour.can_dismiss() {
984            return;
985        }
986        self.dismiss(shell);
987        if !on_anchor && !self.behaviour.pass_through {
988            shell.capture_event();
989        }
990    }
991
992    fn mouse_interaction(
993        &self,
994        layout: Layout<'_>,
995        cursor: mouse::Cursor,
996        renderer: &Renderer,
997    ) -> mouse::Interaction {
998        let Some(panel) = layout.children().next() else {
999            return mouse::Interaction::None;
1000        };
1001        if !cursor.is_over(panel.bounds()) {
1002            return mouse::Interaction::None;
1003        }
1004        self.content
1005            .as_widget()
1006            .mouse_interaction(self.tree, panel, cursor, &layout.bounds(), renderer)
1007            .max(mouse::Interaction::Idle)
1008    }
1009
1010    fn overlay<'c>(
1011        &'c mut self,
1012        layout: Layout<'c>,
1013        renderer: &Renderer,
1014    ) -> Option<overlay::Element<'c, Message, Theme, Renderer>> {
1015        let panel = layout.children().next()?;
1016        let viewport = layout.bounds();
1017        self.content
1018            .as_widget_mut()
1019            .overlay(self.tree, panel, renderer, &viewport, Vector::ZERO)
1020    }
1021}
1022
1023/// Whether any focusable widget in a subtree has focus.
1024#[derive(Default)]
1025struct FindFocus(bool);
1026
1027impl Operation for FindFocus {
1028    fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) {
1029        operate(self);
1030    }
1031
1032    fn focusable(&mut self, _id: Option<&Id>, _bounds: Rectangle, state: &mut dyn Focusable) {
1033        self.0 |= state.is_focused();
1034    }
1035}
1036
1037/// Takes focus away from every widget in a subtree.
1038struct Unfocus;
1039
1040impl Operation for Unfocus {
1041    fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) {
1042        operate(self);
1043    }
1044
1045    fn focusable(&mut self, _id: Option<&Id>, _bounds: Rectangle, state: &mut dyn Focusable) {
1046        if state.is_focused() {
1047            state.unfocus();
1048        }
1049    }
1050}
1051
1052#[cfg(test)]
1053mod tests {
1054    use super::*;
1055    use crate::theme::{dark, light};
1056    use iced::keyboard::{Key, Modifiers};
1057
1058    const VIEWPORT: Rectangle = Rectangle {
1059        x: 0.0,
1060        y: 0.0,
1061        width: 400.0,
1062        height: 300.0,
1063    };
1064
1065    fn anchor() -> Rectangle {
1066        Rectangle::new(Point::new(150.0, 100.0), Size::new(100.0, 40.0))
1067    }
1068
1069    const PANEL: Size = Size::new(60.0, 50.0);
1070
1071    fn at(side: Side, align: Align) -> Point {
1072        place(anchor(), PANEL, VIEWPORT, Placement::new(side, align)).position()
1073    }
1074
1075    #[test]
1076    fn defaults_to_bottom_centre_with_a_small_gap() {
1077        let placement = Placement::default();
1078        assert_eq!(placement.side, Side::Bottom);
1079        assert_eq!(placement.align, Align::Center);
1080        assert_eq!(placement.gap, GAP);
1081        assert_eq!(placement.offset, 0.0);
1082    }
1083
1084    #[test]
1085    fn each_side_sits_outside_the_anchor_with_the_gap() {
1086        assert_eq!(at(Side::Bottom, Align::Start), Point::new(150.0, 144.0));
1087        assert_eq!(at(Side::Top, Align::Start), Point::new(150.0, 46.0));
1088        assert_eq!(at(Side::Left, Align::Start), Point::new(86.0, 100.0));
1089        assert_eq!(at(Side::Right, Align::Start), Point::new(254.0, 100.0));
1090    }
1091
1092    #[test]
1093    fn alignment_moves_along_the_side() {
1094        assert_eq!(at(Side::Bottom, Align::Center).x, 170.0);
1095        assert_eq!(at(Side::Bottom, Align::End).x, 190.0);
1096        assert_eq!(at(Side::Right, Align::Center).y, 95.0);
1097        assert_eq!(at(Side::Right, Align::End).y, 90.0);
1098    }
1099
1100    #[test]
1101    fn offset_shifts_away_from_the_aligned_edge() {
1102        let placement = Placement::new(Side::Bottom, Align::Start).offset(5.0);
1103        let start = place(anchor(), PANEL, VIEWPORT, placement);
1104        assert_eq!(start.x, 155.0);
1105        let placement = Placement::new(Side::Bottom, Align::End).offset(5.0);
1106        let end = place(anchor(), PANEL, VIEWPORT, placement);
1107        assert_eq!(end.x, 185.0);
1108    }
1109
1110    #[test]
1111    fn flips_to_the_opposite_side_when_there_is_no_room() {
1112        let low = Rectangle::new(Point::new(150.0, 230.0), Size::new(100.0, 40.0));
1113        let panel = place(
1114            low,
1115            PANEL,
1116            VIEWPORT,
1117            Placement::new(Side::Bottom, Align::Start),
1118        );
1119        assert_eq!(panel.y, 230.0 - GAP - PANEL.height);
1120
1121        let left_edge = Rectangle::new(Point::new(20.0, 100.0), Size::new(40.0, 40.0));
1122        let panel = place(
1123            left_edge,
1124            PANEL,
1125            VIEWPORT,
1126            Placement::new(Side::Left, Align::Start),
1127        );
1128        assert_eq!(panel.x, 60.0 + GAP);
1129    }
1130
1131    #[test]
1132    fn stays_put_when_neither_side_fits_and_the_requested_one_is_larger() {
1133        let tall = Size::new(60.0, 290.0);
1134        let panel = place(
1135            anchor(),
1136            tall,
1137            VIEWPORT,
1138            Placement::new(Side::Bottom, Align::Start),
1139        );
1140        assert_eq!(panel.y, MARGIN, "shifted up to fit, pinned at the margin");
1141    }
1142
1143    #[test]
1144    fn fitted_height_keeps_a_tall_panel_off_its_anchor() {
1145        let below = Placement::new(Side::Bottom, Align::Start);
1146        assert_eq!(fitted_height(anchor(), PANEL, VIEWPORT, below), None);
1147        let tall = Size::new(60.0, 290.0);
1148        let room_below = VIEWPORT.height - MARGIN - (140.0 + GAP);
1149        assert_eq!(
1150            fitted_height(anchor(), tall, VIEWPORT, below),
1151            Some(room_below)
1152        );
1153        let panel = place(anchor(), Size::new(60.0, room_below), VIEWPORT, below);
1154        assert_eq!(panel.y, 140.0 + GAP, "stays below the anchor");
1155
1156        let low = Rectangle::new(Point::new(150.0, 200.0), Size::new(100.0, 40.0));
1157        let room_above = 200.0 - GAP - MARGIN;
1158        assert_eq!(fitted_height(low, tall, VIEWPORT, below), Some(room_above));
1159        let panel = place(low, Size::new(60.0, room_above), VIEWPORT, below);
1160        assert_eq!(panel.y + panel.height, 200.0 - GAP, "flips above it");
1161
1162        let beside = Placement::new(Side::Right, Align::Start);
1163        assert_eq!(fitted_height(anchor(), tall, VIEWPORT, beside), None);
1164    }
1165
1166    #[test]
1167    fn fitted_height_uses_the_roomier_side_however_little_room_it_has() {
1168        let below = Placement::new(Side::Bottom, Align::Start);
1169        let tall = Size::new(60.0, 290.0);
1170        let squeezed = Rectangle::new(Point::new(150.0, 100.0), Size::new(100.0, 120.0));
1171        let room_above = 100.0 - GAP - MARGIN;
1172        assert_eq!(
1173            fitted_height(squeezed, tall, VIEWPORT, below),
1174            Some(room_above)
1175        );
1176        let panel = place(squeezed, Size::new(60.0, room_above), VIEWPORT, below);
1177        assert_eq!(panel.y + panel.height, 100.0 - GAP, "clear of the anchor");
1178
1179        let filling = Rectangle::new(Point::new(0.0, 0.0), Size::new(400.0, 300.0));
1180        assert_eq!(fitted_height(filling, tall, VIEWPORT, below), Some(0.0));
1181    }
1182
1183    #[test]
1184    fn a_menu_low_in_the_window_opens_above_at_full_height() {
1185        let point = Rectangle::new(Point::new(100.0, 230.0), Size::ZERO);
1186        let menu = Size::new(180.0, 200.0);
1187        let placement = Placement::new(Side::Bottom, Align::Start).gap(0.0);
1188        assert_eq!(fitted_height(point, menu, VIEWPORT, placement), None);
1189        let panel = place(point, menu, VIEWPORT, placement);
1190        assert_eq!(panel.y + panel.height, 230.0, "flipped above the point");
1191    }
1192
1193    #[test]
1194    fn a_side_panel_that_fits_neither_side_moves_below_its_anchor() {
1195        let narrow = Rectangle::new(Point::new(0.0, 0.0), Size::new(320.0, 300.0));
1196        let row = Rectangle::new(Point::new(40.0, 100.0), Size::new(216.0, 32.0));
1197        let submenu = Size::new(224.0, 120.0);
1198        let placement = Placement::new(Side::Right, Align::Start).gap(4.0);
1199        let panel = place(row, submenu, narrow, placement);
1200        assert_eq!(panel.y, 132.0 + 4.0, "below the row, clear of its label");
1201        assert_eq!(panel.x, 40.0, "lined up with the row");
1202
1203        let low_row = Rectangle::new(Point::new(40.0, 200.0), Size::new(216.0, 32.0));
1204        let panel = place(low_row, submenu, narrow, placement);
1205        assert_eq!(panel.y + panel.height, 200.0 - 4.0, "above the row");
1206
1207        let wide = Rectangle::new(Point::new(0.0, 0.0), Size::new(600.0, 300.0));
1208        let panel = place(row, submenu, wide, placement);
1209        assert_eq!(panel.x, 256.0 + 4.0, "beside the row when it fits");
1210        assert_eq!(fitted_height(row, submenu, wide, placement), None);
1211    }
1212
1213    #[test]
1214    fn a_tall_side_panel_without_room_beside_scrolls_on_the_roomier_side() {
1215        let narrow = Rectangle::new(Point::new(0.0, 0.0), Size::new(320.0, 300.0));
1216        let row = Rectangle::new(Point::new(40.0, 160.0), Size::new(216.0, 32.0));
1217        let submenu = Size::new(224.0, 180.0);
1218        let placement = Placement::new(Side::Right, Align::Start).gap(4.0);
1219        assert_eq!(
1220            fitted_height(row, submenu, narrow, placement),
1221            Some(160.0 - 4.0 - MARGIN)
1222        );
1223        let fitted = Size::new(224.0, 160.0 - 4.0 - MARGIN);
1224        let panel = place(row, fitted, narrow, placement);
1225        assert_eq!(panel.y, MARGIN);
1226        assert_eq!(panel.y + panel.height, 160.0 - 4.0, "clear of the row");
1227    }
1228
1229    #[test]
1230    fn mirrors_the_alignment_when_it_would_overflow() {
1231        let right_edge = Rectangle::new(Point::new(360.0, 100.0), Size::new(30.0, 30.0));
1232        let panel = place(
1233            right_edge,
1234            PANEL,
1235            VIEWPORT,
1236            Placement::new(Side::Bottom, Align::Start),
1237        );
1238        assert_eq!(panel.x, 390.0 - PANEL.width);
1239    }
1240
1241    #[test]
1242    fn shifts_inside_the_margin_when_nothing_else_fits() {
1243        let corner = Rectangle::new(Point::new(0.0, 0.0), Size::new(10.0, 10.0));
1244        let panel = place(
1245            corner,
1246            PANEL,
1247            VIEWPORT,
1248            Placement::new(Side::Left, Align::Center),
1249        );
1250        assert_eq!(panel.x, 10.0 + GAP, "flipped to the right");
1251        assert_eq!(panel.y, MARGIN, "shifted down to the margin");
1252    }
1253
1254    #[test]
1255    fn a_list_near_the_bottom_keeps_the_margin() {
1256        let field = Rectangle::new(Point::new(20.0, 200.0), Size::new(200.0, 36.0));
1257        let list = Size::new(200.0, 80.0);
1258        let placement = Placement::new(Side::Bottom, Align::Start);
1259        let panel = place(field, list, VIEWPORT, placement);
1260        assert_eq!(panel.y, 200.0 - GAP - list.height, "flips above the field");
1261
1262        let wide = Size::new(420.0, 40.0);
1263        let panel = place(field, wide, VIEWPORT, placement);
1264        assert_eq!(panel.x, MARGIN, "clamped inside the window");
1265    }
1266
1267    #[test]
1268    fn a_point_anchor_opens_below_and_right_then_flips_near_edges() {
1269        let point = |x, y| Rectangle::new(Point::new(x, y), Size::ZERO);
1270        let placement = Placement::new(Side::Bottom, Align::Start).gap(0.0);
1271        assert_eq!(
1272            place(point(100.0, 100.0), PANEL, VIEWPORT, placement).position(),
1273            Point::new(100.0, 100.0)
1274        );
1275        assert_eq!(
1276            place(point(380.0, 280.0), PANEL, VIEWPORT, placement).position(),
1277            Point::new(320.0, 230.0)
1278        );
1279    }
1280
1281    #[test]
1282    fn a_panel_larger_than_the_window_is_pinned_top_left() {
1283        let huge = Size::new(1000.0, 1000.0);
1284        let panel = place(anchor(), huge, VIEWPORT, Placement::default());
1285        assert_eq!(panel.position(), Point::new(MARGIN, MARGIN));
1286        assert_eq!(panel.size(), huge);
1287    }
1288
1289    #[test]
1290    fn sides_and_alignments_mirror() {
1291        for side in Side::ALL {
1292            assert_eq!(side.opposite().opposite(), side);
1293            assert_ne!(side.opposite(), side);
1294        }
1295        assert_eq!(Align::Center.mirrored(), Align::Center);
1296        assert_eq!(Align::Start.mirrored(), Align::End);
1297    }
1298
1299    #[test]
1300    fn surface_uses_the_popover_colour_a_border_and_a_shadow() {
1301        for theme in [light(), dark()] {
1302            let tokens = Tokens::of(&theme);
1303            let style = surface_style(&tokens);
1304            assert_eq!(style.background, Some(Background::Color(tokens.popover)));
1305            assert_eq!(style.text_color, Some(tokens.foreground));
1306            assert_eq!(style.border.color, tokens.border);
1307            assert_eq!(style.border.width, 1.0);
1308            assert_eq!(style.border.radius, radius::MD.into());
1309            assert_eq!(style.shadow, shadow(&tokens));
1310            assert_eq!(style.shadow.offset, Vector::new(0.0, 4.0));
1311            assert_eq!(style.shadow.blur_radius, 12.0);
1312        }
1313        let light = shadow(&Tokens::of(&light())).color.a;
1314        let dark = shadow(&Tokens::of(&dark())).color.a;
1315        assert!((light - 0.1).abs() < 1e-6);
1316        assert!((dark - 0.5).abs() < 1e-6);
1317    }
1318
1319    #[test]
1320    fn builder_starts_closed_and_undismissable_with_escape_bound() {
1321        let anchored: Anchored<'_, ()> = anchored(iced::widget::text("Anchor"));
1322        let behaviour = &anchored.behaviour;
1323        assert!(anchored.content.is_none());
1324        assert!(behaviour.on_dismiss.is_none());
1325        assert!(behaviour.point.is_none());
1326        assert!(!behaviour.dismiss_on_anchor_press);
1327        assert!(!behaviour.dismiss_on_blur);
1328        assert!(!behaviour.pass_through);
1329        assert!(!behaviour.match_width);
1330        assert_eq!(behaviour.dismiss_keys, vec![Chord::named(Named::Escape)]);
1331        assert!(!behaviour.can_dismiss());
1332        assert!(!behaviour.watches_focus());
1333    }
1334
1335    #[test]
1336    fn builder_sets_every_option() {
1337        let anchored: Anchored<'_, u8> = anchored(iced::widget::text("Anchor"))
1338            .at(Point::new(3.0, 4.0))
1339            .match_width(true)
1340            .on_dismiss(1)
1341            .dismiss_keys([Chord::character('q')])
1342            .dismiss_on_anchor_press(true)
1343            .dismiss_on_blur(true)
1344            .pass_through(true)
1345            .on_anchor_press_maybe(Some(2))
1346            .on_key(|_| Some(3))
1347            .on_focus(|focused| if focused { 4 } else { 5 });
1348        let behaviour = &anchored.behaviour;
1349        assert_eq!(behaviour.focus_message(false, true), Some(4));
1350        assert_eq!(behaviour.focus_message(true, false), Some(5));
1351        assert_eq!(behaviour.focus_message(true, true), None);
1352        assert_eq!(behaviour.point, Some(Point::new(3.0, 4.0)));
1353        assert!(behaviour.match_width && behaviour.pass_through);
1354        assert!(behaviour.dismiss_on_anchor_press && behaviour.dismiss_on_blur);
1355        assert_eq!(behaviour.on_dismiss, Some(1));
1356        assert_eq!(behaviour.on_anchor_press, Some(2));
1357        assert_eq!(behaviour.dismiss_keys, vec![Chord::character('q')]);
1358        assert!(behaviour.can_dismiss() && behaviour.watches_focus());
1359    }
1360
1361    fn key_press(key: Key, modifiers: Modifiers) -> Event {
1362        Event::Keyboard(keyboard::Event::KeyPressed {
1363            key: key.clone(),
1364            modified_key: key,
1365            physical_key: keyboard::key::Physical::Unidentified(
1366                keyboard::key::NativeCode::Unidentified,
1367            ),
1368            location: keyboard::Location::Standard,
1369            modifiers,
1370            text: None,
1371            repeat: false,
1372        })
1373    }
1374
1375    #[test]
1376    fn keys_resolve_through_on_key_then_the_dismiss_chords() {
1377        let behaviour: Behaviour<'_, u8> = Behaviour {
1378            on_key: Some(Box::new(|key: &keys::Event| {
1379                (key.key == Key::Named(Named::ArrowDown)).then_some(7)
1380            })),
1381            ..Behaviour::default()
1382        };
1383        let down = key_press(Key::Named(Named::ArrowDown), Modifiers::empty());
1384        let escape = key_press(Key::Named(Named::Escape), Modifiers::empty());
1385        let shift_escape = key_press(Key::Named(Named::Escape), Modifiers::SHIFT);
1386        assert_eq!(key_message(&behaviour, &down), Some(7));
1387        assert_eq!(key_message(&behaviour, &escape), None);
1388        assert!(is_dismiss_key(&behaviour, &escape));
1389        assert!(!is_dismiss_key(&behaviour, &shift_escape));
1390        assert!(!is_dismiss_key(&behaviour, &down));
1391
1392        let unbound: Behaviour<'_, u8> = Behaviour {
1393            dismiss_keys: Vec::new(),
1394            ..Behaviour::default()
1395        };
1396        assert!(!is_dismiss_key(&unbound, &escape));
1397    }
1398
1399    #[test]
1400    fn press_position_only_reports_presses() {
1401        let cursor = mouse::Cursor::Available(Point::new(3.0, 4.0));
1402        let pressed = Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right));
1403        let released = Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left));
1404        assert_eq!(press_position(&pressed, cursor), Some(Point::new(3.0, 4.0)));
1405        assert_eq!(press_position(&released, cursor), None);
1406        assert_eq!(press_position(&pressed, mouse::Cursor::Unavailable), None);
1407        assert!(is_pointer_input(&released));
1408        assert!(is_release(&released));
1409        assert!(is_press(&pressed));
1410        assert!(!is_release(&pressed));
1411        assert!(!is_pointer_input(&Event::Mouse(mouse::Event::CursorLeft)));
1412    }
1413}