1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
//! Dropdown persistent state.
//!
//! `DropdownState` is a flat struct — fields irrelevant to the active
//! `DropdownRenderKind` are never touched.
use crate::types::Rect;
/// All per-dropdown instance state.
#[derive(Debug, Clone)]
pub struct DropdownState {
// --- Lifecycle ---
/// Whether the dropdown panel is currently visible.
pub open: bool,
// --- Position ---
/// Top-left of the open panel in screen coordinates.
/// Re-computed each frame from `anchor_rect` + orientation.
pub origin: (f64, f64),
/// Trigger button rect — used to re-anchor when layout reflows.
/// `None` = caller provides position via `open_position_override`.
pub anchor_rect: Option<Rect>,
// --- Selection ---
/// Id of the last-selected item (persistent across open/close cycles).
/// Used for accent bar in presets menu; optional for other menus.
pub selected_id: Option<String>,
// --- Hover ---
/// Id of the currently hovered item within the open list.
pub hovered_id: Option<String>,
// --- Scroll ---
/// Vertical scroll offset in pixels for long item lists.
/// `0.0` = top of list fully visible.
pub scroll_offset: f64,
// --- Submenu ---
/// Id of the item whose submenu is currently open.
/// `None` = no submenu open.
pub submenu_open: Option<String>,
/// Screen-space top-left position for the open submenu panel.
/// Computed as `(parent_menu.right() + gap, trigger_item.y)`.
pub submenu_origin: (f64, f64),
// --- Sizing constraints ---
/// Maximum height of the panel in pixels.
/// `0.0` = no height limit (scroll disabled).
pub max_height: f64,
/// Minimum width of the panel in pixels.
/// Defaults to `180.0`; inline variant uses button width instead.
pub min_width: f64,
// --- Primed state ---
/// "Primed" item id: last quick-selected tool shown with accent on the trigger.
/// Specific to toolbar drawing-tool groups; `None` for all other kinds.
pub primed_id: Option<String>,
// --- Custom position override ---
/// When `Some`, overrides anchor-based positioning.
/// Used by external callers (chrome button, context trigger) that need to
/// open the dropdown at an arbitrary screen coordinate.
/// Cleared when the dropdown closes.
pub open_position_override: Option<(f64, f64)>,
}
impl Default for DropdownState {
fn default() -> Self {
Self {
open: false,
origin: (0.0, 0.0),
anchor_rect: None,
selected_id: None,
hovered_id: None,
scroll_offset: 0.0,
submenu_open: None,
submenu_origin: (0.0, 0.0),
max_height: 0.0,
min_width: 180.0,
primed_id: None,
open_position_override: None,
}
}
}
impl DropdownState {
/// Open the dropdown, computing origin from an anchor rect.
///
/// `anchor` — trigger button rect in screen coordinates.
/// `gap` — pixels between the bottom of the trigger and the panel top.
pub fn open_below(&mut self, anchor: Rect, gap: f64) {
self.open = true;
self.anchor_rect = Some(anchor);
self.origin = (anchor.x, anchor.y + anchor.height + gap);
self.open_position_override = None;
self.hovered_id = None;
self.submenu_open = None;
self.scroll_offset = 0.0;
}
/// Open the dropdown at an explicit screen-space position.
pub fn open_at(&mut self, x: f64, y: f64) {
self.open = true;
self.anchor_rect = None;
self.origin = (x, y);
self.open_position_override = Some((x, y));
self.hovered_id = None;
self.submenu_open = None;
self.scroll_offset = 0.0;
}
/// Close the dropdown and reset transient state.
pub fn close(&mut self) {
self.open = false;
self.hovered_id = None;
self.submenu_open = None;
self.open_position_override = None;
self.scroll_offset = 0.0;
}
/// Select an item by id (persists across open/close cycles).
pub fn select(&mut self, id: impl Into<String>) {
self.selected_id = Some(id.into());
}
/// Returns the effective panel origin: override > anchor-derived.
pub fn effective_origin(&self) -> (f64, f64) {
self.open_position_override.unwrap_or(self.origin)
}
}