agg_gui/widget.rs
1//! Widget trait, tree traversal, and the top-level [`App`] struct.
2//!
3//! # Coordinate system
4//!
5//! Widget bounds are expressed in **parent-local** first-quadrant (Y-up)
6//! coordinates. A widget at `bounds.x = 10, bounds.y = 20` is drawn 10 units
7//! right and 20 units up from its parent's bottom-left corner.
8//!
9//! OS/browser mouse events arrive in Y-down screen coordinates. The single
10//! conversion `y_up = viewport_height - y_down` happens inside
11//! [`App::on_mouse_move`] / [`App::on_mouse_down`] / [`App::on_mouse_up`].
12//! All widget code sees Y-up coordinates only.
13//!
14//! # Tree traversal
15//!
16//! Paint: root → leaves (children painted on top of parents).
17//! Hit test: root → leaves (deepest child under cursor wins).
18//! Event dispatch: leaf → root (events bubble up; any widget can consume).
19
20use crate::draw_ctx::DrawCtx;
21use crate::event::{Event, EventResult, Key, Modifiers};
22use crate::geometry::{Point, Rect, Size};
23use crate::layout_props::{HAnchor, Insets, VAnchor, WidgetBase};
24
25// ---------------------------------------------------------------------------
26// Widget trait
27// ---------------------------------------------------------------------------
28
29/// Every visible element in the UI is a widget.
30///
31/// Implementors handle their own painting and event handling. The framework
32/// takes care of tree traversal, coordinate translation, and focus management.
33pub trait Widget {
34 /// Bounding rectangle in **parent-local** Y-up coordinates.
35 fn bounds(&self) -> Rect;
36
37 /// Set the bounding rectangle. Called by the parent during layout.
38 fn set_bounds(&mut self, bounds: Rect);
39
40 /// Immutable access to child widgets.
41 fn children(&self) -> &[Box<dyn Widget>];
42
43 /// Mutable access to child widgets (required for event dispatch + layout).
44 fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>>;
45
46 /// Compute desired size given available space, and update internal layout.
47 ///
48 /// The parent passes the space it can offer; the widget returns the size it
49 /// actually wants to occupy. The parent uses the returned size to set this
50 /// widget's bounds before calling `layout` on the next sibling.
51 fn layout(&mut self, available: Size) -> Size;
52
53 /// Paint this widget's own content into `ctx`.
54 ///
55 /// The framework has already translated `ctx` so that `(0, 0)` is this
56 /// widget's bottom-left corner. **Do not paint children here** — the
57 /// framework recurses into them automatically after `paint` returns.
58 ///
59 /// `ctx` is a `&mut dyn DrawCtx`; the concrete type is either a software
60 /// `GfxCtx` (back-buffer path) or a `GlGfxCtx` (hardware GL path).
61 fn paint(&mut self, ctx: &mut dyn DrawCtx);
62
63 /// Return `true` if `local_pos` (in this widget's local coordinates) falls
64 /// inside this widget's interactive area. Default: axis-aligned rect test.
65 fn hit_test(&self, local_pos: Point) -> bool {
66 let b = self.bounds();
67 local_pos.x >= 0.0
68 && local_pos.x <= b.width
69 && local_pos.y >= 0.0
70 && local_pos.y <= b.height
71 }
72
73 /// When `true`, `hit_test_subtree` stops recursing into this widget's
74 /// children and returns this widget as the hit target. Used for floating
75 /// overlays (e.g. a scrollbar painted above its content) that must claim
76 /// the pointer before children that happen to share the same pixels.
77 /// Default: `false`.
78 fn claims_pointer_exclusively(&self, _local_pos: Point) -> bool {
79 false
80 }
81
82 /// When `true`, this widget disables *all* interaction — pointer and
83 /// keyboard — within its subtree, reproducing egui's
84 /// `UiBuilder::disabled()`. Unlike [`claims_pointer_exclusively`], this is
85 /// a position-independent state predicate: pointer hit-testing stops at
86 /// this widget (so clicks are swallowed rather than passing through), and
87 /// focus collection skips the whole subtree (so Tab cannot reach a child).
88 /// Default: `false`.
89 fn blocks_child_interaction(&self) -> bool {
90 false
91 }
92
93 /// Return true when `local_pos` hits an app-level overlay owned by this
94 /// widget. Unlike normal hit testing, ancestors may be missed because the
95 /// overlay is painted outside their bounds.
96 fn hit_test_global_overlay(&self, _local_pos: Point) -> bool {
97 false
98 }
99
100 /// Whether this widget currently owns an app-modal interaction layer.
101 ///
102 /// When true anywhere in the tree, [`App`](crate::App) routes pointer and
103 /// key events to that modal subtree before normal hit testing so content
104 /// underneath the modal backdrop cannot be interacted with.
105 fn has_active_modal(&self) -> bool {
106 false
107 }
108
109 /// Handle an event. The event's positions are already in **local** Y-up
110 /// coordinates. Return [`EventResult::Consumed`] to stop bubbling.
111 ///
112 /// # Invalidation contract
113 ///
114 /// If your handler mutates state that affects the next paint (hover
115 /// index, focus, button-pressed bool, animation phase, ...), call
116 /// [`crate::animation::request_draw`] from inside the handler. That
117 /// bumps the invalidation epoch, which `dispatch_event` reads to mark
118 /// retained ancestor backbuffers dirty — without it, the cached
119 /// bitmap composites unchanged and your state mutation is invisible
120 /// until something else dirties the cache.
121 ///
122 /// Returning `Consumed` *also* dirties the ancestor path automatically,
123 /// so a consumed click handler that calls `request_draw` is belt-and-
124 /// suspenders. But `MouseMove` handlers that return `Ignored` (the
125 /// usual case for hover-tracking) **only** invalidate via
126 /// `request_draw`'s epoch bump. Forgetting it produces "hover only
127 /// works the first time after I drag the window resize edge" bugs.
128 ///
129 /// `request_draw_without_invalidation` is for the rare cases where
130 /// the visual change is in an app-overlay or a position-only
131 /// composite — see its rustdoc.
132 fn on_event(&mut self, event: &Event) -> EventResult;
133
134 /// Handle a key that was not consumed by the focused widget path.
135 ///
136 /// This is used for window/menu accelerators: focused controls get first
137 /// chance at the key, then visible widgets in paint order may claim it.
138 fn on_unconsumed_key(&mut self, _key: &Key, _modifiers: Modifiers) -> EventResult {
139 EventResult::Ignored
140 }
141
142 /// Whether this widget can receive keyboard focus. Default: false.
143 fn is_focusable(&self) -> bool {
144 false
145 }
146
147 /// Stable identifier for the programmatic focus channel
148 /// ([`crate::focus::request_focus`]).
149 ///
150 /// App code can't reach the [`App`](crate::widget::App)'s private focus
151 /// path to focus a widget the moment it appears (e.g. a search field
152 /// that should grab the keyboard when its overlay opens). A widget that
153 /// returns `Some(id)` here can be focused by calling
154 /// [`crate::focus::request_focus(id)`](crate::focus::request_focus); the
155 /// `App` services the request on its next `layout`, moving focus to the
156 /// matching focusable widget (which dispatches `FocusGained` and raises
157 /// the on-screen keyboard for text inputs). Default: `None`.
158 fn focus_id(&self) -> Option<crate::focus::FocusId> {
159 None
160 }
161
162 /// A static name for this widget type, used by the inspector. Default: "Widget".
163 fn type_name(&self) -> &'static str {
164 "Widget"
165 }
166
167 /// Optional human-readable identifier for this widget instance.
168 ///
169 /// Distinct from [`type_name`] (which is per-type and constant):
170 /// `id` lets external code look up a specific *instance* — used
171 /// today by the demo's z-order persistence to match a saved title
172 /// against a live `Window` in the canvas `Stack`. Default
173 /// implementation returns `None`; widgets that want to be
174 /// identifiable (e.g. `Window` returning its title) override.
175 fn id(&self) -> Option<&str> {
176 None
177 }
178
179 /// Return `false` to suppress painting this widget **and all its children**.
180 /// The widget's own `paint()` will not be called. Default: `true`.
181 fn is_visible(&self) -> bool {
182 true
183 }
184
185 /// Return type-specific properties for the inspector properties pane.
186 ///
187 /// Each entry is `(name, display_value)`. The default returns an empty
188 /// list; widgets override this to expose their state to the inspector.
189 fn properties(&self) -> Vec<(&'static str, String)> {
190 vec![]
191 }
192
193 /// `true` when this widget accepts free-form character input (typing
194 /// arbitrary letters, numbers, punctuation). Used by the on-screen
195 /// software keyboard (`crate::widgets::on_screen_keyboard`) to decide
196 /// whether to slide up when this widget gains focus.
197 ///
198 /// Default is `false`. `TextField` and `TextArea` override to `true`.
199 /// `DragValue` and similar numeric editors should override to `true`
200 /// only when they are in their full-text-edit mode; otherwise the
201 /// keyboard would appear for transient drag interactions.
202 ///
203 /// This is independent of [`is_focusable`](Self::is_focusable) — a
204 /// `Button` is focusable but doesn't accept typed text.
205 fn accepts_text_input(&self) -> bool {
206 false
207 }
208
209 /// Current text contents of this widget if it is text-bearing.
210 /// Used by the on-screen software keyboard to apply the
211 /// sentence-start auto-capitalize heuristic: an empty field (or one
212 /// ending in `.`, `!`, `?`, newline) opens the keyboard with Shift
213 /// active.
214 ///
215 /// Default is `None`. `TextField` and `TextArea` override to return
216 /// their current text. Callers that only need to know *whether* the
217 /// widget accepts text input should use
218 /// [`accepts_text_input`](Self::accepts_text_input).
219 fn text_input_value(&self) -> Option<String> {
220 None
221 }
222
223 /// Preferred keyboard input mode for this widget — used by the
224 /// on-screen software keyboard to pick the initial layer when this
225 /// widget gains focus. Default is
226 /// [`KeyboardInputMode::Text`](crate::widgets::on_screen_keyboard::KeyboardInputMode::Text);
227 /// numeric fields override to
228 /// [`Numeric`](crate::widgets::on_screen_keyboard::KeyboardInputMode::Numeric)
229 /// so the digit pad slides up instead of the letter row.
230 ///
231 /// Only consulted when [`accepts_text_input`](Self::accepts_text_input)
232 /// also returns `true`.
233 fn text_input_mode(&self) -> crate::widgets::on_screen_keyboard::KeyboardInputMode {
234 crate::widgets::on_screen_keyboard::KeyboardInputMode::Text
235 }
236
237 /// Try to lift this widget's visible content upward by `amount`
238 /// pixels. Used by the on-screen-keyboard auto-scroll so a
239 /// focused text field doesn't end up hidden behind the keyboard
240 /// panel: the App walks UP the focus path and asks each ancestor
241 /// to absorb some of the deficit.
242 ///
243 /// Scrolling containers (notably
244 /// [`ScrollView`](crate::widgets::ScrollView)) override this and
245 /// increase their vertical scroll offset by up to `amount`,
246 /// returning how much they actually applied (clamped to their
247 /// remaining slack). Negative `amount` reverses the operation
248 /// (used to restore scroll when focus leaves a text-input).
249 /// Default returns `0.0` — non-scrolling widgets contribute
250 /// nothing.
251 fn try_scroll_to_lift(&mut self, _amount: f64) -> f64 {
252 0.0
253 }
254
255 /// If this widget is text-bearing (e.g. `Label`), update its foreground
256 /// colour. Default is a no-op. Composite widgets call this on their
257 /// children to retint labels without rebuilding them — used by `Button`
258 /// when toggling between active (white text on accent) and inactive
259 /// (theme text on subtle bg) appearances.
260 fn set_label_color(&mut self, _color: crate::color::Color) {}
261
262 /// If this widget is text-bearing (e.g. `Label`), update its
263 /// displayed text. Default is a no-op. Composite widgets that
264 /// own a `Label` child use this to push live values (e.g. an FPS
265 /// counter) into the child without bypassing the standard
266 /// backbuffered glyph cache — calling this on a `Label` only
267 /// invalidates the cache when the text actually changed.
268 fn set_label_text(&mut self, _text: &str) {}
269
270 /// Opt-in reflection accessor for the inspector's typed property editors.
271 ///
272 /// Widgets that derive [`bevy_reflect::Reflect`] (via the `reflect`
273 /// cargo feature) override this to return `Some(self)` so the inspector
274 /// can walk their fields with type information — boolean toggles,
275 /// numeric sliders, color pickers, enum dropdowns — instead of falling
276 /// back to the read-only string [`properties`](Self::properties) list.
277 ///
278 /// Default returns `None`; the inspector then uses the string list.
279 /// Available only with the `reflect` feature so consumers without it
280 /// don't pay the dependency cost.
281 #[cfg(feature = "reflect")]
282 fn as_reflect(&self) -> Option<&dyn bevy_reflect::Reflect> {
283 None
284 }
285
286 /// Mutable counterpart of [`as_reflect`](Self::as_reflect). Used by the
287 /// inspector to write edits back into the live widget.
288 #[cfg(feature = "reflect")]
289 fn as_reflect_mut(&mut self) -> Option<&mut dyn bevy_reflect::Reflect> {
290 None
291 }
292
293 /// Whether this widget renders into its own offscreen buffer before
294 /// compositing into the parent.
295 ///
296 /// When `true`, `paint_subtree` wraps the widget (and all its descendants)
297 /// in `ctx.push_layer` / `ctx.pop_layer`. The widget and its children draw
298 /// into a fresh transparent framebuffer; when complete, the buffer is
299 /// SrcOver-composited back into the parent render target. This enables
300 /// per-widget alpha compositing, caching, and isolation.
301 ///
302 /// Default: `false` (pass-through rendering).
303 fn has_backbuffer(&self) -> bool {
304 false
305 }
306
307 /// Request that this widget subtree be painted into a transient
308 /// transparent compositing layer before being blended into its parent.
309 ///
310 /// Renderers that do not implement real layers ignore this hook. The
311 /// method is mutable so widgets can advance visibility tweens at the
312 /// point where the traversal knows the layer will be painted.
313 fn compositing_layer(&mut self) -> Option<CompositingLayer> {
314 None
315 }
316
317 /// Unified widget-owned backbuffer request.
318 fn backbuffer_spec(&mut self) -> BackbufferSpec {
319 let mode = self.backbuffer_mode();
320 if self.backbuffer_cache_mut().is_some() {
321 BackbufferSpec {
322 kind: match mode {
323 BackbufferMode::Rgba => BackbufferKind::SoftwareRgba,
324 BackbufferMode::LcdCoverage => BackbufferKind::SoftwareLcd,
325 },
326 cached: true,
327 alpha: 1.0,
328 outsets: Insets::ZERO,
329 rounded_clip: None,
330 }
331 } else {
332 BackbufferSpec::none()
333 }
334 }
335
336 /// Mutable retained backbuffer state for widgets that request a
337 /// [`BackbufferSpec`] other than [`BackbufferKind::None`].
338 fn backbuffer_state_mut(&mut self) -> Option<&mut BackbufferState> {
339 None
340 }
341
342 /// Mark this widget's own retained surface dirty, if it owns one.
343 ///
344 /// Invalidates *both* the retained-layer [`BackbufferState`] and the
345 /// per-widget bitmap [`BackbufferCache`]. Inspector edits that mutate
346 /// a widget's reflected props bypass setters that normally invalidate
347 /// the cache (e.g. `Label::set_text`); calling `mark_dirty` after an
348 /// edit restores correct re-raster on the next frame.
349 fn mark_dirty(&mut self) {
350 if let Some(state) = self.backbuffer_state_mut() {
351 state.invalidate();
352 }
353 if let Some(cache) = self.backbuffer_cache_mut() {
354 cache.invalidate();
355 }
356 }
357
358 /// Opt into per-widget CPU bitmap caching with a dirty flag.
359 ///
360 /// Widgets that return `Some(&mut cache)` get their paint +
361 /// children cached as a `Vec<u8>` of RGBA8 pixels. `paint_subtree`
362 /// re-rasterises via AGG only when `cache.dirty` is true; otherwise
363 /// it blits the existing bitmap. GL backends key their texture
364 /// cache on the `Arc`'s pointer identity so the uploaded GPU
365 /// texture is also reused across frames.
366 ///
367 /// The widget is responsible for calling `cache.invalidate()` (or
368 /// setting `cache.dirty = true`) from any mutation that could
369 /// change the rendered output — text/color setters, focus/hover
370 /// state changes, layout size changes, etc. The framework clears
371 /// the flag after a successful re-raster.
372 ///
373 /// Default: `None` (no caching — paint every frame directly).
374 fn backbuffer_cache_mut(&mut self) -> Option<&mut BackbufferCache> {
375 None
376 }
377
378 /// Storage format for this widget's backbuffer. Ignored unless
379 /// [`backbuffer_cache_mut`] returns `Some`. Default
380 /// [`BackbufferMode::Rgba`] — correct for any widget.
381 /// Opt into [`BackbufferMode::LcdCoverage`] only when the widget
382 /// paints opaque content covering its full bounds.
383 fn backbuffer_mode(&self) -> BackbufferMode {
384 BackbufferMode::Rgba
385 }
386
387 /// Whether the inspector should recurse into this widget's children.
388 ///
389 /// Returns `false` for widgets that are part of the inspector infrastructure
390 /// (e.g. the inspector's own `TreeView`) to prevent the inspector from
391 /// showing itself recursively, which would grow the node list every frame.
392 ///
393 /// The widget itself is still included in the inspector snapshot — only
394 /// its subtree is suppressed.
395 fn contributes_children_to_inspector(&self) -> bool {
396 true
397 }
398
399 /// Return `false` to hide this widget (and its subtree) from the inspector
400 /// node snapshot entirely. Intended for zero-size utility widgets such
401 /// as layout-time watchers / tickers / invisible composers — they bloat
402 /// the inspector tree without providing user-relevant information and,
403 /// at scale, can make the inspector's per-frame tree rebuild expensive.
404 fn show_in_inspector(&self) -> bool {
405 true
406 }
407
408 /// Per-widget LCD subpixel preference for backbuffered text rendering.
409 ///
410 /// - `Some(true)` — always raster text with LCD subpixel.
411 /// - `Some(false)` — always use grayscale AA.
412 /// - `None` — defer to the global `font_settings::lcd_enabled()`.
413 ///
414 /// Only widgets that raster text into an offscreen backbuffer act on
415 /// this flag (today: `Label`). Defaulting to `None` means every such
416 /// widget follows the global toggle unless the instance explicitly
417 /// opts in or out.
418 fn lcd_preference(&self) -> Option<bool> {
419 None
420 }
421
422 /// Paint decorations that must appear **on top of all children**.
423 ///
424 /// Called by [`paint_subtree`] after all children have been painted.
425 /// The default implementation is a no-op; override in widgets that need
426 /// to draw overlays (e.g. resize handles, drag previews) that must not
427 /// be occluded by child content.
428 fn paint_overlay(&mut self, _ctx: &mut dyn DrawCtx) {}
429
430 /// Called after `paint`, child painting, and optional overlay painting.
431 ///
432 /// Most widgets do not need this. It exists for widgets that intentionally
433 /// open a backend compositing scope in `paint` and must close it after all
434 /// descendants have rendered into that scope.
435 fn finish_paint(&mut self, _ctx: &mut dyn DrawCtx) {}
436
437 /// Paint app-level overlays after the entire widget tree has been painted.
438 ///
439 /// The traversal preserves this widget's local transform but skips ancestor
440 /// clips and retained parent redraw requirements. Use this for portal-style
441 /// UI that draws outside normal bounds while still participating in the
442 /// widget tree's Z order.
443 fn paint_global_overlay(&mut self, _ctx: &mut dyn DrawCtx) {}
444
445 /// Opt this widget's *entire subtree* out of the normal inline paint pass so
446 /// it renders in [`paint_global_overlay`](Self::paint_global_overlay)
447 /// instead — the same clip-escaping global pass used by menus and tooltips.
448 ///
449 /// When this returns `true` and the widget is visible, [`paint_subtree`]
450 /// skips it during the ordinary tree walk (nothing, including children, is
451 /// painted inline), and the widget is expected to paint itself in its
452 /// `paint_global_overlay` via [`paint_subtree_forced`]. Because the global
453 /// overlay walk applies only per-widget translations (never ancestor
454 /// clips), the subtree then floats above and outside any ancestor's clip —
455 /// which is exactly what a modal dialog nested inside a scrolled/clipped
456 /// container needs so it can't be truncated by its host.
457 ///
458 /// Default `false`: ordinary widgets paint inline as usual.
459 ///
460 /// # Z-order caveat for deferred hosts
461 ///
462 /// The global-overlay walk is post-order (a parent's
463 /// `paint_global_overlay` runs *after* its descendants'), so a deferred
464 /// widget's body — painted in its own `paint_global_overlay` — draws on top
465 /// of anything a descendant painted *directly* in an earlier
466 /// `paint_global_overlay` (e.g. a `menu`/`PopupMenu` surface, the markdown
467 /// link layer, or a nested modal). Descendants that instead submit to a
468 /// drained queue (`ComboBox` popups, `Tooltip`) are unaffected — those
469 /// queues drain after the whole overlay walk. Today the colour dialog's
470 /// content uses only queue-based descendants, so it's safe; a future modal
471 /// host embedding a direct-overlay child must account for this occlusion.
472 fn defer_paint_to_overlay(&self) -> bool {
473 false
474 }
475
476 /// Return a clip rectangle (in local coordinates) that constrains all child
477 /// painting. `paint_subtree` applies this clip before recursing into
478 /// children, then restores the previous clip state afterward. The clip does
479 /// **not** affect `paint_overlay`, which runs after the clip is removed.
480 ///
481 /// The default clips children to this widget's own bounds, preventing
482 /// overflow. Override to return a narrower rect (e.g. Window clips to the
483 /// content area below the title bar, or an empty rect when collapsed).
484 fn clip_children_rect(&self) -> Option<(f64, f64, f64, f64)> {
485 let b = self.bounds();
486 Some((0.0, 0.0, b.width, b.height))
487 }
488
489 /// Affine transform applied between this widget and its children during
490 /// inspector traversal. Mirrors what `paint()` does — e.g. a widget
491 /// that pushes pan/zoom in `paint()` and pops it in `finish_paint()`
492 /// makes the framework recurse into its children with pan/zoom active,
493 /// so `collect_inspector_nodes` must apply the same transform when
494 /// accumulating descendant screen bounds. Without this hook the
495 /// inspector hover overlay lands at the un-transformed canvas position
496 /// when the widget sits inside a panning/zooming container.
497 ///
498 /// Default: identity (most widgets translate their children only
499 /// through `child.bounds()`, which `collect_inspector_nodes` already
500 /// accumulates separately).
501 ///
502 /// Defaults to [`child_transform`](Self::child_transform) so a widget that
503 /// injects a real pan/zoom into pointer + paint (a [`Scene`]) is seen by the
504 /// inspector at the same on-screen position without having to implement two
505 /// hooks. Widgets that need an inspector-only transform (rare) override
506 /// this directly.
507 fn inspector_child_transform(&self) -> crate::TransAffine {
508 self.child_transform().unwrap_or_else(crate::TransAffine::new)
509 }
510
511 /// Affine transform (child-local → this widget's local space) that the
512 /// framework applies to **all** of this widget's children for painting,
513 /// hit-testing, and event dispatch alike.
514 ///
515 /// This is the "scale hook" that lets a container magnify/pan its whole
516 /// child subtree (a [`Scene`]) while keeping those children fully
517 /// first-class: because they live in [`children`](Self::children), keyboard
518 /// focus (Tab + click-to-focus), the inspector, and pointer input all reach
519 /// them through the framework's normal traversals, which map coordinates
520 /// through this transform when descending.
521 ///
522 /// The transform is applied to the child group as a whole; per-child
523 /// `bounds()` offsets are interpreted **inside** the transform (i.e. in the
524 /// child/scene space), so a container that pins its content at the origin
525 /// (the common case) needs no further care. Pointer traversals invert this
526 /// transform to map an incoming screen-local point into child space.
527 ///
528 /// Default: `None` — children are positioned by `bounds()` alone.
529 ///
530 /// [`Scene`]: crate::widgets::Scene
531 fn child_transform(&self) -> Option<crate::TransAffine> {
532 None
533 }
534
535 // -------------------------------------------------------------------------
536 // Layout properties (universal — every widget carries these)
537 // -------------------------------------------------------------------------
538
539 /// Outer margin around this widget in logical units.
540 ///
541 /// The parent layout reads this to compute spacing and position.
542 /// Default: [`Insets::ZERO`].
543 fn margin(&self) -> Insets {
544 Insets::ZERO
545 }
546
547 /// Inner padding — space the widget reserves between its own bounds and
548 /// its child layout area. Only container widgets carry padding; leaf
549 /// widgets default to [`Insets::ZERO`].
550 ///
551 /// The inspector reads this to draw a Chrome F12-style padding band on
552 /// the highlighted widget. Containers that already store padding
553 /// internally (e.g. `FlexColumn::inner_padding`) override this to expose
554 /// it to the inspector.
555 fn padding(&self) -> Insets {
556 Insets::ZERO
557 }
558
559 /// Horizontal anchor: how this widget sizes/positions itself horizontally
560 /// within the slot the parent assigns.
561 /// Default: [`HAnchor::FIT`] (take natural content width).
562 fn h_anchor(&self) -> HAnchor {
563 HAnchor::FIT
564 }
565
566 /// Vertical anchor: how this widget sizes/positions itself vertically
567 /// within the slot the parent assigns.
568 /// Default: [`VAnchor::FIT`] (take natural content height).
569 fn v_anchor(&self) -> VAnchor {
570 VAnchor::FIT
571 }
572
573 /// Minimum size constraint (logical units).
574 ///
575 /// The parent will never assign a slot smaller than this.
576 /// Default: [`Size::ZERO`] (no minimum).
577 fn min_size(&self) -> Size {
578 Size::ZERO
579 }
580
581 /// Maximum size constraint (logical units).
582 ///
583 /// The parent will never assign a slot larger than this.
584 /// Default: [`Size::MAX`] (no maximum).
585 fn max_size(&self) -> Size {
586 Size::MAX
587 }
588
589 /// Direct read access to the widget's embedded [`WidgetBase`].
590 ///
591 /// Returns `Some` for every widget that embeds `WidgetBase` — effectively
592 /// all concrete widgets. The inspector uses this to read margin, anchors,
593 /// and size constraints without going through the individual trait methods.
594 /// Default returns `None`; widgets that embed `WidgetBase` override.
595 fn widget_base(&self) -> Option<&WidgetBase> {
596 None
597 }
598
599 /// Mutable counterpart of [`widget_base`](Self::widget_base).
600 ///
601 /// The inspector calls this to apply live edits to margin, h_anchor,
602 /// v_anchor, min_size, and max_size from the properties pane.
603 fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
604 None
605 }
606
607 /// Whether [`paint_subtree`] should snap this widget's incoming
608 /// translation to the physical pixel grid.
609 ///
610 /// Defaults to the process-wide
611 /// [`pixel_bounds::default_enforce_integer_bounds`](crate::pixel_bounds::default_enforce_integer_bounds)
612 /// flag so the common case — crisp UI text + strokes — works without
613 /// ceremony. Widgets with a [`WidgetBase`] should delegate to
614 /// `self.base().enforce_integer_bounds` so per-instance overrides take
615 /// effect; widgets that genuinely want sub-pixel positioning (smooth
616 /// scroll markers, zoomed canvases) override to return `false`.
617 ///
618 /// Mirrors MatterCAD's `GuiWidget.EnforceIntegerBounds` accessor.
619 fn enforce_integer_bounds(&self) -> bool {
620 crate::pixel_bounds::default_enforce_integer_bounds()
621 }
622
623 /// Report the minimum height this widget needs to fully render
624 /// its content when given the supplied `available_w` for width.
625 ///
626 /// Used by parents whose layout strategy depends on a true
627 /// content-required height that's independent of the slot they
628 /// might hand the widget — most importantly by
629 /// `Window::with_tight_content_fit(true)` to enforce "no
630 /// clipping, no whitespace" on the height axis even when the
631 /// content tree contains a flex-fill widget that would
632 /// otherwise return `available.height` from `layout`.
633 ///
634 /// Default returns `min_size().height` — accurate for widgets
635 /// whose minimum doesn't depend on width. Width-sensitive
636 /// widgets (wrapped text containers like `TextArea`, recursive
637 /// containers like `FlexColumn`) override and compute properly.
638 fn measure_min_height(&self, _available_w: f64) -> f64 {
639 self.min_size().height
640 }
641
642 /// Container widgets (notably [`crate::widgets::Stack`]) call this on each
643 /// child at the start of `layout()`. A widget that returns `true` is
644 /// moved to the END of its parent's child list — painted last, i.e.
645 /// raised to the top of the z-order. `take_` semantics: the call is
646 /// also expected to **clear** the request so the child doesn't keep
647 /// getting raised every frame.
648 ///
649 /// Default: no raise ever requested. `Window` overrides to fire on the
650 /// false→true visibility transition (see its `with_visible_cell`), so
651 /// toggling a demo checkbox on in the sidebar automatically pops that
652 /// window to the front.
653 fn take_raise_request(&mut self) -> bool {
654 false
655 }
656
657 // -------------------------------------------------------------------------
658 // Visibility-gated scheduled draw propagation
659 // -------------------------------------------------------------------------
660 //
661 // The host render loop walks the widget tree from the root to decide
662 // whether a visible subtree has a scheduled draw need such as cursor blink.
663 // Ordinary visual invalidation should call `animation::request_draw`, which
664 // also advances the retained-layer invalidation epoch. `needs_draw` stays
665 // for visibility-gated future/ongoing draw needs: invisible subtrees
666 // (collapsed Window, non-selected TabView tab, off-viewport content)
667 // must NOT keep the app in a continuous draw loop.
668
669 /// Return `true` if this widget, or any visible descendant, has an ongoing
670 /// draw need that should keep the host drawing.
671 ///
672 /// The default walks visible children. Widgets with their own pending
673 /// state OR that state with the default walk — see `WidgetBase` helpers.
674 fn needs_draw(&self) -> bool {
675 if !self.is_visible() {
676 return false;
677 }
678 self.children().iter().any(|c| c.needs_draw())
679 }
680
681 /// Return the earliest wall-clock instant at which this widget (or any
682 /// visible descendant) wants the next draw. `None` = no scheduled wake.
683 /// The host loop turns a `Some(t)` into `ControlFlow::WaitUntil(t)` so
684 /// e.g. a cursor blink fires without continuous polling.
685 ///
686 /// Same visibility contract as [`needs_draw`]: hidden subtrees return
687 /// `None` regardless of what the widget *would* ask for if shown.
688 fn next_draw_deadline(&self) -> Option<web_time::Instant> {
689 if !self.is_visible() {
690 return None;
691 }
692 let mut best: Option<web_time::Instant> = None;
693 for c in self.children() {
694 if let Some(t) = c.next_draw_deadline() {
695 best = Some(match best {
696 Some(b) if b <= t => b,
697 _ => t,
698 });
699 }
700 }
701 best
702 }
703}
704
705mod app;
706mod backbuffer;
707pub(crate) mod keyboard_scroll;
708mod paint;
709mod tree;
710mod tree_inspector;
711
712pub use app::App;
713pub use backbuffer::{
714 BackbufferCache, BackbufferKind, BackbufferMode, BackbufferSpec, BackbufferState,
715 CompositingLayer,
716};
717pub use paint::{current_paint_clip, paint_global_overlays, paint_subtree};
718pub(crate) use paint::paint_subtree_forced;
719pub use tree::{
720 active_modal_path, dispatch_event, dispatch_event_broadcast, dispatch_event_dyn,
721 dispatch_unconsumed_key, global_overlay_hit_path, hit_test_subtree, mark_subtree_dirty,
722};
723#[cfg(feature = "reflect")]
724pub use tree_inspector::{apply_inspector_edit, reflect_fields, InspectorEdit};
725pub use tree_inspector::{
726 apply_widget_base_edit, collect_inspector_nodes, current_mouse_world, current_viewport,
727 find_widget_by_id, find_widget_by_id_mut, find_widget_by_type, set_current_mouse_world,
728 set_current_viewport, walk_path_mut, InspectorNode, InspectorOverlay, WidgetBaseEdit,
729 WidgetBaseField,
730};