Skip to main content

egui/
ui_stack.rs

1use std::sync::Arc;
2use std::{any::Any, iter::FusedIterator};
3
4use epaint::Color32;
5
6use crate::{Direction, Frame, Id, Rect};
7
8/// What kind is this [`crate::Ui`]?
9#[derive(Clone, Copy, Debug, PartialEq, Eq)]
10pub enum UiKind {
11    /// A [`crate::Window`].
12    Window,
13
14    /// A [`crate::CentralPanel`].
15    CentralPanel,
16
17    /// A left [`crate::Panel`].
18    LeftPanel,
19
20    /// A right [`crate::Panel`].
21    RightPanel,
22
23    /// A top [`crate::Panel`].
24    TopPanel,
25
26    /// A bottom [`crate::Panel`].
27    BottomPanel,
28
29    /// A modal [`crate::Modal`].
30    Modal,
31
32    /// A [`crate::Frame`].
33    Frame,
34
35    /// A [`crate::ScrollArea`].
36    ScrollArea,
37
38    /// A [`crate::Resize`].
39    Resize,
40
41    /// The content of a regular menu.
42    Menu,
43
44    /// The content of a popup menu.
45    Popup,
46
47    /// A tooltip, as shown by e.g. [`crate::Response::on_hover_ui`].
48    Tooltip,
49
50    /// A picker, such as color picker.
51    Picker,
52
53    /// A table cell (from the `egui_extras` crate).
54    TableCell,
55
56    /// An [`crate::Area`] that is not of any other kind.
57    GenericArea,
58
59    /// A collapsible container, e.g. a [`crate::CollapsingHeader`].
60    Collapsible,
61}
62
63impl UiKind {
64    /// Is this any kind of panel?
65    #[inline]
66    pub fn is_panel(&self) -> bool {
67        matches!(
68            self,
69            Self::CentralPanel
70                | Self::LeftPanel
71                | Self::RightPanel
72                | Self::TopPanel
73                | Self::BottomPanel
74        )
75    }
76
77    /// Is this any kind of [`crate::Area`]?
78    #[inline]
79    pub fn is_area(&self) -> bool {
80        match self {
81            Self::CentralPanel
82            | Self::LeftPanel
83            | Self::RightPanel
84            | Self::TopPanel
85            | Self::BottomPanel
86            | Self::Frame
87            | Self::ScrollArea
88            | Self::Resize
89            | Self::Collapsible
90            | Self::TableCell => false,
91
92            Self::Window
93            | Self::Menu
94            | Self::Modal
95            | Self::Popup
96            | Self::Tooltip
97            | Self::Picker
98            | Self::GenericArea => true,
99        }
100    }
101}
102
103// ----------------------------------------------------------------------------
104
105/// Information about a [`crate::Ui`] to be included in the corresponding [`UiStack`].
106#[derive(Clone, Default, Debug)]
107pub struct UiStackInfo {
108    pub kind: Option<UiKind>,
109    pub frame: Frame,
110    pub tags: UiTags,
111}
112
113impl UiStackInfo {
114    /// Create a new [`UiStackInfo`] with the given kind and an empty frame.
115    #[inline]
116    pub fn new(kind: UiKind) -> Self {
117        Self {
118            kind: Some(kind),
119            ..Default::default()
120        }
121    }
122
123    #[inline]
124    pub fn with_frame(mut self, frame: Frame) -> Self {
125        self.frame = frame;
126        self
127    }
128
129    /// Insert a tag with no value.
130    #[inline]
131    pub fn with_tag(mut self, key: impl Into<String>) -> Self {
132        self.tags.insert(key, None);
133        self
134    }
135
136    /// Insert a tag with some value.
137    #[inline]
138    pub fn with_tag_value(
139        mut self,
140        key: impl Into<String>,
141        value: impl Any + Send + Sync + 'static,
142    ) -> Self {
143        self.tags.insert(key, Some(Arc::new(value)));
144        self
145    }
146}
147
148// ----------------------------------------------------------------------------
149
150/// User-chosen tags.
151///
152/// You can use this in any way you want,
153/// i.e. to set some tag on a [`crate::Ui`] and then in your own widget check
154/// for the existence of this tag up the [`UiStack`].
155///
156/// Note that egui never sets any tags itself, so this is purely for user code.
157///
158/// All tagging is transient, and will only live as long as the parent [`crate::Ui`], i.e. within a single render frame.
159#[derive(Clone, Default, Debug)]
160pub struct UiTags(pub ahash::HashMap<String, Option<Arc<dyn Any + Send + Sync + 'static>>>);
161
162impl UiTags {
163    #[inline]
164    pub fn insert(
165        &mut self,
166        key: impl Into<String>,
167        value: Option<Arc<dyn Any + Send + Sync + 'static>>,
168    ) {
169        self.0.insert(key.into(), value);
170    }
171
172    #[inline]
173    pub fn contains(&self, key: &str) -> bool {
174        self.0.contains_key(key)
175    }
176
177    /// Get the value of a tag.
178    ///
179    /// Note that `None` is returned both if the key is set to the value `None`,
180    /// and if the key is not set at all.
181    #[inline]
182    pub fn get_any(&self, key: &str) -> Option<&Arc<dyn Any + Send + Sync + 'static>> {
183        self.0.get(key)?.as_ref()
184    }
185
186    /// Get the value of a tag.
187    ///
188    /// Note that `None` is returned both if the key is set to the value `None`,
189    /// and if the key is not set at all.
190    pub fn get_downcast<T: Any + Send + Sync + 'static>(&self, key: &str) -> Option<&T> {
191        self.0.get(key)?.as_ref().and_then(|any| any.downcast_ref())
192    }
193}
194
195// ----------------------------------------------------------------------------
196
197/// Information about a [`crate::Ui`] and its parents.
198///
199/// [`UiStack`] serves to keep track of the current hierarchy of [`crate::Ui`]s, such
200/// that nested widgets or user code may adapt to the surrounding context or obtain layout information
201/// from a [`crate::Ui`] that might be several steps higher in the hierarchy.
202///
203/// Note: since [`UiStack`] contains a reference to its parent, it is both a stack, and a node within
204/// that stack. Most of its methods are about the specific node, but some methods walk up the
205/// hierarchy to provide information about the entire stack.
206#[derive(Debug)]
207pub struct UiStack {
208    // stuff that `Ui::child_ui` can deal with directly
209    pub id: Id,
210    pub info: UiStackInfo,
211    pub layout_direction: Direction,
212    pub min_rect: Rect,
213    pub max_rect: Rect,
214    pub parent: Option<Arc<Self>>,
215}
216
217// these methods act on this specific node
218impl UiStack {
219    #[inline]
220    pub fn kind(&self) -> Option<UiKind> {
221        self.info.kind
222    }
223
224    #[inline]
225    pub fn frame(&self) -> &Frame {
226        &self.info.frame
227    }
228
229    /// User tags.
230    #[inline]
231    pub fn tags(&self) -> &UiTags {
232        &self.info.tags
233    }
234
235    /// Is this [`crate::Ui`] a panel?
236    #[inline]
237    pub fn is_panel_ui(&self) -> bool {
238        self.kind().is_some_and(|kind| kind.is_panel())
239    }
240
241    /// Is this [`crate::Ui`] an [`crate::Area`]?
242    #[inline]
243    pub fn is_area_ui(&self) -> bool {
244        self.kind().is_some_and(|kind| kind.is_area())
245    }
246
247    /// Is this a root [`crate::Ui`], i.e. created with [`crate::Ui::new()`]?
248    #[inline]
249    pub fn is_root_ui(&self) -> bool {
250        self.parent.is_none()
251    }
252
253    /// This this [`crate::Ui`] a [`crate::Frame`] with a visible stroke?
254    #[inline]
255    pub fn has_visible_frame(&self) -> bool {
256        !self.info.frame.stroke.is_empty()
257    }
258
259    /// The background color of this [`crate::Ui`].
260    ///
261    /// This blend together all [`Frame::fill`] colors
262    /// up to the root.
263    #[inline]
264    pub fn bg_color(&self) -> Color32 {
265        let mut total = Color32::TRANSPARENT;
266        for node in self.iter() {
267            let fill = node.frame().fill;
268            if fill != Color32::TRANSPARENT {
269                total = fill.blend(total);
270                if total.is_opaque() {
271                    break;
272                }
273            }
274        }
275        total
276    }
277}
278
279// these methods act on the entire stack
280impl UiStack {
281    /// Return an iterator that walks the stack from this node to the root.
282    #[expect(clippy::iter_without_into_iter)]
283    pub fn iter(&self) -> UiStackIterator<'_> {
284        UiStackIterator { next: Some(self) }
285    }
286
287    /// Check if this node is or is contained in a [`crate::Ui`] of a specific kind.
288    pub fn contained_in(&self, kind: UiKind) -> bool {
289        self.iter().any(|frame| frame.kind() == Some(kind))
290    }
291}
292
293// ----------------------------------------------------------------------------
294
295/// Iterator that walks up a stack of `StackFrame`s.
296///
297/// See [`UiStack::iter`].
298pub struct UiStackIterator<'a> {
299    next: Option<&'a UiStack>,
300}
301
302impl<'a> Iterator for UiStackIterator<'a> {
303    type Item = &'a UiStack;
304
305    #[inline]
306    fn next(&mut self) -> Option<Self::Item> {
307        let current = self.next;
308        self.next = current.and_then(|frame| frame.parent.as_deref());
309        current
310    }
311}
312
313impl FusedIterator for UiStackIterator<'_> {}