Skip to main content

fret_ui/overlay_placement/
types.rs

1pub use fret_core::LayoutDirection;
2
3use fret_core::{Edges, Px, Rect, Size};
4
5#[derive(Debug, Clone, Copy, PartialEq, Eq)]
6pub enum Side {
7    Top,
8    Bottom,
9    Left,
10    Right,
11}
12
13#[derive(Debug, Clone, Copy, PartialEq, Eq)]
14pub enum Align {
15    Start,
16    Center,
17    End,
18}
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
21pub enum StickyMode {
22    Partial,
23    #[default]
24    Always,
25}
26
27/// Offset configuration inspired by Floating UI's `offset()` middleware.
28#[derive(Debug, Clone, Copy, Default, PartialEq)]
29pub struct Offset {
30    /// Distance along the placement side axis (the "gap" between anchor and panel).
31    pub main_axis: Px,
32    /// Distance along the alignment axis (skidding).
33    pub cross_axis: Px,
34    /// Optional skidding override for aligned placements (Start/End).
35    ///
36    /// When present and `align != Center`, this overrides `cross_axis` and flips sign for `End`.
37    /// For vertical placements (Top/Bottom), the direction is also flipped under RTL.
38    pub alignment_axis: Option<Px>,
39}
40
41/// Collision/overflow options inspired by Floating UI's `detectOverflow` configuration.
42///
43/// This is applied to the `outer` boundary before running the placement solver:
44///
45/// 1) If `boundary` is set, intersect `outer` with it (clipping ancestor style).
46/// 2) Inset by `padding` (collision padding).
47#[derive(Debug, Clone, Copy, Default, PartialEq)]
48pub struct CollisionOptions {
49    pub padding: Edges,
50    pub boundary: Option<Rect>,
51}
52
53#[derive(Debug, Clone, Copy, Default, PartialEq)]
54pub struct AnchoredPanelOptions {
55    pub direction: LayoutDirection,
56    pub offset: Offset,
57    /// Shift/clamp policy for keeping the floating panel within the collision boundary.
58    ///
59    /// This is inspired by Floating UI's `shift()` middleware options.
60    pub shift: ShiftOptions,
61    pub arrow: Option<ArrowOptions>,
62    pub collision: CollisionOptions,
63    pub sticky: StickyMode,
64}
65
66/// Shift configuration inspired by Floating UI's `shift()` middleware.
67///
68/// - `main_axis` clamps the panel along the placement axis (y for Top/Bottom, x for Left/Right).
69/// - `cross_axis` clamps the panel along the alignment axis (x for Top/Bottom, y for Left/Right).
70#[derive(Debug, Clone, Copy, PartialEq, Eq)]
71pub struct ShiftOptions {
72    pub main_axis: bool,
73    pub cross_axis: bool,
74}
75
76impl Default for ShiftOptions {
77    fn default() -> Self {
78        Self {
79            main_axis: true,
80            cross_axis: true,
81        }
82    }
83}
84
85/// Arrow positioning options inspired by Floating UI's `arrow()` middleware.
86#[derive(Debug, Clone, Copy, PartialEq)]
87pub struct ArrowOptions {
88    /// Arrow element size (in the same coordinate space as `outer`/`anchor`/`content`).
89    pub size: Size,
90    /// Padding between the arrow and the floating element edges (useful for rounded corners).
91    pub padding: Edges,
92}
93
94#[derive(Debug, Clone, Copy, PartialEq)]
95pub struct ArrowLayout {
96    /// Which side of the floating panel the arrow should attach to.
97    pub side: Side,
98    /// Offset along the arrow's axis inside the floating panel (x for Top/Bottom, y for Left/Right).
99    pub offset: Px,
100    /// The alignment-axis translation applied to the panel to keep the arrow pointing at the anchor
101    /// when the anchor is too small (Radix/Floating behavior).
102    pub alignment_offset: Px,
103    /// Signed center delta between the ideal arrow center point and the clamped offset.
104    ///
105    /// This matches Floating UI's `centerOffset` and is used by Radix to determine whether the arrow
106    /// should be hidden (`shouldHideArrow`).
107    pub center_offset: Px,
108}
109
110#[derive(Debug, Clone, Copy, PartialEq)]
111pub struct AnchoredPanelLayout {
112    pub rect: Rect,
113    pub side: Side,
114    pub align: Align,
115    pub arrow: Option<ArrowLayout>,
116}
117
118/// Debug-only trace capturing key decisions made by the anchored panel placement solver.
119///
120/// This is intended for diagnostics evidence (e.g. `fretboard diag`) so scripted repros can report
121/// why a popover/menu was flipped/clamped/shifted without relying on screenshots.
122#[derive(Debug, Clone, Copy, PartialEq)]
123pub struct AnchoredPanelLayoutTrace {
124    /// Solver inputs before applying collision options.
125    pub outer_input: Rect,
126    /// Collision boundary after applying `options.collision` (boundary + padding).
127    pub outer_collision: Rect,
128    pub anchor: Rect,
129    /// Desired (intrinsic) content size.
130    pub desired: Size,
131    /// Requested side gap (before `options.offset.main_axis` is applied).
132    pub side_offset: Px,
133    pub preferred_side: Side,
134    pub align: Align,
135    pub options: AnchoredPanelOptions,
136    /// Effective main-axis gap used by the solver (`side_offset + options.offset.main_axis`).
137    pub gap: Px,
138
139    /// Candidate rect for the preferred side (pre-shift, pre-arrow).
140    pub preferred_rect: Rect,
141    /// Candidate rect for the flipped side (pre-shift, pre-arrow).
142    pub flipped_rect: Rect,
143    /// Whether the preferred side fit without requiring main-axis clamping.
144    pub preferred_fits_without_main_clamp: bool,
145    /// Whether the flipped side fit without requiring main-axis clamping.
146    pub flipped_fits_without_main_clamp: bool,
147
148    pub preferred_available_main_px: f32,
149    pub flipped_available_main_px: f32,
150
151    /// The side chosen by the solver before shift/clamp/arrow adjustments.
152    pub chosen_side: Side,
153    /// The chosen candidate rect before shift/clamp/arrow adjustments.
154    pub chosen_rect: Rect,
155    /// The rect after applying shift/clamp (sticky/shift policy), before arrow adjustments.
156    pub rect_after_shift: Rect,
157    /// Signed delta applied by shift/clamp (after_shift.origin - chosen.origin).
158    pub shift_delta: fret_core::Point,
159
160    /// Final layout output (after shift + arrow).
161    pub layout: AnchoredPanelLayout,
162}