1use ahash::{HashMap, HashSet};
2
3use crate::{id::IdSet, style, Align, Id, IdMap, LayerId, Rangef, Rect, Vec2, WidgetRects};
4
5#[cfg(debug_assertions)]
6use crate::{pos2, Align2, Color32, FontId, NumExt, Painter};
7
8#[derive(Clone, Debug, Default)]
10pub struct TooltipPassState {
11 pub widget_tooltips: IdMap<PerWidgetTooltipState>,
14}
15
16impl TooltipPassState {
17 pub fn clear(&mut self) {
18 let Self { widget_tooltips } = self;
19 widget_tooltips.clear();
20 }
21}
22
23#[derive(Clone, Copy, Debug)]
24pub struct PerWidgetTooltipState {
25 pub bounding_rect: Rect,
27
28 pub tooltip_count: usize,
30}
31
32#[derive(Clone, Debug, Default)]
33pub struct PerLayerState {
34 pub open_popups: HashSet<Id>,
38
39 pub widget_with_tooltip: Option<Id>,
44}
45
46#[derive(Clone, Debug)]
47pub struct ScrollTarget {
48 pub range: Rangef,
50
51 pub align: Option<Align>,
55
56 pub animation: style::ScrollAnimation,
58}
59
60impl ScrollTarget {
61 pub fn new(range: Rangef, align: Option<Align>, animation: style::ScrollAnimation) -> Self {
62 Self {
63 range,
64 align,
65 animation,
66 }
67 }
68}
69
70#[cfg(feature = "accesskit")]
71#[derive(Clone)]
72pub struct AccessKitPassState {
73 pub nodes: IdMap<accesskit::Node>,
74 pub parent_stack: Vec<Id>,
75}
76
77#[cfg(debug_assertions)]
78#[derive(Clone)]
79pub struct DebugRect {
80 pub rect: Rect,
81 pub callstack: String,
82 pub is_clicking: bool,
83}
84
85#[cfg(debug_assertions)]
86impl DebugRect {
87 pub fn paint(self, painter: &Painter) {
88 let Self {
89 rect,
90 callstack,
91 is_clicking,
92 } = self;
93
94 let ctx = painter.ctx();
95
96 {
98 let text_color = if ctx.style().visuals.dark_mode {
100 Color32::WHITE
101 } else {
102 Color32::BLACK
103 };
104 painter.debug_text(
105 rect.left_center() + 2.0 * Vec2::LEFT,
106 Align2::RIGHT_CENTER,
107 text_color,
108 format!("H: {:.1}", rect.height()),
109 );
110 painter.debug_text(
111 rect.center_top(),
112 Align2::CENTER_BOTTOM,
113 text_color,
114 format!("W: {:.1}", rect.width()),
115 );
116
117 let rect_fg_color = if is_clicking {
119 Color32::WHITE
120 } else {
121 Color32::LIGHT_BLUE
122 };
123 let rect_bg_color = Color32::BLUE.gamma_multiply(0.5);
124 painter.rect(rect, 0.0, rect_bg_color, (1.0, rect_fg_color));
125 }
126
127 if !callstack.is_empty() {
128 let font_id = FontId::monospace(12.0);
129 let text = format!("{callstack}\n\n(click to copy)");
130 let text_color = Color32::WHITE;
131 let galley = painter.layout_no_wrap(text, font_id, text_color);
132
133 let screen_rect = ctx.screen_rect();
135 let y = if galley.size().y <= rect.top() {
136 rect.top() - galley.size().y - 16.0
138 } else {
139 rect.bottom()
141 };
142
143 let y = y
144 .at_most(screen_rect.bottom() - galley.size().y)
145 .at_least(0.0);
146
147 let x = rect
148 .left()
149 .at_most(screen_rect.right() - galley.size().x)
150 .at_least(0.0);
151 let text_pos = pos2(x, y);
152
153 let text_bg_color = Color32::from_black_alpha(180);
154 let text_rect_stroke_color = if is_clicking {
155 Color32::WHITE
156 } else {
157 text_bg_color
158 };
159 let text_rect = Rect::from_min_size(text_pos, galley.size());
160 painter.rect(text_rect, 0.0, text_bg_color, (1.0, text_rect_stroke_color));
161 painter.galley(text_pos, galley, text_color);
162
163 if is_clicking {
164 ctx.copy_text(callstack);
165 }
166 }
167 }
168}
169
170#[derive(Clone)]
177pub struct PassState {
178 pub used_ids: IdMap<Rect>,
180
181 pub widgets: WidgetRects,
183
184 pub layers: HashMap<LayerId, PerLayerState>,
188
189 pub tooltips: TooltipPassState,
190
191 pub available_rect: Rect,
195
196 pub unused_rect: Rect,
199
200 pub used_by_panels: Rect,
202
203 pub scroll_target: [Option<ScrollTarget>; 2],
205
206 pub scroll_delta: (Vec2, style::ScrollAnimation),
216
217 #[cfg(feature = "accesskit")]
218 pub accesskit_state: Option<AccessKitPassState>,
219
220 pub highlight_next_pass: IdSet,
222
223 #[cfg(debug_assertions)]
224 pub debug_rect: Option<DebugRect>,
225}
226
227impl Default for PassState {
228 fn default() -> Self {
229 Self {
230 used_ids: Default::default(),
231 widgets: Default::default(),
232 layers: Default::default(),
233 tooltips: Default::default(),
234 available_rect: Rect::NAN,
235 unused_rect: Rect::NAN,
236 used_by_panels: Rect::NAN,
237 scroll_target: [None, None],
238 scroll_delta: (Vec2::default(), style::ScrollAnimation::none()),
239 #[cfg(feature = "accesskit")]
240 accesskit_state: None,
241 highlight_next_pass: Default::default(),
242
243 #[cfg(debug_assertions)]
244 debug_rect: None,
245 }
246 }
247}
248
249impl PassState {
250 pub(crate) fn begin_pass(&mut self, screen_rect: Rect) {
251 profiling::function_scope!();
252 let Self {
253 used_ids,
254 widgets,
255 tooltips,
256 layers,
257 available_rect,
258 unused_rect,
259 used_by_panels,
260 scroll_target,
261 scroll_delta,
262 #[cfg(feature = "accesskit")]
263 accesskit_state,
264 highlight_next_pass,
265
266 #[cfg(debug_assertions)]
267 debug_rect,
268 } = self;
269
270 used_ids.clear();
271 widgets.clear();
272 tooltips.clear();
273 layers.clear();
274 *available_rect = screen_rect;
275 *unused_rect = screen_rect;
276 *used_by_panels = Rect::NOTHING;
277 *scroll_target = [None, None];
278 *scroll_delta = Default::default();
279
280 #[cfg(debug_assertions)]
281 {
282 *debug_rect = None;
283 }
284
285 #[cfg(feature = "accesskit")]
286 {
287 *accesskit_state = None;
288 }
289
290 highlight_next_pass.clear();
291 }
292
293 pub(crate) fn available_rect(&self) -> Rect {
297 debug_assert!(
298 self.available_rect.is_finite(),
299 "Called `available_rect()` before `Context::run()`"
300 );
301 self.available_rect
302 }
303
304 pub(crate) fn allocate_left_panel(&mut self, panel_rect: Rect) {
306 debug_assert!(
307 panel_rect.min.distance(self.available_rect.min) < 0.1,
308 "Mismatching left panel. You must not create a panel from within another panel."
309 );
310 self.available_rect.min.x = panel_rect.max.x;
311 self.unused_rect.min.x = panel_rect.max.x;
312 self.used_by_panels = self.used_by_panels.union(panel_rect);
313 }
314
315 pub(crate) fn allocate_right_panel(&mut self, panel_rect: Rect) {
317 debug_assert!(
318 panel_rect.max.distance(self.available_rect.max) < 0.1,
319 "Mismatching right panel. You must not create a panel from within another panel."
320 );
321 self.available_rect.max.x = panel_rect.min.x;
322 self.unused_rect.max.x = panel_rect.min.x;
323 self.used_by_panels = self.used_by_panels.union(panel_rect);
324 }
325
326 pub(crate) fn allocate_top_panel(&mut self, panel_rect: Rect) {
328 debug_assert!(
329 panel_rect.min.distance(self.available_rect.min) < 0.1,
330 "Mismatching top panel. You must not create a panel from within another panel."
331 );
332 self.available_rect.min.y = panel_rect.max.y;
333 self.unused_rect.min.y = panel_rect.max.y;
334 self.used_by_panels = self.used_by_panels.union(panel_rect);
335 }
336
337 pub(crate) fn allocate_bottom_panel(&mut self, panel_rect: Rect) {
339 debug_assert!(
340 panel_rect.max.distance(self.available_rect.max) < 0.1,
341 "Mismatching bottom panel. You must not create a panel from within another panel."
342 );
343 self.available_rect.max.y = panel_rect.min.y;
344 self.unused_rect.max.y = panel_rect.min.y;
345 self.used_by_panels = self.used_by_panels.union(panel_rect);
346 }
347
348 pub(crate) fn allocate_central_panel(&mut self, panel_rect: Rect) {
349 self.unused_rect = Rect::NOTHING; self.used_by_panels = self.used_by_panels.union(panel_rect);
353 }
354}