[go: up one dir, main page]

egui/
pass_state.rs

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/// Reset at the start of each frame.
9#[derive(Clone, Debug, Default)]
10pub struct TooltipPassState {
11    /// If a tooltip has been shown this frame, where was it?
12    /// This is used to prevent multiple tooltips to cover each other.
13    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    /// Bounding rectangle for all widget and all previous tooltips.
26    pub bounding_rect: Rect,
27
28    /// How many tooltips have been shown for this widget this frame?
29    pub tooltip_count: usize,
30}
31
32#[derive(Clone, Debug, Default)]
33pub struct PerLayerState {
34    /// Is there any open popup (menus, combo-boxes, etc)?
35    ///
36    /// Does NOT include tooltips.
37    pub open_popups: IdSet,
38
39    /// Which widget is showing a tooltip (if any)?
40    ///
41    /// Only one widget per layer may show a tooltip.
42    /// But if a tooltip contains a tooltip, you can show a tooltip on top of a tooltip.
43    pub widget_with_tooltip: Option<Id>,
44}
45
46#[derive(Clone, Debug)]
47pub struct ScrollTarget {
48    // The range that the scroll area should scroll to.
49    pub range: Rangef,
50
51    /// How should we align the rect within the visible area?
52    /// If `align` is [`Align::TOP`] it means "put the top of the rect at the top of the scroll area", etc.
53    /// If `align` is `None`, it'll scroll enough to bring the UI into view.
54    pub align: Option<Align>,
55
56    /// How should the scroll be animated?
57    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        // Paint rectangle around widget:
97        {
98            // Print width and height:
99            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            // Paint rect:
118            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            // Position the text either under or above:
140            let screen_rect = ctx.screen_rect();
141            let y = if galley.size().y <= rect.top() {
142                // Above
143                rect.top() - galley.size().y - 16.0
144            } else {
145                // Below
146                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/// State that is collected during a pass, then saved for the next pass,
183/// and then cleared.
184///
185/// (NOTE: we usually run only one pass per frame).
186///
187/// One per viewport.
188#[derive(Clone)]
189pub struct PassState {
190    /// All [`Id`]s that were used this pass.
191    pub used_ids: IdMap<Rect>,
192
193    /// All widgets produced this pass.
194    pub widgets: WidgetRects,
195
196    /// Per-layer state.
197    ///
198    /// Not all layers registers themselves there though.
199    pub layers: HashMap<LayerId, PerLayerState>,
200
201    pub tooltips: TooltipPassState,
202
203    /// Starts off as the `screen_rect`, shrinks as panels are added.
204    /// The [`crate::CentralPanel`] does not change this.
205    /// This is the area available to Window's.
206    pub available_rect: Rect,
207
208    /// Starts off as the `screen_rect`, shrinks as panels are added.
209    /// The [`crate::CentralPanel`] retracts from this.
210    pub unused_rect: Rect,
211
212    /// How much space is used by panels.
213    pub used_by_panels: Rect,
214
215    /// The current scroll area should scroll to this range (horizontal, vertical).
216    pub scroll_target: [Option<ScrollTarget>; 2],
217
218    /// The current scroll area should scroll by this much.
219    ///
220    /// The delta dictates how the _content_ should move.
221    ///
222    /// A positive X-value indicates the content is being moved right,
223    /// as when swiping right on a touch-screen or track-pad with natural scrolling.
224    ///
225    /// A positive Y-value indicates the content is being moved down,
226    /// as when swiping down on a touch-screen or track-pad with natural scrolling.
227    pub scroll_delta: (Vec2, style::ScrollAnimation),
228
229    #[cfg(feature = "accesskit")]
230    pub accesskit_state: Option<AccessKitPassState>,
231
232    /// Highlight these widgets the next pass.
233    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    /// How much space is still available after panels has been added.
306    /// This is the "background" area, what egui doesn't cover with panels (but may cover with windows).
307    /// This is also the area to which windows are constrained.
308    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    /// Shrink `available_rect`.
317    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    /// Shrink `available_rect`.
328    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    /// Shrink `available_rect`.
339    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    /// Shrink `available_rect`.
350    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        // Note: we do not shrink `available_rect`, because
362        // we allow windows to cover the CentralPanel.
363        self.unused_rect = Rect::NOTHING; // Nothing left unused after this
364        self.used_by_panels = self.used_by_panels.union(panel_rect);
365    }
366}