1use ahash::HashMap;
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: 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#[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(
125 rect,
126 0.0,
127 rect_bg_color,
128 (1.0, rect_fg_color),
129 crate::StrokeKind::Outside,
130 );
131 }
132
133 if !callstack.is_empty() {
134 let font_id = FontId::monospace(12.0);
135 let text = format!("{callstack}\n\n(click to copy)");
136 let text_color = Color32::WHITE;
137 let galley = painter.layout_no_wrap(text, font_id, text_color);
138
139 let screen_rect = ctx.screen_rect();
141 let y = if galley.size().y <= rect.top() {
142 rect.top() - galley.size().y - 16.0
144 } else {
145 rect.bottom()
147 };
148
149 let y = y
150 .at_most(screen_rect.bottom() - galley.size().y)
151 .at_least(0.0);
152
153 let x = rect
154 .left()
155 .at_most(screen_rect.right() - galley.size().x)
156 .at_least(0.0);
157 let text_pos = pos2(x, y);
158
159 let text_bg_color = Color32::from_black_alpha(180);
160 let text_rect_stroke_color = if is_clicking {
161 Color32::WHITE
162 } else {
163 text_bg_color
164 };
165 let text_rect = Rect::from_min_size(text_pos, galley.size());
166 painter.rect(
167 text_rect,
168 0.0,
169 text_bg_color,
170 (1.0, text_rect_stroke_color),
171 crate::StrokeKind::Middle,
172 );
173 painter.galley(text_pos, galley, text_color);
174
175 if is_clicking {
176 ctx.copy_text(callstack);
177 }
178 }
179 }
180}
181
182#[derive(Clone)]
189pub struct PassState {
190 pub used_ids: IdMap<Rect>,
192
193 pub widgets: WidgetRects,
195
196 pub layers: HashMap<LayerId, PerLayerState>,
200
201 pub tooltips: TooltipPassState,
202
203 pub available_rect: Rect,
207
208 pub unused_rect: Rect,
211
212 pub used_by_panels: Rect,
214
215 pub scroll_target: [Option<ScrollTarget>; 2],
217
218 pub scroll_delta: (Vec2, style::ScrollAnimation),
228
229 #[cfg(feature = "accesskit")]
230 pub accesskit_state: Option<AccessKitPassState>,
231
232 pub highlight_next_pass: IdSet,
234
235 #[cfg(debug_assertions)]
236 pub debug_rect: Option<DebugRect>,
237}
238
239impl Default for PassState {
240 fn default() -> Self {
241 Self {
242 used_ids: Default::default(),
243 widgets: Default::default(),
244 layers: Default::default(),
245 tooltips: Default::default(),
246 available_rect: Rect::NAN,
247 unused_rect: Rect::NAN,
248 used_by_panels: Rect::NAN,
249 scroll_target: [None, None],
250 scroll_delta: (Vec2::default(), style::ScrollAnimation::none()),
251 #[cfg(feature = "accesskit")]
252 accesskit_state: None,
253 highlight_next_pass: Default::default(),
254
255 #[cfg(debug_assertions)]
256 debug_rect: None,
257 }
258 }
259}
260
261impl PassState {
262 pub(crate) fn begin_pass(&mut self, screen_rect: Rect) {
263 profiling::function_scope!();
264 let Self {
265 used_ids,
266 widgets,
267 tooltips,
268 layers,
269 available_rect,
270 unused_rect,
271 used_by_panels,
272 scroll_target,
273 scroll_delta,
274 #[cfg(feature = "accesskit")]
275 accesskit_state,
276 highlight_next_pass,
277
278 #[cfg(debug_assertions)]
279 debug_rect,
280 } = self;
281
282 used_ids.clear();
283 widgets.clear();
284 tooltips.clear();
285 layers.clear();
286 *available_rect = screen_rect;
287 *unused_rect = screen_rect;
288 *used_by_panels = Rect::NOTHING;
289 *scroll_target = [None, None];
290 *scroll_delta = Default::default();
291
292 #[cfg(debug_assertions)]
293 {
294 *debug_rect = None;
295 }
296
297 #[cfg(feature = "accesskit")]
298 {
299 *accesskit_state = None;
300 }
301
302 highlight_next_pass.clear();
303 }
304
305 pub(crate) fn available_rect(&self) -> Rect {
309 debug_assert!(
310 self.available_rect.is_finite(),
311 "Called `available_rect()` before `Context::run()`"
312 );
313 self.available_rect
314 }
315
316 pub(crate) fn allocate_left_panel(&mut self, panel_rect: Rect) {
318 debug_assert!(
319 panel_rect.min.distance(self.available_rect.min) < 0.1,
320 "Mismatching left panel. You must not create a panel from within another panel."
321 );
322 self.available_rect.min.x = panel_rect.max.x;
323 self.unused_rect.min.x = panel_rect.max.x;
324 self.used_by_panels = self.used_by_panels.union(panel_rect);
325 }
326
327 pub(crate) fn allocate_right_panel(&mut self, panel_rect: Rect) {
329 debug_assert!(
330 panel_rect.max.distance(self.available_rect.max) < 0.1,
331 "Mismatching right panel. You must not create a panel from within another panel."
332 );
333 self.available_rect.max.x = panel_rect.min.x;
334 self.unused_rect.max.x = panel_rect.min.x;
335 self.used_by_panels = self.used_by_panels.union(panel_rect);
336 }
337
338 pub(crate) fn allocate_top_panel(&mut self, panel_rect: Rect) {
340 debug_assert!(
341 panel_rect.min.distance(self.available_rect.min) < 0.1,
342 "Mismatching top panel. You must not create a panel from within another panel."
343 );
344 self.available_rect.min.y = panel_rect.max.y;
345 self.unused_rect.min.y = panel_rect.max.y;
346 self.used_by_panels = self.used_by_panels.union(panel_rect);
347 }
348
349 pub(crate) fn allocate_bottom_panel(&mut self, panel_rect: Rect) {
351 debug_assert!(
352 panel_rect.max.distance(self.available_rect.max) < 0.1,
353 "Mismatching bottom panel. You must not create a panel from within another panel."
354 );
355 self.available_rect.max.y = panel_rect.min.y;
356 self.unused_rect.max.y = panel_rect.min.y;
357 self.used_by_panels = self.used_by_panels.union(panel_rect);
358 }
359
360 pub(crate) fn allocate_central_panel(&mut self, panel_rect: Rect) {
361 self.unused_rect = Rect::NOTHING; self.used_by_panels = self.used_by_panels.union(panel_rect);
365 }
366}