Skip to main content

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, MouseButton};
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    /// Return true when `local_pos` hits an app-level overlay owned by this
83    /// widget. Unlike normal hit testing, ancestors may be missed because the
84    /// overlay is painted outside their bounds.
85    fn hit_test_global_overlay(&self, _local_pos: Point) -> bool {
86        false
87    }
88
89    /// Whether this widget currently owns an app-modal interaction layer.
90    ///
91    /// When true anywhere in the tree, [`App`](crate::App) routes pointer and
92    /// key events to that modal subtree before normal hit testing so content
93    /// underneath the modal backdrop cannot be interacted with.
94    fn has_active_modal(&self) -> bool {
95        false
96    }
97
98    /// Handle an event. The event's positions are already in **local** Y-up
99    /// coordinates. Return [`EventResult::Consumed`] to stop bubbling.
100    ///
101    /// # Invalidation contract
102    ///
103    /// If your handler mutates state that affects the next paint (hover
104    /// index, focus, button-pressed bool, animation phase, ...), call
105    /// [`crate::animation::request_draw`] from inside the handler.  That
106    /// bumps the invalidation epoch, which `dispatch_event` reads to mark
107    /// retained ancestor backbuffers dirty — without it, the cached
108    /// bitmap composites unchanged and your state mutation is invisible
109    /// until something else dirties the cache.
110    ///
111    /// Returning `Consumed` *also* dirties the ancestor path automatically,
112    /// so a consumed click handler that calls `request_draw` is belt-and-
113    /// suspenders.  But `MouseMove` handlers that return `Ignored` (the
114    /// usual case for hover-tracking) **only** invalidate via
115    /// `request_draw`'s epoch bump.  Forgetting it produces "hover only
116    /// works the first time after I drag the window resize edge" bugs.
117    ///
118    /// `request_draw_without_invalidation` is for the rare cases where
119    /// the visual change is in an app-overlay or a position-only
120    /// composite — see its rustdoc.
121    fn on_event(&mut self, event: &Event) -> EventResult;
122
123    /// Handle a key that was not consumed by the focused widget path.
124    ///
125    /// This is used for window/menu accelerators: focused controls get first
126    /// chance at the key, then visible widgets in paint order may claim it.
127    fn on_unconsumed_key(&mut self, _key: &Key, _modifiers: Modifiers) -> EventResult {
128        EventResult::Ignored
129    }
130
131    /// Whether this widget can receive keyboard focus. Default: false.
132    fn is_focusable(&self) -> bool {
133        false
134    }
135
136    /// A static name for this widget type, used by the inspector. Default: "Widget".
137    fn type_name(&self) -> &'static str {
138        "Widget"
139    }
140
141    /// Optional human-readable identifier for this widget instance.
142    ///
143    /// Distinct from [`type_name`] (which is per-type and constant):
144    /// `id` lets external code look up a specific *instance* — used
145    /// today by the demo's z-order persistence to match a saved title
146    /// against a live `Window` in the canvas `Stack`.  Default
147    /// implementation returns `None`; widgets that want to be
148    /// identifiable (e.g. `Window` returning its title) override.
149    fn id(&self) -> Option<&str> {
150        None
151    }
152
153    /// Return `false` to suppress painting this widget **and all its children**.
154    /// The widget's own `paint()` will not be called.  Default: `true`.
155    fn is_visible(&self) -> bool {
156        true
157    }
158
159    /// Return type-specific properties for the inspector properties pane.
160    ///
161    /// Each entry is `(name, display_value)`.  The default returns an empty
162    /// list; widgets override this to expose their state to the inspector.
163    fn properties(&self) -> Vec<(&'static str, String)> {
164        vec![]
165    }
166
167    /// If this widget is text-bearing (e.g. `Label`), update its foreground
168    /// colour.  Default is a no-op.  Composite widgets call this on their
169    /// children to retint labels without rebuilding them — used by `Button`
170    /// when toggling between active (white text on accent) and inactive
171    /// (theme text on subtle bg) appearances.
172    fn set_label_color(&mut self, _color: crate::color::Color) {}
173
174    /// If this widget is text-bearing (e.g. `Label`), update its
175    /// displayed text.  Default is a no-op.  Composite widgets that
176    /// own a `Label` child use this to push live values (e.g. an FPS
177    /// counter) into the child without bypassing the standard
178    /// backbuffered glyph cache — calling this on a `Label` only
179    /// invalidates the cache when the text actually changed.
180    fn set_label_text(&mut self, _text: &str) {}
181
182    /// Opt-in reflection accessor for the inspector's typed property editors.
183    ///
184    /// Widgets that derive [`bevy_reflect::Reflect`] (via the `reflect`
185    /// cargo feature) override this to return `Some(self)` so the inspector
186    /// can walk their fields with type information — boolean toggles,
187    /// numeric sliders, color pickers, enum dropdowns — instead of falling
188    /// back to the read-only string [`properties`](Self::properties) list.
189    ///
190    /// Default returns `None`; the inspector then uses the string list.
191    /// Available only with the `reflect` feature so consumers without it
192    /// don't pay the dependency cost.
193    #[cfg(feature = "reflect")]
194    fn as_reflect(&self) -> Option<&dyn bevy_reflect::Reflect> {
195        None
196    }
197
198    /// Mutable counterpart of [`as_reflect`](Self::as_reflect).  Used by the
199    /// inspector to write edits back into the live widget.
200    #[cfg(feature = "reflect")]
201    fn as_reflect_mut(&mut self) -> Option<&mut dyn bevy_reflect::Reflect> {
202        None
203    }
204
205    /// Whether this widget renders into its own offscreen buffer before
206    /// compositing into the parent.
207    ///
208    /// When `true`, `paint_subtree` wraps the widget (and all its descendants)
209    /// in `ctx.push_layer` / `ctx.pop_layer`.  The widget and its children draw
210    /// into a fresh transparent framebuffer; when complete, the buffer is
211    /// SrcOver-composited back into the parent render target.  This enables
212    /// per-widget alpha compositing, caching, and isolation.
213    ///
214    /// Default: `false` (pass-through rendering).
215    fn has_backbuffer(&self) -> bool {
216        false
217    }
218
219    /// Request that this widget subtree be painted into a transient
220    /// transparent compositing layer before being blended into its parent.
221    ///
222    /// Renderers that do not implement real layers ignore this hook. The
223    /// method is mutable so widgets can advance visibility tweens at the
224    /// point where the traversal knows the layer will be painted.
225    fn compositing_layer(&mut self) -> Option<CompositingLayer> {
226        None
227    }
228
229    /// Unified widget-owned backbuffer request.
230    fn backbuffer_spec(&mut self) -> BackbufferSpec {
231        let mode = self.backbuffer_mode();
232        if self.backbuffer_cache_mut().is_some() {
233            BackbufferSpec {
234                kind: match mode {
235                    BackbufferMode::Rgba => BackbufferKind::SoftwareRgba,
236                    BackbufferMode::LcdCoverage => BackbufferKind::SoftwareLcd,
237                },
238                cached: true,
239                alpha: 1.0,
240                outsets: Insets::ZERO,
241                rounded_clip: None,
242            }
243        } else {
244            BackbufferSpec::none()
245        }
246    }
247
248    /// Mutable retained backbuffer state for widgets that request a
249    /// [`BackbufferSpec`] other than [`BackbufferKind::None`].
250    fn backbuffer_state_mut(&mut self) -> Option<&mut BackbufferState> {
251        None
252    }
253
254    /// Mark this widget's own retained surface dirty, if it owns one.
255    ///
256    /// Invalidates *both* the retained-layer [`BackbufferState`] and the
257    /// per-widget bitmap [`BackbufferCache`].  Inspector edits that mutate
258    /// a widget's reflected props bypass setters that normally invalidate
259    /// the cache (e.g. `Label::set_text`); calling `mark_dirty` after an
260    /// edit restores correct re-raster on the next frame.
261    fn mark_dirty(&mut self) {
262        if let Some(state) = self.backbuffer_state_mut() {
263            state.invalidate();
264        }
265        if let Some(cache) = self.backbuffer_cache_mut() {
266            cache.invalidate();
267        }
268    }
269
270    /// Opt into per-widget CPU bitmap caching with a dirty flag.
271    ///
272    /// Widgets that return `Some(&mut cache)` get their paint +
273    /// children cached as a `Vec<u8>` of RGBA8 pixels.  `paint_subtree`
274    /// re-rasterises via AGG only when `cache.dirty` is true; otherwise
275    /// it blits the existing bitmap.  GL backends key their texture
276    /// cache on the `Arc`'s pointer identity so the uploaded GPU
277    /// texture is also reused across frames.
278    ///
279    /// The widget is responsible for calling `cache.invalidate()` (or
280    /// setting `cache.dirty = true`) from any mutation that could
281    /// change the rendered output — text/color setters, focus/hover
282    /// state changes, layout size changes, etc.  The framework clears
283    /// the flag after a successful re-raster.
284    ///
285    /// Default: `None` (no caching — paint every frame directly).
286    fn backbuffer_cache_mut(&mut self) -> Option<&mut BackbufferCache> {
287        None
288    }
289
290    /// Storage format for this widget's backbuffer.  Ignored unless
291    /// [`backbuffer_cache_mut`] returns `Some`.  Default
292    /// [`BackbufferMode::Rgba`] — correct for any widget.
293    /// Opt into [`BackbufferMode::LcdCoverage`] only when the widget
294    /// paints opaque content covering its full bounds.
295    fn backbuffer_mode(&self) -> BackbufferMode {
296        BackbufferMode::Rgba
297    }
298
299    /// Whether the inspector should recurse into this widget's children.
300    ///
301    /// Returns `false` for widgets that are part of the inspector infrastructure
302    /// (e.g. the inspector's own `TreeView`) to prevent the inspector from
303    /// showing itself recursively, which would grow the node list every frame.
304    ///
305    /// The widget itself is still included in the inspector snapshot — only
306    /// its subtree is suppressed.
307    fn contributes_children_to_inspector(&self) -> bool {
308        true
309    }
310
311    /// Return `false` to hide this widget (and its subtree) from the inspector
312    /// node snapshot entirely.  Intended for zero-size utility widgets such
313    /// as layout-time watchers / tickers / invisible composers — they bloat
314    /// the inspector tree without providing user-relevant information and,
315    /// at scale, can make the inspector's per-frame tree rebuild expensive.
316    fn show_in_inspector(&self) -> bool {
317        true
318    }
319
320    /// Per-widget LCD subpixel preference for backbuffered text rendering.
321    ///
322    /// - `Some(true)`  — always raster text with LCD subpixel.
323    /// - `Some(false)` — always use grayscale AA.
324    /// - `None`        — defer to the global `font_settings::lcd_enabled()`.
325    ///
326    /// Only widgets that raster text into an offscreen backbuffer act on
327    /// this flag (today: `Label`).  Defaulting to `None` means every such
328    /// widget follows the global toggle unless the instance explicitly
329    /// opts in or out.
330    fn lcd_preference(&self) -> Option<bool> {
331        None
332    }
333
334    /// Paint decorations that must appear **on top of all children**.
335    ///
336    /// Called by [`paint_subtree`] after all children have been painted.
337    /// The default implementation is a no-op; override in widgets that need
338    /// to draw overlays (e.g. resize handles, drag previews) that must not
339    /// be occluded by child content.
340    fn paint_overlay(&mut self, _ctx: &mut dyn DrawCtx) {}
341
342    /// Called after `paint`, child painting, and optional overlay painting.
343    ///
344    /// Most widgets do not need this. It exists for widgets that intentionally
345    /// open a backend compositing scope in `paint` and must close it after all
346    /// descendants have rendered into that scope.
347    fn finish_paint(&mut self, _ctx: &mut dyn DrawCtx) {}
348
349    /// Paint app-level overlays after the entire widget tree has been painted.
350    ///
351    /// The traversal preserves this widget's local transform but skips ancestor
352    /// clips and retained parent redraw requirements. Use this for portal-style
353    /// UI that draws outside normal bounds while still participating in the
354    /// widget tree's Z order.
355    fn paint_global_overlay(&mut self, _ctx: &mut dyn DrawCtx) {}
356
357    /// Return a clip rectangle (in local coordinates) that constrains all child
358    /// painting.  `paint_subtree` applies this clip before recursing into
359    /// children, then restores the previous clip state afterward.  The clip does
360    /// **not** affect `paint_overlay`, which runs after the clip is removed.
361    ///
362    /// The default clips children to this widget's own bounds, preventing
363    /// overflow.  Override to return a narrower rect (e.g. Window clips to the
364    /// content area below the title bar, or an empty rect when collapsed).
365    fn clip_children_rect(&self) -> Option<(f64, f64, f64, f64)> {
366        let b = self.bounds();
367        Some((0.0, 0.0, b.width, b.height))
368    }
369
370    // -------------------------------------------------------------------------
371    // Layout properties (universal — every widget carries these)
372    // -------------------------------------------------------------------------
373
374    /// Outer margin around this widget in logical units.
375    ///
376    /// The parent layout reads this to compute spacing and position.
377    /// Default: [`Insets::ZERO`].
378    fn margin(&self) -> Insets {
379        Insets::ZERO
380    }
381
382    /// Inner padding — space the widget reserves between its own bounds and
383    /// its child layout area.  Only container widgets carry padding; leaf
384    /// widgets default to [`Insets::ZERO`].
385    ///
386    /// The inspector reads this to draw a Chrome F12-style padding band on
387    /// the highlighted widget.  Containers that already store padding
388    /// internally (e.g. `FlexColumn::inner_padding`) override this to expose
389    /// it to the inspector.
390    fn padding(&self) -> Insets {
391        Insets::ZERO
392    }
393
394    /// Horizontal anchor: how this widget sizes/positions itself horizontally
395    /// within the slot the parent assigns.
396    /// Default: [`HAnchor::FIT`] (take natural content width).
397    fn h_anchor(&self) -> HAnchor {
398        HAnchor::FIT
399    }
400
401    /// Vertical anchor: how this widget sizes/positions itself vertically
402    /// within the slot the parent assigns.
403    /// Default: [`VAnchor::FIT`] (take natural content height).
404    fn v_anchor(&self) -> VAnchor {
405        VAnchor::FIT
406    }
407
408    /// Minimum size constraint (logical units).
409    ///
410    /// The parent will never assign a slot smaller than this.
411    /// Default: [`Size::ZERO`] (no minimum).
412    fn min_size(&self) -> Size {
413        Size::ZERO
414    }
415
416    /// Maximum size constraint (logical units).
417    ///
418    /// The parent will never assign a slot larger than this.
419    /// Default: [`Size::MAX`] (no maximum).
420    fn max_size(&self) -> Size {
421        Size::MAX
422    }
423
424    /// Direct read access to the widget's embedded [`WidgetBase`].
425    ///
426    /// Returns `Some` for every widget that embeds `WidgetBase` — effectively
427    /// all concrete widgets.  The inspector uses this to read margin, anchors,
428    /// and size constraints without going through the individual trait methods.
429    /// Default returns `None`; widgets that embed `WidgetBase` override.
430    fn widget_base(&self) -> Option<&WidgetBase> {
431        None
432    }
433
434    /// Mutable counterpart of [`widget_base`](Self::widget_base).
435    ///
436    /// The inspector calls this to apply live edits to margin, h_anchor,
437    /// v_anchor, min_size, and max_size from the properties pane.
438    fn widget_base_mut(&mut self) -> Option<&mut WidgetBase> {
439        None
440    }
441
442    /// Whether [`paint_subtree`] should snap this widget's incoming
443    /// translation to the physical pixel grid.
444    ///
445    /// Defaults to the process-wide
446    /// [`pixel_bounds::default_enforce_integer_bounds`](crate::pixel_bounds::default_enforce_integer_bounds)
447    /// flag so the common case — crisp UI text + strokes — works without
448    /// ceremony.  Widgets with a [`WidgetBase`] should delegate to
449    /// `self.base().enforce_integer_bounds` so per-instance overrides take
450    /// effect; widgets that genuinely want sub-pixel positioning (smooth
451    /// scroll markers, zoomed canvases) override to return `false`.
452    ///
453    /// Mirrors MatterCAD's `GuiWidget.EnforceIntegerBounds` accessor.
454    fn enforce_integer_bounds(&self) -> bool {
455        crate::pixel_bounds::default_enforce_integer_bounds()
456    }
457
458    /// Report the minimum height this widget needs to fully render
459    /// its content when given the supplied `available_w` for width.
460    ///
461    /// Used by parents whose layout strategy depends on a true
462    /// content-required height that's independent of the slot they
463    /// might hand the widget — most importantly by
464    /// `Window::with_tight_content_fit(true)` to enforce "no
465    /// clipping, no whitespace" on the height axis even when the
466    /// content tree contains a flex-fill widget that would
467    /// otherwise return `available.height` from `layout`.
468    ///
469    /// Default returns `min_size().height` — accurate for widgets
470    /// whose minimum doesn't depend on width.  Width-sensitive
471    /// widgets (wrapped text containers like `TextArea`, recursive
472    /// containers like `FlexColumn`) override and compute properly.
473    fn measure_min_height(&self, _available_w: f64) -> f64 {
474        self.min_size().height
475    }
476
477    /// Container widgets (notably [`crate::widgets::Stack`]) call this on each
478    /// child at the start of `layout()`.  A widget that returns `true` is
479    /// moved to the END of its parent's child list — painted last, i.e.
480    /// raised to the top of the z-order.  `take_` semantics: the call is
481    /// also expected to **clear** the request so the child doesn't keep
482    /// getting raised every frame.
483    ///
484    /// Default: no raise ever requested.  `Window` overrides to fire on the
485    /// false→true visibility transition (see its `with_visible_cell`), so
486    /// toggling a demo checkbox on in the sidebar automatically pops that
487    /// window to the front.
488    fn take_raise_request(&mut self) -> bool {
489        false
490    }
491
492    // -------------------------------------------------------------------------
493    // Visibility-gated scheduled draw propagation
494    // -------------------------------------------------------------------------
495    //
496    // The host render loop walks the widget tree from the root to decide
497    // whether a visible subtree has a scheduled draw need such as cursor blink.
498    // Ordinary visual invalidation should call `animation::request_draw`, which
499    // also advances the retained-layer invalidation epoch.  `needs_draw` stays
500    // for visibility-gated future/ongoing draw needs: invisible subtrees
501    // (collapsed Window, non-selected TabView tab, off-viewport content)
502    // must NOT keep the app in a continuous draw loop.
503
504    /// Return `true` if this widget, or any visible descendant, has an ongoing
505    /// draw need that should keep the host drawing.
506    ///
507    /// The default walks visible children.  Widgets with their own pending
508    /// state OR that state with the default walk — see `WidgetBase` helpers.
509    fn needs_draw(&self) -> bool {
510        if !self.is_visible() {
511            return false;
512        }
513        self.children().iter().any(|c| c.needs_draw())
514    }
515
516    /// Return the earliest wall-clock instant at which this widget (or any
517    /// visible descendant) wants the next draw.  `None` = no scheduled wake.
518    /// The host loop turns a `Some(t)` into `ControlFlow::WaitUntil(t)` so
519    /// e.g. a cursor blink fires without continuous polling.
520    ///
521    /// Same visibility contract as [`needs_draw`]: hidden subtrees return
522    /// `None` regardless of what the widget *would* ask for if shown.
523    fn next_draw_deadline(&self) -> Option<web_time::Instant> {
524        if !self.is_visible() {
525            return None;
526        }
527        let mut best: Option<web_time::Instant> = None;
528        for c in self.children() {
529            if let Some(t) = c.next_draw_deadline() {
530                best = Some(match best {
531                    Some(b) if b <= t => b,
532                    _ => t,
533                });
534            }
535        }
536        best
537    }
538}
539
540mod app;
541mod backbuffer;
542mod paint;
543mod tree;
544
545pub use app::App;
546pub use backbuffer::{
547    BackbufferCache, BackbufferKind, BackbufferMode, BackbufferSpec, BackbufferState,
548    CompositingLayer,
549};
550pub use paint::{current_paint_clip, paint_global_overlays, paint_subtree};
551pub use tree::{
552    active_modal_path, collect_inspector_nodes, current_mouse_world, current_viewport,
553    dispatch_event, dispatch_unconsumed_key, find_widget_by_id, find_widget_by_id_mut,
554    find_widget_by_type, global_overlay_hit_path, hit_test_subtree, mark_subtree_dirty,
555    set_current_mouse_world, set_current_viewport, walk_path_mut, InspectorNode,
556    InspectorOverlay, WidgetBaseEdit, WidgetBaseField, apply_widget_base_edit,
557};
558#[cfg(feature = "reflect")]
559pub use tree::{apply_inspector_edit, reflect_fields, InspectorEdit};