Skip to main content

egui/
ui_stack.rs

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