1use ahash::HashMap;
2
3use crate::{Align, Id, IdMap, LayerId, Rangef, Rect, Vec2, WidgetRects, id::IdSet, style};
4
5#[cfg(debug_assertions)]
6use crate::{Align2, Color32, FontId, NumExt as _, Painter, pos2};
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: IdSet,
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#[derive(Clone)]
71pub struct AccessKitPassState {
72 pub nodes: IdMap<accesskit::Node>,
73 pub parent_map: IdMap<Id>,
74}
75
76#[cfg(debug_assertions)]
77#[derive(Clone)]
78pub struct DebugRect {
79 pub rect: Rect,
80 pub callstack: String,
81 pub is_clicking: bool,
82}
83
84#[cfg(debug_assertions)]
85impl DebugRect {
86 pub fn paint(self, painter: &Painter) {
87 let Self {
88 rect,
89 callstack,
90 is_clicking,
91 } = self;
92
93 let ctx = painter.ctx();
94
95 {
97 let text_color = if ctx.global_style().visuals.dark_mode {
99 Color32::WHITE
100 } else {
101 Color32::BLACK
102 };
103 painter.debug_text(
104 rect.left_center() + 2.0 * Vec2::LEFT,
105 Align2::RIGHT_CENTER,
106 text_color,
107 format!("H: {:.1}", rect.height()),
108 );
109 painter.debug_text(
110 rect.center_top(),
111 Align2::CENTER_BOTTOM,
112 text_color,
113 format!("W: {:.1}", rect.width()),
114 );
115
116 let rect_fg_color = if is_clicking {
118 Color32::WHITE
119 } else {
120 Color32::LIGHT_BLUE
121 };
122 let rect_bg_color = Color32::BLUE.gamma_multiply(0.5);
123 painter.rect(
124 rect,
125 0.0,
126 rect_bg_color,
127 (1.0, rect_fg_color),
128 crate::StrokeKind::Outside,
129 );
130 }
131
132 if !callstack.is_empty() {
133 let font_id = FontId::monospace(12.0);
134 let text = format!("{callstack}\n\n(click to copy)");
135 let text_color = Color32::WHITE;
136 let galley = painter.layout_no_wrap(text, font_id, text_color);
137
138 let content_rect = ctx.content_rect();
140 let y = if galley.size().y <= rect.top() {
141 rect.top() - galley.size().y - 16.0
143 } else {
144 rect.bottom()
146 };
147
148 let y = y
149 .at_most(content_rect.bottom() - galley.size().y)
150 .at_least(0.0);
151
152 let x = rect
153 .left()
154 .at_most(content_rect.right() - galley.size().x)
155 .at_least(0.0);
156 let text_pos = pos2(x, y);
157
158 let text_bg_color = Color32::from_black_alpha(180);
159 let text_rect_stroke_color = if is_clicking {
160 Color32::WHITE
161 } else {
162 text_bg_color
163 };
164 let text_rect = Rect::from_min_size(text_pos, galley.size());
165 painter.rect(
166 text_rect,
167 0.0,
168 text_bg_color,
169 (1.0, text_rect_stroke_color),
170 crate::StrokeKind::Middle,
171 );
172 painter.galley(text_pos, galley, text_color);
173
174 if is_clicking {
175 ctx.copy_text(callstack);
176 }
177 }
178 }
179}
180
181#[derive(Clone)]
188pub struct PassState {
189 pub used_ids: IdMap<Rect>,
191
192 pub widgets: WidgetRects,
194
195 pub layers: HashMap<LayerId, PerLayerState>,
199
200 pub tooltips: TooltipPassState,
201
202 pub available_rect: Rect,
205
206 pub unused_rect: Rect,
209
210 pub used_by_panels: Rect,
212
213 pub scroll_target: [Option<ScrollTarget>; 2],
215
216 pub scroll_delta: (Vec2, style::ScrollAnimation),
226
227 pub accesskit_state: Option<AccessKitPassState>,
228
229 pub highlight_next_pass: IdSet,
231
232 #[cfg(debug_assertions)]
233 pub debug_rect: Option<DebugRect>,
234}
235
236impl Default for PassState {
237 fn default() -> Self {
238 Self {
239 used_ids: Default::default(),
240 widgets: Default::default(),
241 layers: Default::default(),
242 tooltips: Default::default(),
243 available_rect: Rect::NAN,
244 unused_rect: Rect::NAN,
245 used_by_panels: Rect::NAN,
246 scroll_target: [None, None],
247 scroll_delta: (Vec2::default(), style::ScrollAnimation::none()),
248 accesskit_state: None,
249 highlight_next_pass: Default::default(),
250
251 #[cfg(debug_assertions)]
252 debug_rect: None,
253 }
254 }
255}
256
257impl PassState {
258 pub(crate) fn begin_pass(&mut self, content_rect: Rect) {
259 profiling::function_scope!();
260 let Self {
261 used_ids,
262 widgets,
263 tooltips,
264 layers,
265 available_rect,
266 unused_rect,
267 used_by_panels,
268 scroll_target,
269 scroll_delta,
270 accesskit_state,
271 highlight_next_pass,
272
273 #[cfg(debug_assertions)]
274 debug_rect,
275 } = self;
276
277 used_ids.clear();
278 widgets.clear();
279 tooltips.clear();
280 layers.clear();
281 *available_rect = content_rect;
282 *unused_rect = content_rect;
283 *used_by_panels = Rect::NOTHING;
284 *scroll_target = [None, None];
285 *scroll_delta = Default::default();
286
287 #[cfg(debug_assertions)]
288 {
289 *debug_rect = None;
290 }
291
292 *accesskit_state = None;
293
294 highlight_next_pass.clear();
295 }
296
297 pub(crate) fn available_rect(&self) -> Rect {
299 debug_assert!(
300 self.available_rect.is_finite(),
301 "Called `available_rect()` before `Context::run()`"
302 );
303 self.available_rect
304 }
305
306 pub(crate) fn allocate_left_panel(&mut self, panel_rect: Rect) {
308 debug_assert!(
309 panel_rect.min.distance(self.available_rect.min) < 0.1,
310 "Mismatching left panel. You must not create a panel from within another panel."
311 );
312 self.available_rect.min.x = panel_rect.max.x;
313 self.unused_rect.min.x = panel_rect.max.x;
314 self.used_by_panels |= panel_rect;
315 }
316
317 pub(crate) fn allocate_right_panel(&mut self, panel_rect: Rect) {
319 debug_assert!(
320 panel_rect.max.distance(self.available_rect.max) < 0.1,
321 "Mismatching right panel. You must not create a panel from within another panel."
322 );
323 self.available_rect.max.x = panel_rect.min.x;
324 self.unused_rect.max.x = panel_rect.min.x;
325 self.used_by_panels |= panel_rect;
326 }
327
328 pub(crate) fn allocate_top_panel(&mut self, panel_rect: Rect) {
330 debug_assert!(
331 panel_rect.min.distance(self.available_rect.min) < 0.1,
332 "Mismatching top panel. You must not create a panel from within another panel."
333 );
334 self.available_rect.min.y = panel_rect.max.y;
335 self.unused_rect.min.y = panel_rect.max.y;
336 self.used_by_panels |= panel_rect;
337 }
338
339 pub(crate) fn allocate_bottom_panel(&mut self, panel_rect: Rect) {
341 debug_assert!(
342 panel_rect.max.distance(self.available_rect.max) < 0.1,
343 "Mismatching bottom panel. You must not create a panel from within another panel."
344 );
345 self.available_rect.max.y = panel_rect.min.y;
346 self.unused_rect.max.y = panel_rect.min.y;
347 self.used_by_panels |= panel_rect;
348 }
349
350 pub(crate) fn allocate_central_panel(&mut self, panel_rect: Rect) {
351 self.unused_rect = Rect::NOTHING; self.used_by_panels |= panel_rect;
355 }
356}