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}