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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
//! Toolbar persistent state.
use crate::docking::panels::DockPanel;
use crate::input::core::coordinator::InputCoordinator;
use crate::layout::LayoutManager;
use crate::types::Rect;
use super::super::resize_drag::ResizeDrag;
use super::types::SplitButtonHoverZone;
/// All per-toolbar instance state.
///
/// Fields are stored flat — fields irrelevant to the active `ToolbarRenderKind`
/// are never touched.
#[derive(Debug, Default, Clone)]
pub struct ToolbarState {
// --- Item interaction ---
/// Id of the item that was last clicked / activated (persists across frames).
pub active_item_id: Option<String>,
/// Id of the item the pointer is currently hovering over.
pub hovered_item_id: Option<String>,
/// Id of the item currently held down (pointer pressed, not yet released).
pub pressed_item_id: Option<String>,
// --- Dropdown ---
/// Id of the item whose dropdown is currently open.
/// `None` = no dropdown open.
pub dropdown_open: Option<String>,
// --- Scroll (Horizontal overflow) ---
/// Horizontal (or vertical for Vertical toolbar) scroll offset in pixels.
/// `0.0` = no scroll.
pub scroll_offset: f64,
// --- Tooltip ---
/// Screen-space anchor rect of the item that should show a tooltip.
/// `None` = no tooltip pending.
pub tooltip_anchor: Option<Rect>,
/// Id of the item that is the current tooltip target.
pub tooltip_item_id: Option<String>,
// --- Split button ---
/// Split button currently being interacted with: `(item_id, zone)`.
/// `None` = no split interaction.
pub dragging_split: Option<(String, SplitButtonHoverZone)>,
// --- ChromeStrip ---
/// Index of the currently active (selected) chrome tab.
pub active_chrome_tab: usize,
/// Index of the hovered chrome tab (`None` = none).
pub hovered_chrome_tab: Option<usize>,
/// Whether the window-control close button is hovered.
pub chrome_close_hovered: bool,
/// Whether the window-control minimize button is hovered.
pub chrome_minimize_hovered: bool,
/// Whether the window-control maximize button is hovered.
pub chrome_maximize_hovered: bool,
// --- Resize ---
/// Resize drag in progress (set by `start_resize`).
pub resize_drag: Option<ResizeDrag>,
/// User-resized override for thickness (height for horizontal /
/// width for vertical toolbars). `0.0` = use the measured value.
pub resized_thickness: f64,
}
impl ToolbarState {
/// Mark an item as active (e.g. after click).
pub fn activate(&mut self, id: impl Into<String>) {
self.active_item_id = Some(id.into());
}
/// Clear the active item selection.
pub fn clear_active(&mut self) {
self.active_item_id = None;
}
/// Open the dropdown for `id`, closing any previously open one.
pub fn open_dropdown(&mut self, id: impl Into<String>) {
self.dropdown_open = Some(id.into());
}
/// Close the open dropdown.
pub fn close_dropdown(&mut self) {
self.dropdown_open = None;
}
/// Returns `true` when a dropdown is open for the given item id.
pub fn is_dropdown_open(&self, id: &str) -> bool {
self.dropdown_open.as_deref() == Some(id)
}
/// Apply a horizontal scroll delta (positive = scroll right).
///
/// `min` / `max` define the valid scroll range in pixels.
pub fn scroll(&mut self, delta: f64, min: f64, max: f64) {
self.scroll_offset = (self.scroll_offset + delta).clamp(min, max);
}
/// Reset transient hover/press state — call at frame start.
pub fn clear_transient(&mut self) {
self.hovered_item_id = None;
self.pressed_item_id = None;
self.tooltip_anchor = None;
self.tooltip_item_id = None;
self.dragging_split = None;
}
/// Set the tooltip target for this frame.
pub fn set_tooltip(&mut self, id: impl Into<String>, anchor: Rect) {
self.tooltip_item_id = Some(id.into());
self.tooltip_anchor = Some(anchor);
}
/// Start a split-button interaction.
pub fn begin_split_drag(&mut self, id: impl Into<String>, zone: SplitButtonHoverZone) {
self.dragging_split = Some((id.into(), zone));
}
/// End a split-button interaction.
pub fn end_split_drag(&mut self) {
self.dragging_split = None;
}
/// Sync the hovered-item id from the coordinator's hovered widget.
///
/// **Deprecated** — use `sync_hover_from_layout` when a `LayoutManager`
/// is available. Kept for back-compat with L3 callers.
pub fn sync_hover_from(&mut self, coord: &InputCoordinator, widget_id_prefix: &str) {
let hovered = coord.hovered_widget().map(|id| id.0.clone());
self.apply_hover(hovered, widget_id_prefix);
}
/// Sync hover state from the `LayoutManager` (L3 authoritative hover).
///
/// Preferred over `sync_hover_from`.
pub fn sync_hover_from_layout<P: DockPanel>(
&mut self,
layout: &LayoutManager<P>,
widget_id_prefix: &str,
) {
let hovered = layout.hovered_widget().map(|id| id.0.clone());
self.apply_hover(hovered, widget_id_prefix);
}
fn apply_hover(&mut self, hovered: Option<String>, widget_id_prefix: &str) {
self.hovered_item_id = hovered
.as_deref()
.filter(|s| s.starts_with(widget_id_prefix))
.map(|s| s[widget_id_prefix.len()..].to_owned());
}
/// Begin a thickness-resize drag.
///
/// Toolbars only resize on a single axis (height for horizontal,
/// width for vertical), so the dummy zero dimension on the inactive
/// axis is sized to a large value to keep the drag math simple.
pub fn start_resize(
&mut self,
edge: crate::layout::ResizeEdge,
start_rect: Rect,
cursor: (f64, f64),
min_size: f64,
cap_size: f64,
) {
self.resize_drag = Some(ResizeDrag::begin(
edge,
start_rect,
cursor,
(min_size, min_size),
(cap_size, cap_size),
));
}
/// Update the resized thickness from a fresh cursor position.
pub fn update_resize(&mut self, cursor: (f64, f64), is_vertical: bool) {
if let Some(drag) = self.resize_drag {
let rect = drag.resolve(cursor);
self.resized_thickness = if is_vertical { rect.width } else { rect.height };
}
}
/// End any active resize drag.
pub fn end_resize(&mut self) {
self.resize_drag = None;
}
}