1#![expect(deprecated)] use ahash::HashMap;
4
5use crate::{Align, Id, IdMap, LayerId, Rangef, Rect, Vec2, WidgetRects, id::IdSet, style};
6
7#[cfg(debug_assertions)]
8use crate::{Align2, Color32, FontId, NumExt as _, Painter, pos2};
9
10#[derive(Clone, Debug, Default)]
12pub struct TooltipPassState {
13 pub widget_tooltips: IdMap<PerWidgetTooltipState>,
16}
17
18impl TooltipPassState {
19 pub fn clear(&mut self) {
20 let Self { widget_tooltips } = self;
21 widget_tooltips.clear();
22 }
23}
24
25#[derive(Clone, Copy, Debug)]
26pub struct PerWidgetTooltipState {
27 pub bounding_rect: Rect,
29
30 pub tooltip_count: usize,
32}
33
34#[derive(Clone, Debug, Default)]
35pub struct PerLayerState {
36 pub open_popups: IdSet,
40
41 pub widget_with_tooltip: Option<Id>,
46}
47
48#[derive(Clone, Debug)]
49pub struct ScrollTarget {
50 pub range: Rangef,
52
53 pub align: Option<Align>,
57
58 pub animation: style::ScrollAnimation,
60}
61
62impl ScrollTarget {
63 pub fn new(range: Rangef, align: Option<Align>, animation: style::ScrollAnimation) -> Self {
64 Self {
65 range,
66 align,
67 animation,
68 }
69 }
70}
71
72#[derive(Clone)]
73pub struct AccessKitPassState {
74 pub nodes: IdMap<accesskit::Node>,
75 pub parent_map: IdMap<Id>,
76}
77
78#[cfg(debug_assertions)]
79#[derive(Clone)]
80pub struct DebugRect {
81 pub rect: Rect,
82 pub callstack: String,
83 pub is_clicking: bool,
84}
85
86#[cfg(debug_assertions)]
87impl DebugRect {
88 pub fn paint(self, painter: &Painter) {
89 let Self {
90 rect,
91 callstack,
92 is_clicking,
93 } = self;
94
95 let ctx = painter.ctx();
96
97 {
99 let text_color = if ctx.global_style().visuals.dark_mode {
101 Color32::WHITE
102 } else {
103 Color32::BLACK
104 };
105 painter.debug_text(
106 rect.left_center() + 2.0 * Vec2::LEFT,
107 Align2::RIGHT_CENTER,
108 text_color,
109 format!("H: {:.1}", rect.height()),
110 );
111 painter.debug_text(
112 rect.center_top(),
113 Align2::CENTER_BOTTOM,
114 text_color,
115 format!("W: {:.1}", rect.width()),
116 );
117
118 let rect_fg_color = if is_clicking {
120 Color32::WHITE
121 } else {
122 Color32::LIGHT_BLUE
123 };
124 let rect_bg_color = Color32::BLUE.gamma_multiply(0.5);
125 painter.rect(
126 rect,
127 0.0,
128 rect_bg_color,
129 (1.0, rect_fg_color),
130 crate::StrokeKind::Outside,
131 );
132 }
133
134 if !callstack.is_empty() {
135 let font_id = FontId::monospace(12.0);
136 let text = format!("{callstack}\n\n(click to copy)");
137 let text_color = Color32::WHITE;
138 let galley = painter.layout_no_wrap(text, font_id, text_color);
139
140 let content_rect = ctx.content_rect();
142 let y = if galley.size().y <= rect.top() {
143 rect.top() - galley.size().y - 16.0
145 } else {
146 rect.bottom()
148 };
149
150 let y = y
151 .at_most(content_rect.bottom() - galley.size().y)
152 .at_least(0.0);
153
154 let x = rect
155 .left()
156 .at_most(content_rect.right() - galley.size().x)
157 .at_least(0.0);
158 let text_pos = pos2(x, y);
159
160 let text_bg_color = Color32::from_black_alpha(180);
161 let text_rect_stroke_color = if is_clicking {
162 Color32::WHITE
163 } else {
164 text_bg_color
165 };
166 let text_rect = Rect::from_min_size(text_pos, galley.size());
167 painter.rect(
168 text_rect,
169 0.0,
170 text_bg_color,
171 (1.0, text_rect_stroke_color),
172 crate::StrokeKind::Middle,
173 );
174 painter.galley(text_pos, galley, text_color);
175
176 if is_clicking {
177 ctx.copy_text(callstack);
178 }
179 }
180 }
181}
182
183#[derive(Clone)]
190pub struct PassState {
191 pub used_ids: IdMap<Rect>,
193
194 pub widgets: WidgetRects,
196
197 pub layers: HashMap<LayerId, PerLayerState>,
201
202 pub tooltips: TooltipPassState,
203
204 pub root_ui_available_rect: Option<Rect>,
208
209 pub root_ui_min_rect: Option<Rect>,
213
214 #[deprecated = "Only used by legacy Context-Panels"]
217 pub available_rect: Rect,
218
219 #[deprecated = "Only used by legacy Context-Panels"]
222 pub unused_rect: Rect,
223
224 #[deprecated = "Only used by legacy Context-Panels"]
226 pub used_by_panels: Rect,
227
228 pub scroll_target: [Option<ScrollTarget>; 2],
230
231 pub scroll_delta: (Vec2, style::ScrollAnimation),
241
242 pub accesskit_state: Option<AccessKitPassState>,
243
244 pub highlight_next_pass: IdSet,
246
247 #[cfg(debug_assertions)]
248 pub debug_rect: Option<DebugRect>,
249}
250
251impl Default for PassState {
252 fn default() -> Self {
253 Self {
254 used_ids: Default::default(),
255 widgets: Default::default(),
256 layers: Default::default(),
257 tooltips: Default::default(),
258 root_ui_available_rect: None,
259 root_ui_min_rect: None,
260 available_rect: Rect::NAN,
261 unused_rect: Rect::NAN,
262 used_by_panels: Rect::NAN,
263 scroll_target: [None, None],
264 scroll_delta: (Vec2::default(), style::ScrollAnimation::none()),
265 accesskit_state: None,
266 highlight_next_pass: Default::default(),
267
268 #[cfg(debug_assertions)]
269 debug_rect: None,
270 }
271 }
272}
273
274impl PassState {
275 pub(crate) fn begin_pass(&mut self, content_rect: Rect) {
276 profiling::function_scope!();
277 let Self {
278 used_ids,
279 widgets,
280 tooltips,
281 layers,
282 root_ui_available_rect,
283 root_ui_min_rect,
284 available_rect,
285 unused_rect,
286 used_by_panels,
287 scroll_target,
288 scroll_delta,
289 accesskit_state,
290 highlight_next_pass,
291
292 #[cfg(debug_assertions)]
293 debug_rect,
294 } = self;
295
296 used_ids.clear();
297 widgets.clear();
298 tooltips.clear();
299 layers.clear();
300 *root_ui_available_rect = None;
301 *root_ui_min_rect = None;
302 *available_rect = content_rect;
303 *unused_rect = content_rect;
304 *used_by_panels = Rect::NOTHING;
305 *scroll_target = [None, None];
306 *scroll_delta = Default::default();
307
308 #[cfg(debug_assertions)]
309 {
310 *debug_rect = None;
311 }
312
313 *accesskit_state = None;
314
315 highlight_next_pass.clear();
316 }
317
318 #[deprecated = "Only used by legacy Context-Panels"]
320 pub(crate) fn available_rect(&self) -> Rect {
321 debug_assert!(
322 self.available_rect.is_finite(),
323 "Called `available_rect()` before `Context::run()`"
324 );
325 self.available_rect
326 }
327
328 #[deprecated = "Only used by legacy Context-Panels"]
330 pub(crate) fn allocate_left_panel(&mut self, panel_rect: Rect) {
331 debug_assert!(
332 panel_rect.min.distance(self.available_rect.min) < 0.1,
333 "Mismatching left panel. You must not create a panel from within another panel."
334 );
335 self.available_rect.min.x = panel_rect.max.x;
336 self.unused_rect.min.x = panel_rect.max.x;
337 self.used_by_panels |= panel_rect;
338 }
339
340 #[deprecated = "Only used by legacy Context-Panels"]
342 pub(crate) fn allocate_right_panel(&mut self, panel_rect: Rect) {
343 debug_assert!(
344 panel_rect.max.distance(self.available_rect.max) < 0.1,
345 "Mismatching right panel. You must not create a panel from within another panel."
346 );
347 self.available_rect.max.x = panel_rect.min.x;
348 self.unused_rect.max.x = panel_rect.min.x;
349 self.used_by_panels |= panel_rect;
350 }
351
352 #[deprecated = "Only used by legacy Context-Panels"]
354 pub(crate) fn allocate_top_panel(&mut self, panel_rect: Rect) {
355 debug_assert!(
356 panel_rect.min.distance(self.available_rect.min) < 0.1,
357 "Mismatching top panel. You must not create a panel from within another panel."
358 );
359 self.available_rect.min.y = panel_rect.max.y;
360 self.unused_rect.min.y = panel_rect.max.y;
361 self.used_by_panels |= panel_rect;
362 }
363
364 #[deprecated = "Only used by legacy Context-Panels"]
366 pub(crate) fn allocate_bottom_panel(&mut self, panel_rect: Rect) {
367 debug_assert!(
368 panel_rect.max.distance(self.available_rect.max) < 0.1,
369 "Mismatching bottom panel. You must not create a panel from within another panel."
370 );
371 self.available_rect.max.y = panel_rect.min.y;
372 self.unused_rect.max.y = panel_rect.min.y;
373 self.used_by_panels |= panel_rect;
374 }
375
376 #[deprecated = "Only used by legacy Context-Panels"]
377 pub(crate) fn allocate_central_panel(&mut self, panel_rect: Rect) {
378 self.unused_rect = Rect::NOTHING; self.used_by_panels |= panel_rect;
382 }
383}