Skip to main content

egui_reactor/
cx.rs

1//! [`Cx`]: the context threaded through every component and hook.
2
3use std::fmt::Debug;
4use std::hash::Hash;
5use std::panic::Location;
6
7use crate::engine::lite::{self, LiteCx};
8use crate::engine::{self, Reserve, TreeCx};
9use crate::layout::{ContainerStyle, ItemStyle};
10use crate::paint::PaintStyle;
11use crate::store::Store;
12
13/// Hash key derived from a `#[track_caller]` call site.
14pub(crate) fn location_key(location: &'static Location<'static>) -> (&'static str, u32, u32) {
15    (location.file(), location.line(), location.column())
16}
17
18/// Where a [`Cx`] is currently drawing.
19///
20/// Outside a `<View>` the surface is a plain egui `Ui`; inside one it is a
21/// position in the layout tree (see [`crate::engine`]), and every direct child
22/// has to become a taffy node of its own.
23enum Surface<'u> {
24    Ui(&'u mut egui::Ui),
25    Tree(TreeCx<'u>),
26    /// A position in a `<VirtualList>` row laid out without taffy. Everything
27    /// `Cx` does here it also does in [`Surface::Tree`]; only who solves the
28    /// boxes differs. See [`crate::engine::lite`].
29    Lite(LiteCx<'u>),
30}
31
32/// The context passed to components and hooks.
33///
34/// `'s` is the lifetime of the [`Store`] and of every hook handle taken from
35/// it; it is independent of the `&mut Cx` borrow, so a hook can return a guard
36/// that outlives the borrow of the `Cx` it was taken from.
37pub struct Cx<'s, 'u> {
38    /// The hook store. `Copy`, so it can be re-borrowed into nested closures.
39    pub store: &'s Store,
40    surface: Surface<'u>,
41    scope: egui::Id,
42    /// The id layout trees and nodes are keyed by.
43    ///
44    /// Normally this walks in step with `scope`, so it is the same id. It parts
45    /// from `scope` where a list draws the same shape in a reused slot: hook
46    /// state has to follow the row, while the taffy tree has to stay with the
47    /// slot so that scrolling reuses nodes instead of building new ones. See
48    /// [`Cx::with_layout_id`].
49    layout: egui::Id,
50    /// The size the next tree opened over a plain `Ui` is laid out into, if the
51    /// caller set one. See [`Cx::with_root_size`].
52    root_size: Option<egui::Vec2>,
53}
54
55impl<'s, 'u> Cx<'s, 'u> {
56    /// Build a `Cx` for `ui` under the scope id `scope`.
57    ///
58    /// This is what an egui container closure (`ui.vertical(|ui| ..)`) uses to
59    /// re-enter the tree with the inner `Ui`. The layout id starts at `scope`;
60    /// an element that re-enters inside a reused list slot puts the slot's
61    /// layout id back with [`Cx::with_layout_id`].
62    pub fn new(store: &'s Store, ui: &'u mut egui::Ui, scope: egui::Id) -> Self {
63        Self::at_ui(store, ui, scope, scope, None)
64    }
65
66    /// A `Cx` over an egui `Ui`, with both ids and the root size hint given.
67    fn at_ui(
68        store: &'s Store,
69        ui: &'u mut egui::Ui,
70        scope: egui::Id,
71        layout: egui::Id,
72        root_size: Option<egui::Vec2>,
73    ) -> Self {
74        Self {
75            store,
76            surface: Surface::Ui(ui),
77            scope,
78            layout,
79            root_size,
80        }
81    }
82
83    /// A `Cx` at a position in a lite row, with both ids given.
84    ///
85    /// The root size hint is dropped for the same reason as in
86    /// [`Cx::at_tree`]: inside a row a `<View>` is a node, and its rect comes
87    /// from the layout.
88    fn at_lite(store: &'s Store, lite: LiteCx<'u>, scope: egui::Id, layout: egui::Id) -> Self {
89        Self {
90            store,
91            surface: Surface::Lite(lite),
92            scope,
93            layout,
94            root_size: None,
95        }
96    }
97
98    /// A `Cx` at a position in a layout tree, with both ids given.
99    ///
100    /// The root size hint is dropped here: it is about the rect a tree is laid
101    /// out into, and inside a tree a `<View>` is a node whose rect the layout
102    /// decides. A tree opened by a leaf of this one starts from a plain `Ui`
103    /// again and is unaffected as well.
104    fn at_tree(store: &'s Store, tree: TreeCx<'u>, scope: egui::Id, layout: egui::Id) -> Self {
105        Self {
106            store,
107            surface: Surface::Tree(tree),
108            scope,
109            layout,
110            root_size: None,
111        }
112    }
113
114    /// The egui `Ui` currently being drawn into.
115    ///
116    /// In taffy mode this is the tree's own `Ui`, so anything drawn through it
117    /// is *not* laid out by taffy. Elements use [`Cx::leaf`] and
118    /// [`Cx::container`] instead; this is the escape hatch for user code that
119    /// wants to call egui directly, and it is what a docked `<Panel>` carves
120    /// its space out of.
121    ///
122    /// "The tree's own `Ui`" is exact: a container is a rect, not a `Ui`, so
123    /// however many `<View>`s sit between the tree root and the caller, this
124    /// is the `Ui` the tree was started in. That is what makes four sibling
125    /// `<Panel>`s dock along the window's four edges instead of each carving
126    /// up its own little node.
127    pub fn ui(&mut self) -> &mut egui::Ui {
128        match &mut self.surface {
129            Surface::Ui(ui) => ui,
130            Surface::Tree(tree) => tree.root_ui(),
131            Surface::Lite(lite) => lite.root_ui(),
132        }
133    }
134
135    /// Whether this `Cx` is inside a `<View>`.
136    ///
137    /// True on both layout paths: what an element reads it for is whether its
138    /// size is the layout's decision, and that is the same either way.
139    pub fn in_taffy(&self) -> bool {
140        matches!(self.surface, Surface::Tree(_) | Surface::Lite(_))
141    }
142
143    /// Is this `Cx` inside a `display="none"` subtree?
144    ///
145    /// True inside a hidden `<View>` on either layout path, and inside anything
146    /// a leaf of one opens (a `<ScrollArea>`'s children, a tree of their own).
147    pub fn is_hidden(&self) -> bool {
148        self.store.in_hidden()
149            || match &self.surface {
150                Surface::Ui(_) => false,
151                Surface::Tree(tree) => tree.hidden(),
152                Surface::Lite(lite) => lite.hidden(),
153            }
154    }
155
156    /// The id of the current component scope; the base of every hook id.
157    pub fn scope_id(&self) -> egui::Id {
158        self.scope
159    }
160
161    /// The id the next taffy tree or node is keyed by.
162    ///
163    /// The same as [`Cx::scope_id`] unless a list element replaced it (see
164    /// [`Cx::with_layout_id`]). `<View>` passes this to [`Cx::container`].
165    pub fn layout_id(&self) -> egui::Id {
166        self.layout
167    }
168
169    /// Run `f` with `id` as the layout id, leaving hooks and the egui id stack
170    /// alone.
171    ///
172    /// This is an internal for list elements, not something a component body
173    /// needs. `<VirtualList>` uses it to key each row's taffy tree by the slot
174    /// the row occupies rather than by the row's index: a slot is drawn on
175    /// every frame, so its nodes are reused instead of created, measured in an
176    /// invisible pass and thrown away when the row scrolls out.
177    ///
178    /// The ids handed to this must be unique among the nodes drawn in one
179    /// frame; two of them under one taffy tree would collide.
180    pub fn with_layout_id<R>(&mut self, id: egui::Id, f: impl FnOnce(&mut Cx<'s, '_>) -> R) -> R {
181        let store = self.store;
182        let scope = self.scope;
183        let root_size = self.root_size;
184        match &mut self.surface {
185            Surface::Ui(ui) => {
186                let mut cx = Cx::at_ui(store, ui, scope, id, root_size);
187                f(&mut cx)
188            }
189            // Inside a tree the layout id is what an unnamed node hashes into
190            // its key, so swapping it moves the whole subtree's nodes with it.
191            // Nothing else changes: no `Ui` is pushed.
192            Surface::Tree(tree) => {
193                let mut cx = Cx::at_tree(store, tree.reborrow(), scope, id);
194                f(&mut cx)
195            }
196            Surface::Lite(lite) => {
197                let mut cx = Cx::at_lite(store, lite.reborrow(), scope, id);
198                f(&mut cx)
199            }
200        }
201    }
202
203    /// Run `f` with the size the trees it opens are laid out into.
204    ///
205    /// This is an internal for list elements, like [`Cx::with_layout_id`], not
206    /// something a component body needs.
207    ///
208    /// A `<View>` over a plain `Ui` normally takes the width that is left,
209    /// measures its own height and reserves that much room. Neither is what a
210    /// `<VirtualList>` row wants. Inside `egui::ScrollArea::show_rows` the
211    /// space that is left runs from the row to the bottom of the band of
212    /// visible rows, so the root rect moves with the scroll offset; and the
213    /// room reserved is whatever the row drew, which is not the `row_h` the
214    /// visible range was worked out from, so the rows drift out of step with
215    /// it.
216    ///
217    /// With a size given, the tree's root rect is that size at the cursor, both
218    /// axes are definite, and exactly that much room is reserved afterwards. So
219    /// the rows sit at one pitch, and the root size is the same on every frame:
220    /// a scrolled frame lays a row out again only when something in the row
221    /// really changed.
222    ///
223    /// The size wins over what the tree measured. A tree that draws taller than
224    /// this overlaps whatever comes after it; `<VirtualList>` says the same
225    /// thing about its rows, and it is the same rule.
226    ///
227    /// **The hint is not consumed by the first tree**: every tree `f` opens
228    /// over a plain `Ui` gets it, at any depth of components, until a tree is
229    /// entered — inside a tree the hint is dropped, so a tree opened by a leaf
230    /// of this one is unaffected. Consuming it would mean shared mutable state,
231    /// because a `Cx` is copied into each child scope rather than borrowed, and
232    /// it would buy nothing: the caller's contract is that everything drawn
233    /// here is one row of a fixed height, so a second root tree in the same row
234    /// is already a mistake either way.
235    pub fn with_root_size<R>(
236        &mut self,
237        size: egui::Vec2,
238        f: impl FnOnce(&mut Cx<'s, '_>) -> R,
239    ) -> R {
240        let (store, scope, layout) = (self.store, self.scope, self.layout);
241        match &mut self.surface {
242            Surface::Ui(ui) => {
243                let mut cx = Cx::at_ui(store, ui, scope, layout, Some(size));
244                f(&mut cx)
245            }
246            // Nothing to do in tree mode: a `<View>` here is a node, and its
247            // rect comes from the layout.
248            Surface::Tree(tree) => {
249                let mut cx = Cx::at_tree(store, tree.reborrow(), scope, layout);
250                f(&mut cx)
251            }
252            Surface::Lite(lite) => {
253                let mut cx = Cx::at_lite(store, lite.reborrow(), scope, layout);
254                f(&mut cx)
255            }
256        }
257    }
258
259    /// The egui context.
260    pub fn ctx(&self) -> &egui::Context {
261        match &self.surface {
262            Surface::Ui(ui) => ui.ctx(),
263            Surface::Tree(tree) => tree.ctx(),
264            Surface::Lite(lite) => lite.ctx(),
265        }
266    }
267
268    /// Enter a component scope: deepen the hook scope, the layout id and the
269    /// egui id.
270    ///
271    /// `source` needs `Debug` as well as `Hash` because egui 0.36's
272    /// `Ui::push_id` takes `impl AsIdSalt`, which is `Hash + Debug`.
273    pub fn scope<R>(
274        &mut self,
275        source: impl Hash + Debug,
276        f: impl FnOnce(&mut Cx<'s, '_>) -> R,
277    ) -> R {
278        let store = self.store;
279        let scope = self.scope.with(&source);
280        let layout = self.layout.with(&source);
281        let root_size = self.root_size;
282        match &mut self.surface {
283            Surface::Ui(ui) => {
284                ui.push_id(source, |ui| {
285                    let mut cx = Cx::at_ui(store, ui, scope, layout, root_size);
286                    f(&mut cx)
287                })
288                .inner
289            }
290            // No `Ui` is pushed in tree mode: a node is a rect, and the only
291            // `Ui` in the tree is the root's. Deepening the two ids is what
292            // separates two instances of the same subtree — the layout id
293            // keys their nodes, and the hook scope salts their leaves' `Ui`s.
294            Surface::Tree(tree) => {
295                let mut cx = Cx::at_tree(store, tree.reborrow(), scope, layout);
296                f(&mut cx)
297            }
298            Surface::Lite(lite) => {
299                let mut cx = Cx::at_lite(store, lite.reborrow(), scope, layout);
300                f(&mut cx)
301            }
302        }
303    }
304
305    /// Enter a custom hook scope: deepen the hook scope only.
306    ///
307    /// Unlike [`Cx::scope`] this does not touch the egui id stack, because a
308    /// custom hook is not a widget boundary.
309    pub fn hook_scope<R>(
310        &mut self,
311        location: &'static Location<'static>,
312        f: impl FnOnce(&mut Cx<'s, '_>) -> R,
313    ) -> R {
314        self.scope_sharing_ui(location_key(location), f)
315    }
316
317    /// Enter a component scope that draws into *this* `Ui`.
318    ///
319    /// Same hook scoping as [`Cx::scope`], but without `Ui::push_id`, so the
320    /// component shares the surface with its parent. `#[component(shares_ui)]`
321    /// elements go through here: a docked panel has to carve space out of the
322    /// parent's `Ui`, and `Ui::end_row` only reaches the grid it was called on.
323    pub fn scope_sharing_ui<R>(
324        &mut self,
325        source: impl Hash + Debug,
326        f: impl FnOnce(&mut Cx<'s, '_>) -> R,
327    ) -> R {
328        let store = self.store;
329        let scope = self.scope.with(&source);
330        // The layout id deepens here too, and it has to: two sibling
331        // `shares_ui` components draw into one `Ui`, so if both kept the
332        // parent's layout id their `<View>`s would ask for the same taffy tree
333        // and collide. `source` is a call site or a hook's location, never a
334        // row index, so nothing positional leaks in.
335        let layout = self.layout.with(&source);
336        let root_size = self.root_size;
337        let mut cx = Cx {
338            store,
339            surface: self.reborrow(),
340            scope,
341            layout,
342            root_size,
343        };
344        f(&mut cx)
345    }
346
347    /// Draw one egui widget, as a taffy leaf when inside a container.
348    ///
349    /// Outside a container `style` is ignored: a plain `Ui` has nothing to
350    /// apply flex item properties to, and nothing to paint a background on
351    /// either — the engine paints a node's box from the rect the layout gave
352    /// it, and here the rect is only known after the widget has drawn. That is
353    /// what `<Frame>` is for.
354    pub fn leaf<R>(&mut self, style: &ItemStyle, f: impl FnOnce(&mut egui::Ui) -> R) -> R {
355        let (prefix, scope) = (self.layout, self.scope);
356        // Held while the widget draws, so a tree it opens over its own `Ui`
357        // starts hidden as well.
358        let _hidden = self.is_hidden().then(|| self.store.enter_hidden());
359        match &mut self.surface {
360            Surface::Ui(ui) => f(ui),
361            Surface::Tree(tree) => tree.leaf(prefix, scope, style.to_taffy(), style.paint, true, f),
362            Surface::Lite(lite) => lite.leaf(scope, style, true, f),
363        }
364    }
365
366    /// Draw static text, as a taffy node when inside a container.
367    ///
368    /// Outside a container this is `ui.add(egui::Label::new(text))` and
369    /// nothing else. Inside one it skips the `Label` and the `Ui` it would need:
370    /// the galley is laid out by the layout engine, which is where its size is
371    /// wanted anyway, and painted straight onto the tree's own `Ui`. What the
372    /// engine keeps of `Label` is the widget rect, the `WidgetInfo` and the
373    /// text selection, so the text is still hoverable, still in the
374    /// accessibility tree, still found by `egui_kittest`'s label queries and
375    /// still selectable with the mouse.
376    ///
377    /// `wrap` picks between `TextWrapMode::Wrap` and `TextWrapMode::Extend`,
378    /// as `<Text>`'s own prop does. `selectable` is `Label::selectable`:
379    /// `None` follows the style's `interaction.selectable_labels`. A `<Text>`
380    /// created this frame is not selectable for that one frame, because its
381    /// place on screen is only known once the layout is computed.
382    pub fn text(
383        &mut self,
384        style: &ItemStyle,
385        text: egui::WidgetText,
386        wrap: bool,
387        selectable: Option<bool>,
388    ) -> egui::Response {
389        let (prefix, scope) = (self.layout, self.scope);
390        let hidden = self.is_hidden();
391        match &mut self.surface {
392            Surface::Ui(ui) => {
393                if hidden {
394                    // Inside a hidden leaf: no `Label`, so no galley, no shape
395                    // and no accesskit node. The auto id is stepped over all
396                    // the same, so the widgets after this one keep their ids.
397                    let id = ui.next_auto_id();
398                    ui.skip_ahead_auto_ids(1);
399                    return ui.interact(egui::Rect::NOTHING, id, egui::Sense::hover());
400                }
401                let wrap_mode = if wrap {
402                    egui::TextWrapMode::Wrap
403                } else {
404                    egui::TextWrapMode::Extend
405                };
406                let mut label = egui::Label::new(text).wrap_mode(wrap_mode);
407                if let Some(selectable) = selectable {
408                    label = label.selectable(selectable);
409                }
410                ui.add(label)
411            }
412            Surface::Tree(tree) => tree.text(
413                prefix,
414                scope,
415                style.to_taffy(),
416                style.paint,
417                text,
418                wrap,
419                selectable,
420            ),
421            Surface::Lite(lite) => lite.text(scope, style, text, wrap, selectable),
422        }
423    }
424
425    /// Like [`Cx::leaf`], but the leaf takes whatever space taffy gives it.
426    ///
427    /// A plain leaf is measured by its content: taffy sizes it to what it drew
428    /// and never larger. That is wrong for egui widgets that themselves fill
429    /// the space they are given (`ScrollArea`): the widget fills its node, then
430    /// reports that as its content size, and the node is pinned at whatever
431    /// size the first frame happened to have. This variant reports no content
432    /// size at all, so the node is sized purely by taffy: `w` / `h`, `grow`,
433    /// or the remaining space in the container.
434    pub fn leaf_fill<R>(&mut self, style: &ItemStyle, f: impl FnOnce(&mut egui::Ui) -> R) -> R {
435        let (prefix, scope) = (self.layout, self.scope);
436        let _hidden = self.is_hidden().then(|| self.store.enter_hidden());
437        match &mut self.surface {
438            Surface::Ui(ui) => f(ui),
439            Surface::Tree(tree) => {
440                tree.leaf(prefix, scope, style.to_taffy(), style.paint, false, f)
441            }
442            Surface::Lite(lite) => lite.leaf(scope, style, false, f),
443        }
444    }
445
446    /// Open a container and draw `f` inside it.
447    ///
448    /// Outside a container this starts a new layout tree in the current `Ui`;
449    /// inside one it adds a child node. Either way `f` receives a `Cx` that is
450    /// inside a container, so its direct children become layout nodes.
451    ///
452    /// The two styles are passed as they are rather than merged into a
453    /// [`taffy::Style`], because a `<VirtualList>` row is laid out by the lite
454    /// path (`crate::engine::lite`), which reads them directly and never builds
455    /// a taffy style at all. The taffy path merges them itself.
456    pub fn container<R>(
457        &mut self,
458        id: egui::Id,
459        container: &ContainerStyle,
460        item: &ItemStyle,
461        f: impl FnOnce(&mut Cx<'s, '_>) -> R,
462    ) -> R {
463        let store = self.store;
464        let scope = self.scope;
465        let layout = self.layout;
466
467        // A row of a `<VirtualList>`: a fixed rect over a plain `Ui`. That is
468        // the one place the lite path applies, and only while every style in
469        // the row is inside its subset.
470        if let Surface::Ui(ui) = &mut self.surface
471            && let Some(size) = self.root_size
472            && !store.taffy_rows_forced()
473        {
474            let tree = store.lite_tree(id);
475            if lite::supported(&tree, container, item) {
476                let hidden = store.in_hidden();
477                return lite::show(&tree, ui, container, item, size, hidden, |lite| {
478                    let mut cx = Cx::at_lite(store, lite.reborrow(), scope, layout);
479                    f(&mut cx)
480                });
481            }
482        }
483
484        if let Surface::Lite(lite) = &mut self.surface {
485            return lite.container(container, item, |lite| {
486                let mut cx = Cx::at_lite(store, lite.reborrow(), scope, layout);
487                f(&mut cx)
488            });
489        }
490
491        self.container_taffy(id, container.merge(item), item.paint, false, f)
492    }
493
494    /// [`Cx::container`] for the root of an app: reserve *all* available space.
495    ///
496    /// The runner uses this so that the outermost `<View>` fills the window;
497    /// nested containers only reserve the available width, so that a column of
498    /// them stacks instead of each one claiming the whole height.
499    ///
500    /// Takes a [`taffy::Style`], unlike [`Cx::container`]: an app root is never
501    /// a `<VirtualList>` row, so it never takes the lite path, and the runner's
502    /// `root_style()` is a taffy style users can reach for. A taffy style says
503    /// nothing about paint, so the root node paints nothing; a `<View>` inside
504    /// it does.
505    pub fn root_container<R>(
506        &mut self,
507        id: egui::Id,
508        style: taffy::Style,
509        f: impl FnOnce(&mut Cx<'s, '_>) -> R,
510    ) -> R {
511        self.container_taffy(id, style, PaintStyle::default(), true, f)
512    }
513
514    fn container_taffy<R>(
515        &mut self,
516        id: egui::Id,
517        style: taffy::Style,
518        paint: PaintStyle,
519        all_space: bool,
520        f: impl FnOnce(&mut Cx<'s, '_>) -> R,
521    ) -> R {
522        let store = self.store;
523        let scope = self.scope;
524        let layout = self.layout;
525        // A size set by `with_root_size` wins over both: the caller knows the
526        // rect, so nothing is taken from the `Ui`.
527        let reserve = match (self.root_size, all_space) {
528            (Some(size), _) => Reserve::Fixed(size),
529            (None, true) => Reserve::AllSpace,
530            (None, false) => Reserve::Content,
531        };
532        match &mut self.surface {
533            Surface::Ui(ui) => engine::show(store, ui, id, style, paint, reserve, |tree| {
534                let mut cx = Cx::at_tree(store, tree.reborrow(), scope, layout);
535                f(&mut cx)
536            }),
537            Surface::Tree(tree) => tree.container(id, style, paint, |tree| {
538                let mut cx = Cx::at_tree(store, tree.reborrow(), scope, layout);
539                f(&mut cx)
540            }),
541            // A raw taffy style inside a lite row, which only
542            // [`Cx::root_container`] can be: the solver cannot read it, so the
543            // row moves to the taffy path and this frame is drawn again.
544            Surface::Lite(lite) => lite.fall_back_container(|lite| {
545                let mut cx = Cx::at_lite(store, lite.reborrow(), scope, layout);
546                f(&mut cx)
547            }),
548        }
549    }
550
551    /// Queue `f` to run at the end of the pass, after every guard is gone.
552    ///
553    /// The closure is `'static` because it outlives the component body, so
554    /// captured values need `move`. It cannot touch the store, so it does not
555    /// request a repaint on its own; use
556    /// [`State::update_later`](crate::State::update_later) for that.
557    pub fn defer(&self, f: impl FnOnce() + 'static) {
558        self.store.defer_raw(Box::new(move |_store| f()));
559    }
560
561    /// Re-borrow the surface for a shorter lifetime.
562    fn reborrow(&mut self) -> Surface<'_> {
563        match &mut self.surface {
564            Surface::Ui(ui) => Surface::Ui(ui),
565            Surface::Tree(tree) => Surface::Tree(tree.reborrow()),
566            Surface::Lite(lite) => Surface::Lite(lite.reborrow()),
567        }
568    }
569}