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. Whatever still overflows is shifted back
146/// inside, pinned to the top left if the panel is larger than the window.
147pub fn place(
148    anchor: Rectangle,
149    size: Size,
150    viewport: Rectangle,
151    placement: Placement,
152) -> Rectangle {
153    let area = viewport.shrink(MARGIN);
154    let room = |side: Side| match side {
155        Side::Top => anchor.y - placement.gap - area.y,
156        Side::Bottom => area.y + area.height - (anchor.y + anchor.height + placement.gap),
157        Side::Left => anchor.x - placement.gap - area.x,
158        Side::Right => area.x + area.width - (anchor.x + anchor.width + placement.gap),
159    };
160    let needed = |side: Side| {
161        if side.is_vertical() {
162            size.height
163        } else {
164            size.width
165        }
166    };
167
168    let requested = placement.side;
169    let opposite = requested.opposite();
170    let side = if room(requested) >= needed(requested) {
171        requested
172    } else if room(opposite) >= needed(opposite) || room(opposite) > room(requested) {
173        opposite
174    } else {
175        requested
176    };
177
178    let main = match side {
179        Side::Top => anchor.y - placement.gap - size.height,
180        Side::Bottom => anchor.y + anchor.height + placement.gap,
181        Side::Left => anchor.x - placement.gap - size.width,
182        Side::Right => anchor.x + anchor.width + placement.gap,
183    };
184
185    let (start, length, extent, low, high) = if side.is_vertical() {
186        (
187            anchor.x,
188            anchor.width,
189            size.width,
190            area.x,
191            area.x + area.width,
192        )
193    } else {
194        (
195            anchor.y,
196            anchor.height,
197            size.height,
198            area.y,
199            area.y + area.height,
200        )
201    };
202    let cross_at = |align: Align| match align {
203        Align::Start => start + placement.offset,
204        Align::Center => start + (length - extent) / 2.0 + placement.offset,
205        Align::End => start + length - extent - placement.offset,
206    };
207    let fits = |value: f32| value >= low && value + extent <= high;
208    let preferred = cross_at(placement.align);
209    let mirrored = cross_at(placement.align.mirrored());
210    let cross = if !fits(preferred) && fits(mirrored) {
211        mirrored
212    } else {
213        preferred
214    };
215
216    let (x, y) = if side.is_vertical() {
217        (cross, main)
218    } else {
219        (main, cross)
220    };
221    Rectangle::new(
222        Point::new(
223            clamp(x, area.x, area.x + area.width - size.width),
224            clamp(y, area.y, area.y + area.height - size.height),
225        ),
226        size,
227    )
228}
229
230/// Keeps `value` within `low..=high`, preferring `low` when the range is empty.
231fn clamp(value: f32, low: f32, high: f32) -> f32 {
232    value.min(high).max(low)
233}
234
235/// The floating surface shared by popovers, menus, lists and dialogs: the
236/// popover colour, a subtle border and a restrained shadow.
237pub fn surface_style(tokens: &Tokens) -> container::Style {
238    container::Style {
239        background: Some(Background::Color(tokens.popover)),
240        text_color: Some(tokens.foreground),
241        border: Border {
242            color: tokens.border,
243            width: 1.0,
244            radius: radius::MD.into(),
245        },
246        shadow: shadow(tokens),
247        snap: true,
248    }
249}
250
251/// The shadow under every floating surface, stronger on a dark page.
252pub fn shadow(tokens: &Tokens) -> Shadow {
253    Shadow {
254        color: fade(Color::BLACK, if tokens.is_dark { 0.5 } else { 0.1 }),
255        offset: Vector::new(0.0, 4.0),
256        blur_radius: 12.0,
257    }
258}
259
260type OnKey<'a, Message> = Box<dyn Fn(&keys::Event) -> Option<Message> + 'a>;
261
262/// How an open panel behaves. Shared with the widgets in this crate that
263/// float a panel of their own.
264pub(crate) struct Behaviour<'a, Message> {
265    pub(crate) placement: Placement,
266    pub(crate) point: Option<Point>,
267    pub(crate) match_width: bool,
268    pub(crate) on_dismiss: Option<Message>,
269    pub(crate) dismiss_keys: Vec<Chord>,
270    pub(crate) dismiss_on_anchor_press: bool,
271    pub(crate) dismiss_on_blur: bool,
272    pub(crate) pass_through: bool,
273    /// The panel opens and closes itself through [`State::open`]: a
274    /// dismissal or a press the content captured closes it.
275    pub(crate) closes_itself: bool,
276    pub(crate) on_key: Option<OnKey<'a, Message>>,
277    pub(crate) on_anchor_press: Option<Message>,
278    pub(crate) on_focus: Option<OnFocus<'a, Message>>,
279}
280
281type OnFocus<'a, Message> = Box<dyn Fn(bool) -> Message + 'a>;
282
283impl<Message> Default for Behaviour<'_, Message> {
284    fn default() -> Self {
285        Self {
286            placement: Placement::default(),
287            point: None,
288            match_width: false,
289            on_dismiss: None,
290            dismiss_keys: vec![Chord::named(Named::Escape)],
291            dismiss_on_anchor_press: false,
292            dismiss_on_blur: false,
293            pass_through: false,
294            closes_itself: false,
295            on_key: None,
296            on_anchor_press: None,
297            on_focus: None,
298        }
299    }
300}
301
302impl<Message> Behaviour<'_, Message> {
303    fn can_dismiss(&self) -> bool {
304        self.on_dismiss.is_some() || self.closes_itself
305    }
306
307    fn watches_focus(&self) -> bool {
308        self.on_key.is_some() || self.dismiss_on_blur || self.on_focus.is_some()
309    }
310
311    /// The message for a change of focus inside the anchor, if it changed
312    /// and anyone listens.
313    fn focus_message(&self, was: bool, is: bool) -> Option<Message> {
314        if was == is {
315            return None;
316        }
317        self.on_focus.as_ref().map(|on_focus| on_focus(is))
318    }
319}
320
321/// What an anchor remembers between events.
322#[derive(Debug, Default)]
323pub(crate) struct State {
324    focused: bool,
325    /// An outside press dismissed the panel, so the anchor gives up focus
326    /// on the next event, without a second dismissal.
327    unfocus: bool,
328    /// Whether a panel that [closes itself](Behaviour::closes_itself) is open.
329    pub(crate) open: bool,
330}
331
332/// An anchor with a panel floating next to it. Convert it into an
333/// [`Element`] to render.
334pub struct Anchored<'a, Message> {
335    anchor: Element<'a, Message>,
336    content: Option<Element<'a, Message>>,
337    behaviour: Behaviour<'a, Message>,
338}
339
340impl<Message> std::fmt::Debug for Anchored<'_, Message> {
341    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
342        let behaviour = &self.behaviour;
343        f.debug_struct("Anchored")
344            .field("open", &self.content.is_some())
345            .field("placement", &behaviour.placement)
346            .field("point", &behaviour.point)
347            .field("match_width", &behaviour.match_width)
348            .field("dismissable", &behaviour.on_dismiss.is_some())
349            .field("dismiss_keys", &behaviour.dismiss_keys)
350            .field("pass_through", &behaviour.pass_through)
351            .finish_non_exhaustive()
352    }
353}
354
355/// Wraps `anchor`. Nothing floats until [`content`](Anchored::content) is set.
356pub fn anchored<'a, Message>(anchor: impl Into<Element<'a, Message>>) -> Anchored<'a, Message> {
357    Anchored {
358        anchor: anchor.into(),
359        content: None,
360        behaviour: Behaviour::default(),
361    }
362}
363
364impl<'a, Message> Anchored<'a, Message> {
365    /// The floating panel. `None` keeps it closed.
366    pub fn content(mut self, content: Option<Element<'a, Message>>) -> Self {
367        self.content = content;
368        self
369    }
370
371    pub fn placement(mut self, placement: Placement) -> Self {
372        self.behaviour.placement = placement;
373        self
374    }
375
376    /// Anchors the panel to a point, relative to the anchor's top left
377    /// corner, instead of to the anchor's edges.
378    pub fn at(mut self, point: Point) -> Self {
379        self.behaviour.point = Some(point);
380        self
381    }
382
383    /// Makes the panel exactly as wide as the anchor, as a list under a
384    /// field is.
385    pub fn match_width(mut self, match_width: bool) -> Self {
386        self.behaviour.match_width = match_width;
387        self
388    }
389
390    /// Sent once when the panel is dismissed; see the module docs.
391    pub fn on_dismiss(self, message: Message) -> Self {
392        self.on_dismiss_maybe(Some(message))
393    }
394
395    /// Like [`on_dismiss`](Self::on_dismiss); `None` makes the panel
396    /// undismissable, so presses outside it pass through.
397    pub fn on_dismiss_maybe(mut self, message: Option<Message>) -> Self {
398        self.behaviour.on_dismiss = message;
399        self
400    }
401
402    /// The chords that dismiss the panel when the content and
403    /// [`on_key`](Self::on_key) leave them alone. Defaults to Escape. Pass
404    /// the chords of a component keymap's close action, so unbinding them
405    /// there works.
406    pub fn dismiss_keys(mut self, chords: impl IntoIterator<Item = Chord>) -> Self {
407        self.behaviour.dismiss_keys = chords.into_iter().collect();
408        self
409    }
410
411    /// Also dismiss on presses on the anchor itself, for anchors that do
412    /// not toggle the panel, such as a context menu area. The press still
413    /// reaches the anchor.
414    pub fn dismiss_on_anchor_press(mut self, dismiss: bool) -> Self {
415        self.behaviour.dismiss_on_anchor_press = dismiss;
416        self
417    }
418
419    /// Also dismiss when a focused widget inside the anchor, such as a text
420    /// field, loses focus.
421    pub fn dismiss_on_blur(mut self, dismiss: bool) -> Self {
422        self.behaviour.dismiss_on_blur = dismiss;
423        self
424    }
425
426    /// Lets a press outside the panel reach the widgets underneath after
427    /// dismissing it, for panels that are not modal, such as a popover.
428    pub fn pass_through(mut self, pass_through: bool) -> Self {
429        self.behaviour.pass_through = pass_through;
430        self
431    }
432
433    /// Resolves key presses the content leaves, while the panel is open or
434    /// something in the anchor has focus. Returning a message sends it and
435    /// captures the key.
436    pub fn on_key(mut self, on_key: impl Fn(&keys::Event) -> Option<Message> + 'a) -> Self {
437        self.behaviour.on_key = Some(Box::new(on_key));
438        self
439    }
440
441    /// Sent with `true` when a widget inside the anchor, such as a text
442    /// field, gains focus, and with `false` when it loses it.
443    pub fn on_focus(mut self, on_focus: impl Fn(bool) -> Message + 'a) -> Self {
444        self.behaviour.on_focus = Some(Box::new(on_focus));
445        self
446    }
447
448    /// Sent when the pointer is pressed on the anchor, before the anchor
449    /// sees the press.
450    pub fn on_anchor_press_maybe(mut self, message: Option<Message>) -> Self {
451        self.behaviour.on_anchor_press = message;
452        self
453    }
454}
455
456impl<'a, Message: Clone + 'a> From<Anchored<'a, Message>> for Element<'a, Message> {
457    fn from(anchored: Anchored<'a, Message>) -> Self {
458        Element::new(anchored)
459    }
460}
461
462impl<Message: Clone> Widget<Message, Theme, Renderer> for Anchored<'_, Message> {
463    fn tag(&self) -> tree::Tag {
464        tree::Tag::of::<State>()
465    }
466
467    fn state(&self) -> tree::State {
468        tree::State::new(State::default())
469    }
470
471    fn size(&self) -> Size<Length> {
472        self.anchor.as_widget().size()
473    }
474
475    fn size_hint(&self) -> Size<Length> {
476        self.anchor.as_widget().size_hint()
477    }
478
479    fn children(&self) -> Vec<Tree> {
480        std::iter::once(&self.anchor)
481            .chain(&self.content)
482            .map(Tree::new)
483            .collect()
484    }
485
486    fn diff(&self, tree: &mut Tree) {
487        match &self.content {
488            Some(content) => tree.diff_children(&[&self.anchor, content]),
489            None => tree.diff_children(&[&self.anchor]),
490        }
491    }
492
493    fn layout(
494        &mut self,
495        tree: &mut Tree,
496        renderer: &Renderer,
497        limits: &layout::Limits,
498    ) -> layout::Node {
499        self.anchor
500            .as_widget_mut()
501            .layout(&mut tree.children[0], renderer, limits)
502    }
503
504    fn draw(
505        &self,
506        tree: &Tree,
507        renderer: &mut Renderer,
508        theme: &Theme,
509        style: &renderer::Style,
510        layout: Layout<'_>,
511        cursor: mouse::Cursor,
512        viewport: &Rectangle,
513    ) {
514        self.anchor.as_widget().draw(
515            &tree.children[0],
516            renderer,
517            theme,
518            style,
519            layout,
520            cursor,
521            viewport,
522        );
523    }
524
525    fn operate(
526        &mut self,
527        tree: &mut Tree,
528        layout: Layout<'_>,
529        renderer: &Renderer,
530        operation: &mut dyn Operation,
531    ) {
532        self.anchor
533            .as_widget_mut()
534            .operate(&mut tree.children[0], layout, renderer, operation);
535    }
536
537    fn update(
538        &mut self,
539        tree: &mut Tree,
540        event: &Event,
541        layout: Layout<'_>,
542        cursor: mouse::Cursor,
543        renderer: &Renderer,
544        clipboard: &mut dyn Clipboard,
545        shell: &mut Shell<'_, Message>,
546        viewport: &Rectangle,
547    ) {
548        let Tree {
549            state, children, ..
550        } = tree;
551        let state = state.downcast_mut::<State>();
552        let Some(anchor_tree) = children.first_mut() else {
553            return;
554        };
555        let behaviour = &self.behaviour;
556
557        if state.unfocus {
558            state.unfocus = false;
559            if let Some(message) = behaviour.focus_message(state.focused, false) {
560                shell.publish(message);
561            }
562            state.focused = false;
563            self.anchor
564                .as_widget_mut()
565                .operate(anchor_tree, layout, renderer, &mut Unfocus);
566            shell.request_redraw();
567        }
568
569        if state.focused
570            && let Some(message) = key_message(behaviour, event)
571        {
572            shell.publish(message);
573            shell.capture_event();
574            return;
575        }
576
577        if is_press(event)
578            && cursor.is_over(layout.bounds())
579            && let Some(message) = &behaviour.on_anchor_press
580        {
581            shell.publish(message.clone());
582        }
583
584        self.anchor.as_widget_mut().update(
585            anchor_tree,
586            event,
587            layout,
588            cursor,
589            renderer,
590            clipboard,
591            shell,
592            viewport,
593        );
594
595        if !behaviour.watches_focus() {
596            return;
597        }
598        let mut focus = FindFocus::default();
599        self.anchor
600            .as_widget_mut()
601            .operate(anchor_tree, layout, renderer, &mut focus);
602        if state.focused
603            && !focus.0
604            && behaviour.dismiss_on_blur
605            && let Some(message) = &behaviour.on_dismiss
606        {
607            shell.publish(message.clone());
608        }
609        if let Some(message) = behaviour.focus_message(state.focused, focus.0) {
610            shell.publish(message);
611        }
612        state.focused = focus.0;
613    }
614
615    fn mouse_interaction(
616        &self,
617        tree: &Tree,
618        layout: Layout<'_>,
619        cursor: mouse::Cursor,
620        viewport: &Rectangle,
621        renderer: &Renderer,
622    ) -> mouse::Interaction {
623        self.anchor.as_widget().mouse_interaction(
624            &tree.children[0],
625            layout,
626            cursor,
627            viewport,
628            renderer,
629        )
630    }
631
632    fn overlay<'b>(
633        &'b mut self,
634        tree: &'b mut Tree,
635        layout: Layout<'b>,
636        renderer: &Renderer,
637        viewport: &Rectangle,
638        translation: Vector,
639    ) -> Option<overlay::Element<'b, Message, Theme, Renderer>> {
640        let Self {
641            anchor,
642            content,
643            behaviour,
644        } = self;
645        let Tree {
646            state, children, ..
647        } = tree;
648        let mut trees = children.iter_mut();
649        let anchor_tree = trees.next()?;
650        let trigger = layout.bounds() + translation;
651
652        // While the panel is open the anchor's own overlays, such as its
653        // tooltip, stay hidden.
654        let Some((content, tree)) = content.as_mut().zip(trees.next()) else {
655            return anchor.as_widget_mut().overlay(
656                anchor_tree,
657                layout,
658                renderer,
659                viewport,
660                translation,
661            );
662        };
663        Some(floating(
664            content,
665            tree,
666            state.downcast_mut::<State>(),
667            behaviour,
668            trigger,
669        ))
670    }
671}
672
673/// The overlay drawing a panel for `trigger`, the anchor's bounds in
674/// window coordinates.
675pub(crate) fn floating<'a, 'b, Message: Clone>(
676    content: &'b mut Element<'a, Message>,
677    tree: &'b mut Tree,
678    state: &'b mut State,
679    behaviour: &'b Behaviour<'a, Message>,
680    trigger: Rectangle,
681) -> overlay::Element<'b, Message, Theme, Renderer> {
682    let target = match behaviour.point {
683        Some(point) => Rectangle::new(
684            trigger.position() + Vector::new(point.x, point.y),
685            Size::ZERO,
686        ),
687        None => trigger,
688    };
689    overlay::Element::new(Box::new(Floating {
690        content,
691        tree,
692        state,
693        behaviour,
694        target,
695        trigger,
696    }))
697}
698
699/// The overlay drawing the panel. Its node covers the whole window so the
700/// panel's shadow is never clipped; the panel is its only child.
701struct Floating<'a, 'b, Message> {
702    content: &'b mut Element<'a, Message>,
703    tree: &'b mut Tree,
704    state: &'b mut State,
705    behaviour: &'b Behaviour<'a, Message>,
706    target: Rectangle,
707    trigger: Rectangle,
708}
709
710impl<Message: Clone> Floating<'_, '_, Message> {
711    fn dismiss(&mut self, shell: &mut Shell<'_, Message>) {
712        if self.behaviour.closes_itself {
713            self.state.open = false;
714            shell.request_redraw();
715        }
716        if self.behaviour.dismiss_on_blur {
717            self.state.unfocus = true;
718        }
719        if let Some(message) = &self.behaviour.on_dismiss {
720            shell.publish(message.clone());
721        }
722    }
723}
724
725/// The message [`Behaviour::on_key`] sends for a key press, if any.
726fn key_message<Message>(behaviour: &Behaviour<'_, Message>, event: &Event) -> Option<Message> {
727    let Event::Keyboard(keyboard::Event::KeyPressed { key, modifiers, .. }) = event else {
728        return None;
729    };
730    let on_key = behaviour.on_key.as_ref()?;
731    on_key(&keys::Event {
732        key: key.clone(),
733        modifiers: *modifiers,
734    })
735}
736
737fn is_dismiss_key<Message>(behaviour: &Behaviour<'_, Message>, event: &Event) -> bool {
738    let Event::Keyboard(keyboard::Event::KeyPressed { key, modifiers, .. }) = event else {
739        return false;
740    };
741    behaviour
742        .dismiss_keys
743        .iter()
744        .any(|chord| chord.matches(key, *modifiers))
745}
746
747fn is_press(event: &Event) -> bool {
748    matches!(
749        event,
750        Event::Mouse(mouse::Event::ButtonPressed(_))
751            | Event::Touch(touch::Event::FingerPressed { .. })
752    )
753}
754
755fn is_release(event: &Event) -> bool {
756    matches!(
757        event,
758        Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left))
759            | Event::Touch(touch::Event::FingerLifted { .. })
760    )
761}
762
763/// Where a mouse or touch press landed, if the event is one.
764fn press_position(event: &Event, cursor: mouse::Cursor) -> Option<Point> {
765    match event {
766        Event::Mouse(mouse::Event::ButtonPressed(_)) => cursor.position(),
767        Event::Touch(touch::Event::FingerPressed { position, .. }) => Some(*position),
768        _ => None,
769    }
770}
771
772fn is_pointer_input(event: &Event) -> bool {
773    matches!(
774        event,
775        Event::Mouse(
776            mouse::Event::ButtonPressed(_)
777                | mouse::Event::ButtonReleased(_)
778                | mouse::Event::WheelScrolled { .. }
779        ) | Event::Touch(_)
780    )
781}
782
783impl<Message: Clone> overlay::Overlay<Message, Theme, Renderer> for Floating<'_, '_, Message> {
784    fn layout(&mut self, renderer: &Renderer, bounds: Size) -> layout::Node {
785        let viewport = Rectangle::with_size(bounds);
786        let room = viewport.shrink(MARGIN).size();
787        let room = Size::new(room.width.max(0.0), room.height.max(0.0));
788        let limits = if self.behaviour.match_width {
789            let width = self.trigger.width.min(room.width);
790            layout::Limits::new(Size::new(width, 0.0), Size::new(width, room.height))
791        } else {
792            layout::Limits::new(Size::ZERO, room)
793        };
794        let node = self
795            .content
796            .as_widget_mut()
797            .layout(self.tree, renderer, &limits);
798        let panel = place(self.target, node.size(), viewport, self.behaviour.placement);
799        layout::Node::with_children(bounds, vec![node.move_to(panel.position())])
800    }
801
802    fn draw(
803        &self,
804        renderer: &mut Renderer,
805        theme: &Theme,
806        style: &renderer::Style,
807        layout: Layout<'_>,
808        cursor: mouse::Cursor,
809    ) {
810        let Some(panel) = layout.children().next() else {
811            return;
812        };
813        self.content.as_widget().draw(
814            self.tree,
815            renderer,
816            theme,
817            style,
818            panel,
819            cursor,
820            &layout.bounds(),
821        );
822    }
823
824    fn operate(&mut self, layout: Layout<'_>, renderer: &Renderer, operation: &mut dyn Operation) {
825        let Some(panel) = layout.children().next() else {
826            return;
827        };
828        self.content
829            .as_widget_mut()
830            .operate(self.tree, panel, renderer, operation);
831    }
832
833    fn update(
834        &mut self,
835        event: &Event,
836        layout: Layout<'_>,
837        cursor: mouse::Cursor,
838        renderer: &Renderer,
839        clipboard: &mut dyn Clipboard,
840        shell: &mut Shell<'_, Message>,
841    ) {
842        let Some(panel) = layout.children().next() else {
843            return;
844        };
845
846        self.content.as_widget_mut().update(
847            self.tree,
848            event,
849            panel,
850            cursor,
851            renderer,
852            clipboard,
853            shell,
854            &layout.bounds(),
855        );
856        if shell.is_event_captured() {
857            // A row took the click, so the choice is made.
858            if self.behaviour.closes_itself && is_release(event) {
859                self.state.open = false;
860                shell.request_redraw();
861            }
862            return;
863        }
864
865        if let Some(message) = key_message(self.behaviour, event) {
866            shell.publish(message);
867            shell.capture_event();
868            return;
869        }
870        if is_dismiss_key(self.behaviour, event) && self.behaviour.can_dismiss() {
871            self.dismiss(shell);
872            shell.capture_event();
873            return;
874        }
875        if !is_pointer_input(event) {
876            return;
877        }
878
879        let bounds = panel.bounds();
880        if cursor.is_over(bounds) {
881            shell.capture_event();
882            return;
883        }
884        let Some(position) = press_position(event, cursor) else {
885            return;
886        };
887        if bounds.contains(position) {
888            shell.capture_event();
889            return;
890        }
891        let on_anchor = self.trigger.contains(position);
892        if (on_anchor && !self.behaviour.dismiss_on_anchor_press) || !self.behaviour.can_dismiss() {
893            return;
894        }
895        self.dismiss(shell);
896        if !on_anchor && !self.behaviour.pass_through {
897            shell.capture_event();
898        }
899    }
900
901    fn mouse_interaction(
902        &self,
903        layout: Layout<'_>,
904        cursor: mouse::Cursor,
905        renderer: &Renderer,
906    ) -> mouse::Interaction {
907        let Some(panel) = layout.children().next() else {
908            return mouse::Interaction::None;
909        };
910        if !cursor.is_over(panel.bounds()) {
911            return mouse::Interaction::None;
912        }
913        self.content
914            .as_widget()
915            .mouse_interaction(self.tree, panel, cursor, &layout.bounds(), renderer)
916            .max(mouse::Interaction::Idle)
917    }
918
919    fn overlay<'c>(
920        &'c mut self,
921        layout: Layout<'c>,
922        renderer: &Renderer,
923    ) -> Option<overlay::Element<'c, Message, Theme, Renderer>> {
924        let panel = layout.children().next()?;
925        let viewport = layout.bounds();
926        self.content
927            .as_widget_mut()
928            .overlay(self.tree, panel, renderer, &viewport, Vector::ZERO)
929    }
930}
931
932/// Whether any focusable widget in a subtree has focus.
933#[derive(Default)]
934struct FindFocus(bool);
935
936impl Operation for FindFocus {
937    fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) {
938        operate(self);
939    }
940
941    fn focusable(&mut self, _id: Option<&Id>, _bounds: Rectangle, state: &mut dyn Focusable) {
942        self.0 |= state.is_focused();
943    }
944}
945
946/// Takes focus away from every widget in a subtree.
947struct Unfocus;
948
949impl Operation for Unfocus {
950    fn traverse(&mut self, operate: &mut dyn FnMut(&mut dyn Operation)) {
951        operate(self);
952    }
953
954    fn focusable(&mut self, _id: Option<&Id>, _bounds: Rectangle, state: &mut dyn Focusable) {
955        if state.is_focused() {
956            state.unfocus();
957        }
958    }
959}
960
961#[cfg(test)]
962mod tests {
963    use super::*;
964    use crate::theme::{dark, light};
965    use iced::keyboard::{Key, Modifiers};
966
967    const VIEWPORT: Rectangle = Rectangle {
968        x: 0.0,
969        y: 0.0,
970        width: 400.0,
971        height: 300.0,
972    };
973
974    fn anchor() -> Rectangle {
975        Rectangle::new(Point::new(150.0, 100.0), Size::new(100.0, 40.0))
976    }
977
978    const PANEL: Size = Size::new(60.0, 50.0);
979
980    fn at(side: Side, align: Align) -> Point {
981        place(anchor(), PANEL, VIEWPORT, Placement::new(side, align)).position()
982    }
983
984    #[test]
985    fn defaults_to_bottom_centre_with_a_small_gap() {
986        let placement = Placement::default();
987        assert_eq!(placement.side, Side::Bottom);
988        assert_eq!(placement.align, Align::Center);
989        assert_eq!(placement.gap, GAP);
990        assert_eq!(placement.offset, 0.0);
991    }
992
993    #[test]
994    fn each_side_sits_outside_the_anchor_with_the_gap() {
995        assert_eq!(at(Side::Bottom, Align::Start), Point::new(150.0, 144.0));
996        assert_eq!(at(Side::Top, Align::Start), Point::new(150.0, 46.0));
997        assert_eq!(at(Side::Left, Align::Start), Point::new(86.0, 100.0));
998        assert_eq!(at(Side::Right, Align::Start), Point::new(254.0, 100.0));
999    }
1000
1001    #[test]
1002    fn alignment_moves_along_the_side() {
1003        assert_eq!(at(Side::Bottom, Align::Center).x, 170.0);
1004        assert_eq!(at(Side::Bottom, Align::End).x, 190.0);
1005        assert_eq!(at(Side::Right, Align::Center).y, 95.0);
1006        assert_eq!(at(Side::Right, Align::End).y, 90.0);
1007    }
1008
1009    #[test]
1010    fn offset_shifts_away_from_the_aligned_edge() {
1011        let placement = Placement::new(Side::Bottom, Align::Start).offset(5.0);
1012        let start = place(anchor(), PANEL, VIEWPORT, placement);
1013        assert_eq!(start.x, 155.0);
1014        let placement = Placement::new(Side::Bottom, Align::End).offset(5.0);
1015        let end = place(anchor(), PANEL, VIEWPORT, placement);
1016        assert_eq!(end.x, 185.0);
1017    }
1018
1019    #[test]
1020    fn flips_to_the_opposite_side_when_there_is_no_room() {
1021        let low = Rectangle::new(Point::new(150.0, 230.0), Size::new(100.0, 40.0));
1022        let panel = place(
1023            low,
1024            PANEL,
1025            VIEWPORT,
1026            Placement::new(Side::Bottom, Align::Start),
1027        );
1028        assert_eq!(panel.y, 230.0 - GAP - PANEL.height);
1029
1030        let left_edge = Rectangle::new(Point::new(20.0, 100.0), Size::new(40.0, 40.0));
1031        let panel = place(
1032            left_edge,
1033            PANEL,
1034            VIEWPORT,
1035            Placement::new(Side::Left, Align::Start),
1036        );
1037        assert_eq!(panel.x, 60.0 + GAP);
1038    }
1039
1040    #[test]
1041    fn stays_put_when_neither_side_fits_and_the_requested_one_is_larger() {
1042        let tall = Size::new(60.0, 290.0);
1043        let panel = place(
1044            anchor(),
1045            tall,
1046            VIEWPORT,
1047            Placement::new(Side::Bottom, Align::Start),
1048        );
1049        assert_eq!(panel.y, MARGIN, "shifted up to fit, pinned at the margin");
1050    }
1051
1052    #[test]
1053    fn mirrors_the_alignment_when_it_would_overflow() {
1054        let right_edge = Rectangle::new(Point::new(360.0, 100.0), Size::new(30.0, 30.0));
1055        let panel = place(
1056            right_edge,
1057            PANEL,
1058            VIEWPORT,
1059            Placement::new(Side::Bottom, Align::Start),
1060        );
1061        assert_eq!(panel.x, 390.0 - PANEL.width);
1062    }
1063
1064    #[test]
1065    fn shifts_inside_the_margin_when_nothing_else_fits() {
1066        let corner = Rectangle::new(Point::new(0.0, 0.0), Size::new(10.0, 10.0));
1067        let panel = place(
1068            corner,
1069            PANEL,
1070            VIEWPORT,
1071            Placement::new(Side::Left, Align::Center),
1072        );
1073        assert_eq!(panel.x, 10.0 + GAP, "flipped to the right");
1074        assert_eq!(panel.y, MARGIN, "shifted down to the margin");
1075    }
1076
1077    #[test]
1078    fn a_list_near_the_bottom_keeps_the_margin() {
1079        let field = Rectangle::new(Point::new(20.0, 200.0), Size::new(200.0, 36.0));
1080        let list = Size::new(200.0, 80.0);
1081        let placement = Placement::new(Side::Bottom, Align::Start);
1082        let panel = place(field, list, VIEWPORT, placement);
1083        assert_eq!(panel.y, 200.0 - GAP - list.height, "flips above the field");
1084
1085        let wide = Size::new(420.0, 40.0);
1086        let panel = place(field, wide, VIEWPORT, placement);
1087        assert_eq!(panel.x, MARGIN, "clamped inside the window");
1088    }
1089
1090    #[test]
1091    fn a_point_anchor_opens_below_and_right_then_flips_near_edges() {
1092        let point = |x, y| Rectangle::new(Point::new(x, y), Size::ZERO);
1093        let placement = Placement::new(Side::Bottom, Align::Start).gap(0.0);
1094        assert_eq!(
1095            place(point(100.0, 100.0), PANEL, VIEWPORT, placement).position(),
1096            Point::new(100.0, 100.0)
1097        );
1098        assert_eq!(
1099            place(point(380.0, 280.0), PANEL, VIEWPORT, placement).position(),
1100            Point::new(320.0, 230.0)
1101        );
1102    }
1103
1104    #[test]
1105    fn a_panel_larger_than_the_window_is_pinned_top_left() {
1106        let huge = Size::new(1000.0, 1000.0);
1107        let panel = place(anchor(), huge, VIEWPORT, Placement::default());
1108        assert_eq!(panel.position(), Point::new(MARGIN, MARGIN));
1109        assert_eq!(panel.size(), huge);
1110    }
1111
1112    #[test]
1113    fn sides_and_alignments_mirror() {
1114        for side in Side::ALL {
1115            assert_eq!(side.opposite().opposite(), side);
1116            assert_ne!(side.opposite(), side);
1117        }
1118        assert_eq!(Align::Center.mirrored(), Align::Center);
1119        assert_eq!(Align::Start.mirrored(), Align::End);
1120    }
1121
1122    #[test]
1123    fn surface_uses_the_popover_colour_a_border_and_a_shadow() {
1124        for theme in [light(), dark()] {
1125            let tokens = Tokens::of(&theme);
1126            let style = surface_style(&tokens);
1127            assert_eq!(style.background, Some(Background::Color(tokens.popover)));
1128            assert_eq!(style.text_color, Some(tokens.foreground));
1129            assert_eq!(style.border.color, tokens.border);
1130            assert_eq!(style.border.width, 1.0);
1131            assert_eq!(style.border.radius, radius::MD.into());
1132            assert_eq!(style.shadow, shadow(&tokens));
1133            assert_eq!(style.shadow.offset, Vector::new(0.0, 4.0));
1134            assert_eq!(style.shadow.blur_radius, 12.0);
1135        }
1136        let light = shadow(&Tokens::of(&light())).color.a;
1137        let dark = shadow(&Tokens::of(&dark())).color.a;
1138        assert!((light - 0.1).abs() < 1e-6);
1139        assert!((dark - 0.5).abs() < 1e-6);
1140    }
1141
1142    #[test]
1143    fn builder_starts_closed_and_undismissable_with_escape_bound() {
1144        let anchored: Anchored<'_, ()> = anchored(iced::widget::text("Anchor"));
1145        let behaviour = &anchored.behaviour;
1146        assert!(anchored.content.is_none());
1147        assert!(behaviour.on_dismiss.is_none());
1148        assert!(behaviour.point.is_none());
1149        assert!(!behaviour.dismiss_on_anchor_press);
1150        assert!(!behaviour.dismiss_on_blur);
1151        assert!(!behaviour.pass_through);
1152        assert!(!behaviour.match_width);
1153        assert_eq!(behaviour.dismiss_keys, vec![Chord::named(Named::Escape)]);
1154        assert!(!behaviour.can_dismiss());
1155        assert!(!behaviour.watches_focus());
1156    }
1157
1158    #[test]
1159    fn builder_sets_every_option() {
1160        let anchored: Anchored<'_, u8> = anchored(iced::widget::text("Anchor"))
1161            .at(Point::new(3.0, 4.0))
1162            .match_width(true)
1163            .on_dismiss(1)
1164            .dismiss_keys([Chord::character('q')])
1165            .dismiss_on_anchor_press(true)
1166            .dismiss_on_blur(true)
1167            .pass_through(true)
1168            .on_anchor_press_maybe(Some(2))
1169            .on_key(|_| Some(3))
1170            .on_focus(|focused| if focused { 4 } else { 5 });
1171        let behaviour = &anchored.behaviour;
1172        assert_eq!(behaviour.focus_message(false, true), Some(4));
1173        assert_eq!(behaviour.focus_message(true, false), Some(5));
1174        assert_eq!(behaviour.focus_message(true, true), None);
1175        assert_eq!(behaviour.point, Some(Point::new(3.0, 4.0)));
1176        assert!(behaviour.match_width && behaviour.pass_through);
1177        assert!(behaviour.dismiss_on_anchor_press && behaviour.dismiss_on_blur);
1178        assert_eq!(behaviour.on_dismiss, Some(1));
1179        assert_eq!(behaviour.on_anchor_press, Some(2));
1180        assert_eq!(behaviour.dismiss_keys, vec![Chord::character('q')]);
1181        assert!(behaviour.can_dismiss() && behaviour.watches_focus());
1182    }
1183
1184    fn key_press(key: Key, modifiers: Modifiers) -> Event {
1185        Event::Keyboard(keyboard::Event::KeyPressed {
1186            key: key.clone(),
1187            modified_key: key,
1188            physical_key: keyboard::key::Physical::Unidentified(
1189                keyboard::key::NativeCode::Unidentified,
1190            ),
1191            location: keyboard::Location::Standard,
1192            modifiers,
1193            text: None,
1194            repeat: false,
1195        })
1196    }
1197
1198    #[test]
1199    fn keys_resolve_through_on_key_then_the_dismiss_chords() {
1200        let behaviour: Behaviour<'_, u8> = Behaviour {
1201            on_key: Some(Box::new(|key: &keys::Event| {
1202                (key.key == Key::Named(Named::ArrowDown)).then_some(7)
1203            })),
1204            ..Behaviour::default()
1205        };
1206        let down = key_press(Key::Named(Named::ArrowDown), Modifiers::empty());
1207        let escape = key_press(Key::Named(Named::Escape), Modifiers::empty());
1208        let shift_escape = key_press(Key::Named(Named::Escape), Modifiers::SHIFT);
1209        assert_eq!(key_message(&behaviour, &down), Some(7));
1210        assert_eq!(key_message(&behaviour, &escape), None);
1211        assert!(is_dismiss_key(&behaviour, &escape));
1212        assert!(!is_dismiss_key(&behaviour, &shift_escape));
1213        assert!(!is_dismiss_key(&behaviour, &down));
1214
1215        let unbound: Behaviour<'_, u8> = Behaviour {
1216            dismiss_keys: Vec::new(),
1217            ..Behaviour::default()
1218        };
1219        assert!(!is_dismiss_key(&unbound, &escape));
1220    }
1221
1222    #[test]
1223    fn press_position_only_reports_presses() {
1224        let cursor = mouse::Cursor::Available(Point::new(3.0, 4.0));
1225        let pressed = Event::Mouse(mouse::Event::ButtonPressed(mouse::Button::Right));
1226        let released = Event::Mouse(mouse::Event::ButtonReleased(mouse::Button::Left));
1227        assert_eq!(press_position(&pressed, cursor), Some(Point::new(3.0, 4.0)));
1228        assert_eq!(press_position(&released, cursor), None);
1229        assert_eq!(press_position(&pressed, mouse::Cursor::Unavailable), None);
1230        assert!(is_pointer_input(&released));
1231        assert!(is_release(&released));
1232        assert!(is_press(&pressed));
1233        assert!(!is_release(&pressed));
1234        assert!(!is_pointer_input(&Event::Mouse(mouse::Event::CursorLeft)));
1235    }
1236}