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#[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,
206
207 pub unused_rect: Rect,
210
211 pub used_by_panels: Rect,
213
214 pub scroll_target: [Option<ScrollTarget>; 2],
216
217 pub scroll_delta: (Vec2, style::ScrollAnimation),
227
228 #[cfg(feature = "accesskit")]
229 pub accesskit_state: Option<AccessKitPassState>,
230
231 pub highlight_next_pass: IdSet,
233
234 #[cfg(debug_assertions)]
235 pub debug_rect: Option<DebugRect>,
236}
237
238impl Default for PassState {
239 fn default() -> Self {
240 Self {
241 used_ids: Default::default(),
242 widgets: Default::default(),
243 layers: Default::default(),
244 tooltips: Default::default(),
245 available_rect: Rect::NAN,
246 unused_rect: Rect::NAN,
247 used_by_panels: Rect::NAN,
248 scroll_target: [None, None],
249 scroll_delta: (Vec2::default(), style::ScrollAnimation::none()),
250 #[cfg(feature = "accesskit")]
251 accesskit_state: None,
252 highlight_next_pass: Default::default(),
253
254 #[cfg(debug_assertions)]
255 debug_rect: None,
256 }
257 }
258}
259
260impl PassState {
261 pub(crate) fn begin_pass(&mut self, screen_rect: Rect) {
262 profiling::function_scope!();
263 let Self {
264 used_ids,
265 widgets,
266 tooltips,
267 layers,
268 available_rect,
269 unused_rect,
270 used_by_panels,
271 scroll_target,
272 scroll_delta,
273 #[cfg(feature = "accesskit")]
274 accesskit_state,
275 highlight_next_pass,
276
277 #[cfg(debug_assertions)]
278 debug_rect,
279 } = self;
280
281 used_ids.clear();
282 widgets.clear();
283 tooltips.clear();
284 layers.clear();
285 *available_rect = screen_rect;
286 *unused_rect = screen_rect;
287 *used_by_panels = Rect::NOTHING;
288 *scroll_target = [None, None];
289 *scroll_delta = Default::default();
290
291 #[cfg(debug_assertions)]
292 {
293 *debug_rect = None;
294 }
295
296 #[cfg(feature = "accesskit")]
297 {
298 *accesskit_state = None;
299 }
300
301 highlight_next_pass.clear();
302 }
303
304 pub(crate) fn available_rect(&self) -> Rect {
306 debug_assert!(
307 self.available_rect.is_finite(),
308 "Called `available_rect()` before `Context::run()`"
309 );
310 self.available_rect
311 }
312
313 pub(crate) fn allocate_left_panel(&mut self, panel_rect: Rect) {
315 debug_assert!(
316 panel_rect.min.distance(self.available_rect.min) < 0.1,
317 "Mismatching left panel. You must not create a panel from within another panel."
318 );
319 self.available_rect.min.x = panel_rect.max.x;
320 self.unused_rect.min.x = panel_rect.max.x;
321 self.used_by_panels |= panel_rect;
322 }
323
324 pub(crate) fn allocate_right_panel(&mut self, panel_rect: Rect) {
326 debug_assert!(
327 panel_rect.max.distance(self.available_rect.max) < 0.1,
328 "Mismatching right panel. You must not create a panel from within another panel."
329 );
330 self.available_rect.max.x = panel_rect.min.x;
331 self.unused_rect.max.x = panel_rect.min.x;
332 self.used_by_panels |= panel_rect;
333 }
334
335 pub(crate) fn allocate_top_panel(&mut self, panel_rect: Rect) {
337 debug_assert!(
338 panel_rect.min.distance(self.available_rect.min) < 0.1,
339 "Mismatching top panel. You must not create a panel from within another panel."
340 );
341 self.available_rect.min.y = panel_rect.max.y;
342 self.unused_rect.min.y = panel_rect.max.y;
343 self.used_by_panels |= panel_rect;
344 }
345
346 pub(crate) fn allocate_bottom_panel(&mut self, panel_rect: Rect) {
348 debug_assert!(
349 panel_rect.max.distance(self.available_rect.max) < 0.1,
350 "Mismatching bottom panel. You must not create a panel from within another panel."
351 );
352 self.available_rect.max.y = panel_rect.min.y;
353 self.unused_rect.max.y = panel_rect.min.y;
354 self.used_by_panels |= panel_rect;
355 }
356
357 pub(crate) fn allocate_central_panel(&mut self, panel_rect: Rect) {
358 self.unused_rect = Rect::NOTHING; self.used_by_panels |= panel_rect;
362 }
363}