// SPDX-License-Identifier: LGPL-2.1-only // Copyright (C) 2026 Liberux Labs, S. L. //! Position a child element below the on-screen rect of another widget, //! looked up from the *previous* frame's [`LaidOutWidget`] snapshot. //! //! The classic use case is a combo / dropdown popup that should appear //! flush below its trigger. The trigger's rect is not known in `view()` //! time — it's assigned during the layout pass — so the popup widget //! cannot place itself there directly. `AnchoredOverlay` solves this //! with a one-frame-old anchor: the trigger carries a stable //! [`WidgetId`], the popup wraps in `AnchoredOverlay` referencing the //! same id, and at draw time the wrapper looks up the trigger's rect //! from the runtime's persisted `widget_rects` (frame N − 1) and //! overrides the rect that its parent gave it. //! //! When the anchor is not found (first frame after open, missing id, //! widget went off-screen) the wrapper falls back to drawing the child //! in the parent-supplied rect — which, for a typical Stack-overlay //! root in a `view()`, means the full surface, giving a sane modal //! fallback until the next frame fixes the position. use crate::types::{ Rect, WidgetId }; use super::Element; #[ cfg( test ) ] mod tests; /// A wrapper that re-positions its child relative to an anchor widget /// found in the previous frame's layout snapshot. pub struct AnchoredOverlay { /// The element to draw at the anchored position. pub( crate ) child: Box>, /// Stable identifier of the widget whose rect provides the anchor. pub( crate ) anchor_id: WidgetId, /// Vertical pixel gap between the bottom of the anchor and the top /// of the child. pub( crate ) gap: f32, } impl AnchoredOverlay { /// Wrap `child` so it draws anchored below the widget that carries /// `anchor_id` in its `.id( … )` builder. `gap` is the vertical /// space (logical pixels) between the anchor's bottom edge and the /// child's top edge. pub fn new( child: impl Into>, anchor_id: WidgetId, gap: f32 ) -> Self { Self { child: Box::new( child.into() ), anchor_id, gap, } } /// Compute the draw rect for the child, given the anchor rect (when /// available) and the parent-supplied fallback. /// /// Anchor available → place the child flush below the anchor with /// `gap` spacing, preserving the anchor's width. /// Anchor missing → return the parent rect verbatim, so the child /// renders modal-style as a fallback. pub fn resolve_rect( anchor: Option, gap: f32, fallback: Rect ) -> Rect { match anchor { Some( a ) => Rect { x: a.x, y: a.y + a.height + gap, width: a.width, height: fallback.height, }, None => fallback, } } pub( crate ) fn map_msg( self, f: &super::MapFn ) -> AnchoredOverlay where U: Clone + 'static, Msg: 'static, { AnchoredOverlay { child: Box::new( self.child.map_arc( f ) ), anchor_id: self.anchor_id, gap: self.gap, } } } impl From> for Element { fn from( a: AnchoredOverlay ) -> Self { Element::AnchoredOverlay( a ) } }