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 std::sync::atomic::{AtomicU64, Ordering};
21use std::sync::Arc;
22
23use crate::draw_ctx::DrawCtx;
24use crate::event::{Event, EventResult, Key, Modifiers, MouseButton};
25use crate::framebuffer::Framebuffer;
26use crate::geometry::{Point, Rect, Size};
27use crate::gfx_ctx::GfxCtx;
28use crate::layout_props::{HAnchor, Insets, VAnchor};
29use crate::lcd_coverage::LcdBuffer;
30
31// ---------------------------------------------------------------------------
32// Widget backbuffer — CPU bitmap cache per widget, invalidated via a dirty flag.
33// ---------------------------------------------------------------------------
34//
35// Any widget can opt into a cached CPU backbuffer by returning `Some(&mut ...)`
36// from [`Widget::backbuffer_cache_mut`].  The framework's `paint_subtree`
37// handles caching transparently: when the widget is dirty (or has no bitmap
38// yet) it allocates a fresh `Framebuffer`, runs `widget.paint` + all children
39// into it via a software `GfxCtx`, and caches the resulting RGBA8 pixels as a
40// shared `Arc<Vec<u8>>`.  Every subsequent frame that finds the widget clean
41// just blits the cached pixels through `ctx.draw_image_rgba_arc` — zero AGG
42// cost in steady state.  On the GL backend the `Arc`'s pointer identity keys
43// the GPU texture cache (see `arc_texture_cache`), so the hardware texture
44// is also reused across frames and dropped when the bitmap drops.
45//
46// The pattern is the one MatterCAD / AggSharp use: every widget CAN be
47// backbuffered, each owns its bitmap, and a single `dirty` flag drives
48// re-rasterisation.
49//
50// LCD subpixel rendering works naturally inside a backbuffer: the widget
51// paints its own background first (so text has a solid dst) and then any
52// `fill_text` call composites the per-channel coverage mask onto that
53// destination.  No walk / sample / bg-declaration needed.
54
55/// How a widget's backbuffer stores pixels.
56///
57/// The choice controls what the framework allocates as the render
58/// target during `paint_subtree_backbuffered` and how the cached
59/// bitmap is composited back onto the parent.
60#[derive(Clone, Copy, Debug, PartialEq, Eq)]
61pub enum BackbufferMode {
62    /// 8-bit straight-alpha RGBA.  Standard Porter-Duff `SRC_ALPHA,
63    /// ONE_MINUS_SRC_ALPHA` composite on blit.  Works for any widget,
64    /// including ones with transparent areas.  Text inside is grayscale
65    /// AA (no LCD subpixel).
66    Rgba,
67    /// 3 bytes-per-pixel **composited opaque RGB** — no alpha channel.
68    /// Every fill (rects, strokes, text, etc.) inside the buffer goes
69    /// through the 3× horizontal supersample + 5-tap filter + per-channel
70    /// src-over pipeline described in `lcd-subpixel-compositing.md`.
71    /// The buffer is blitted as an opaque RGB texture.
72    ///
73    /// **Contract:** the widget is responsible for painting content
74    /// that covers its full bounds with opaque fills (starting with a
75    /// bg rect).  Uncovered pixels land as black on the parent because
76    /// there is no alpha channel to carry "no paint here."
77    LcdCoverage,
78}
79
80/// Unified backbuffer target kind requested by a widget.
81#[derive(Clone, Copy, Debug, PartialEq, Eq)]
82pub enum BackbufferKind {
83    /// Paint directly into the parent render target.
84    None,
85    /// Retained software RGBA framebuffer.
86    SoftwareRgba,
87    /// Retained software LCD coverage framebuffer.
88    SoftwareLcd,
89    /// Retained GL framebuffer object.
90    GlFbo,
91}
92
93/// Widget-owned backbuffer request. Windows use this for retained GL FBOs,
94/// while existing label/text-field CPU caches map naturally to the software
95/// variants.
96#[derive(Clone, Copy, Debug)]
97pub struct BackbufferSpec {
98    pub kind: BackbufferKind,
99    pub cached: bool,
100    pub alpha: f64,
101    pub outsets: Insets,
102    pub rounded_clip: Option<f64>,
103}
104
105impl BackbufferSpec {
106    pub const fn none() -> Self {
107        Self {
108            kind: BackbufferKind::None,
109            cached: false,
110            alpha: 1.0,
111            outsets: Insets::ZERO,
112            rounded_clip: None,
113        }
114    }
115}
116
117impl Default for BackbufferSpec {
118    fn default() -> Self {
119        Self::none()
120    }
121}
122
123/// A CPU bitmap owned by a widget that opts into backbuffer caching.
124///
125/// The framework re-rasterises when the cache's explicit dirty flag is set or
126/// when global styling epochs change.
127pub struct BackbufferCache {
128    /// In **Rgba** mode: top-row-first RGBA8 pixels, straight alpha.
129    /// Blitted via [`DrawCtx::draw_image_rgba_arc`].
130    ///
131    /// In **LcdCoverage** mode: top-row-first **colour plane** — 3
132    /// bytes/pixel (R_premult, G_premult, B_premult) matching the
133    /// convention of [`crate::lcd_coverage::LcdBuffer::color_plane`]
134    /// flipped to top-down.  The companion alpha plane lives in
135    /// [`Self::lcd_alpha`].
136    pub pixels: Option<Arc<Vec<u8>>>,
137    /// `LcdCoverage`-mode companion to `pixels`: top-row-first per-channel
138    /// **alpha plane** (3 bytes/pixel, `(R_alpha, G_alpha, B_alpha)`).
139    /// `None` means this is a plain Rgba cache.  When `Some`, the blit
140    /// step uses [`DrawCtx::draw_lcd_backbuffer_arc`] to preserve the
141    /// per-channel subpixel information through to the destination —
142    /// required for LCD chroma to survive the cache round-trip.
143    pub lcd_alpha: Option<Arc<Vec<u8>>>,
144    pub width: u32,
145    pub height: u32,
146    /// When true, the next paint will re-rasterise rather than reusing
147    /// `pixels`.  Widgets set this from their mutation paths
148    /// (`set_text`, `set_color`, focus/hover changes, etc.) and the
149    /// framework clears it after a successful re-raster.
150    pub dirty: bool,
151    /// Visuals epoch (see [`crate::theme::current_visuals_epoch`]) recorded
152    /// the last time this cache was populated.  `paint_subtree_backbuffered`
153    /// compares it against the live epoch and forces a re-raster on mismatch,
154    /// so widgets whose text/fill colours come from `ctx.visuals()` refresh
155    /// automatically on a dark/light theme flip without needing every widget
156    /// to subscribe to theme-change events.
157    pub theme_epoch: u64,
158    /// Typography epoch (see
159    /// [`crate::font_settings::current_typography_epoch`]) — same
160    /// pattern as `theme_epoch` but for font / size scale / LCD /
161    /// hinting / gamma / width / interval / faux-* globals.  Lets a
162    /// slider drag in the LCD Subpixel demo invalidate every cached
163    /// `Label` bitmap without bespoke hooks per widget.
164    pub typography_epoch: u64,
165}
166
167impl BackbufferCache {
168    pub fn new() -> Self {
169        Self {
170            pixels: None,
171            lcd_alpha: None,
172            width: 0,
173            height: 0,
174            dirty: true,
175            theme_epoch: 0,
176            typography_epoch: 0,
177        }
178    }
179
180    /// Mark the cache dirty so the next paint re-rasterises.
181    pub fn invalidate(&mut self) {
182        self.dirty = true;
183    }
184}
185
186impl Default for BackbufferCache {
187    fn default() -> Self {
188        Self::new()
189    }
190}
191
192static NEXT_BACKBUFFER_ID: AtomicU64 = AtomicU64::new(1);
193
194/// Retained widget backbuffer state shared by software and GL implementations.
195pub struct BackbufferState {
196    id: u64,
197    pub cache: BackbufferCache,
198    pub dirty: bool,
199    pub width: u32,
200    pub height: u32,
201    pub spec_kind: BackbufferKind,
202    /// Visuals epoch recorded the last time this retained surface was repainted.
203    /// Retained backend layers compare it against the live theme epoch so a
204    /// dark/light flip rebuilds the window/layer in the shared paint path.
205    pub theme_epoch: u64,
206    /// Typography epoch recorded the last time this retained surface was
207    /// repainted. Without this, a clean parent FBO can keep compositing old
208    /// text after global font/LCD settings change.
209    pub typography_epoch: u64,
210    pub repaint_count: u64,
211    pub composite_count: u64,
212}
213
214impl BackbufferState {
215    pub fn new() -> Self {
216        Self {
217            id: NEXT_BACKBUFFER_ID.fetch_add(1, Ordering::Relaxed),
218            cache: BackbufferCache::new(),
219            dirty: true,
220            width: 0,
221            height: 0,
222            spec_kind: BackbufferKind::None,
223            theme_epoch: 0,
224            typography_epoch: 0,
225            repaint_count: 0,
226            composite_count: 0,
227        }
228    }
229
230    pub fn id(&self) -> u64 {
231        self.id
232    }
233
234    pub fn invalidate(&mut self) {
235        self.dirty = true;
236        self.cache.invalidate();
237    }
238}
239
240impl Default for BackbufferState {
241    fn default() -> Self {
242        Self::new()
243    }
244}
245
246/// Offscreen compositing layer requested by a widget for itself and its
247/// descendants.
248#[derive(Clone, Copy, Debug)]
249pub struct CompositingLayer {
250    /// Extra transparent pixels to the left of the widget bounds.
251    pub outset_left: f64,
252    /// Extra transparent pixels below the widget bounds.
253    pub outset_bottom: f64,
254    /// Extra transparent pixels to the right of the widget bounds.
255    pub outset_right: f64,
256    /// Extra transparent pixels above the widget bounds.
257    pub outset_top: f64,
258    /// Whole-layer opacity applied while compositing back to the parent.
259    pub alpha: f64,
260}
261
262impl CompositingLayer {
263    pub const fn new(
264        outset_left: f64,
265        outset_bottom: f64,
266        outset_right: f64,
267        outset_top: f64,
268        alpha: f64,
269    ) -> Self {
270        Self {
271            outset_left,
272            outset_bottom,
273            outset_right,
274            outset_top,
275            alpha,
276        }
277    }
278}
279
280// ---------------------------------------------------------------------------
281// Widget trait
282// ---------------------------------------------------------------------------
283
284/// Every visible element in the UI is a widget.
285///
286/// Implementors handle their own painting and event handling. The framework
287/// takes care of tree traversal, coordinate translation, and focus management.
288pub trait Widget {
289    /// Bounding rectangle in **parent-local** Y-up coordinates.
290    fn bounds(&self) -> Rect;
291
292    /// Set the bounding rectangle. Called by the parent during layout.
293    fn set_bounds(&mut self, bounds: Rect);
294
295    /// Immutable access to child widgets.
296    fn children(&self) -> &[Box<dyn Widget>];
297
298    /// Mutable access to child widgets (required for event dispatch + layout).
299    fn children_mut(&mut self) -> &mut Vec<Box<dyn Widget>>;
300
301    /// Compute desired size given available space, and update internal layout.
302    ///
303    /// The parent passes the space it can offer; the widget returns the size it
304    /// actually wants to occupy. The parent uses the returned size to set this
305    /// widget's bounds before calling `layout` on the next sibling.
306    fn layout(&mut self, available: Size) -> Size;
307
308    /// Paint this widget's own content into `ctx`.
309    ///
310    /// The framework has already translated `ctx` so that `(0, 0)` is this
311    /// widget's bottom-left corner. **Do not paint children here** — the
312    /// framework recurses into them automatically after `paint` returns.
313    ///
314    /// `ctx` is a `&mut dyn DrawCtx`; the concrete type is either a software
315    /// `GfxCtx` (back-buffer path) or a `GlGfxCtx` (hardware GL path).
316    fn paint(&mut self, ctx: &mut dyn DrawCtx);
317
318    /// Return `true` if `local_pos` (in this widget's local coordinates) falls
319    /// inside this widget's interactive area. Default: axis-aligned rect test.
320    fn hit_test(&self, local_pos: Point) -> bool {
321        let b = self.bounds();
322        local_pos.x >= 0.0
323            && local_pos.x <= b.width
324            && local_pos.y >= 0.0
325            && local_pos.y <= b.height
326    }
327
328    /// When `true`, `hit_test_subtree` stops recursing into this widget's
329    /// children and returns this widget as the hit target.  Used for floating
330    /// overlays (e.g. a scrollbar painted above its content) that must claim
331    /// the pointer before children that happen to share the same pixels.
332    /// Default: `false`.
333    fn claims_pointer_exclusively(&self, _local_pos: Point) -> bool {
334        false
335    }
336
337    /// Return true when `local_pos` hits an app-level overlay owned by this
338    /// widget. Unlike normal hit testing, ancestors may be missed because the
339    /// overlay is painted outside their bounds.
340    fn hit_test_global_overlay(&self, _local_pos: Point) -> bool {
341        false
342    }
343
344    /// Whether this widget currently owns an app-modal interaction layer.
345    ///
346    /// When true anywhere in the tree, [`App`](crate::App) routes pointer and
347    /// key events to that modal subtree before normal hit testing so content
348    /// underneath the modal backdrop cannot be interacted with.
349    fn has_active_modal(&self) -> bool {
350        false
351    }
352
353    /// Handle an event. The event's positions are already in **local** Y-up
354    /// coordinates. Return [`EventResult::Consumed`] to stop bubbling.
355    fn on_event(&mut self, event: &Event) -> EventResult;
356
357    /// Handle a key that was not consumed by the focused widget path.
358    ///
359    /// This is used for window/menu accelerators: focused controls get first
360    /// chance at the key, then visible widgets in paint order may claim it.
361    fn on_unconsumed_key(&mut self, _key: &Key, _modifiers: Modifiers) -> EventResult {
362        EventResult::Ignored
363    }
364
365    /// Whether this widget can receive keyboard focus. Default: false.
366    fn is_focusable(&self) -> bool {
367        false
368    }
369
370    /// A static name for this widget type, used by the inspector. Default: "Widget".
371    fn type_name(&self) -> &'static str {
372        "Widget"
373    }
374
375    /// Optional human-readable identifier for this widget instance.
376    ///
377    /// Distinct from [`type_name`] (which is per-type and constant):
378    /// `id` lets external code look up a specific *instance* — used
379    /// today by the demo's z-order persistence to match a saved title
380    /// against a live `Window` in the canvas `Stack`.  Default
381    /// implementation returns `None`; widgets that want to be
382    /// identifiable (e.g. `Window` returning its title) override.
383    fn id(&self) -> Option<&str> {
384        None
385    }
386
387    /// Return `false` to suppress painting this widget **and all its children**.
388    /// The widget's own `paint()` will not be called.  Default: `true`.
389    fn is_visible(&self) -> bool {
390        true
391    }
392
393    /// Return type-specific properties for the inspector properties pane.
394    ///
395    /// Each entry is `(name, display_value)`.  The default returns an empty
396    /// list; widgets override this to expose their state to the inspector.
397    fn properties(&self) -> Vec<(&'static str, String)> {
398        vec![]
399    }
400
401    /// Whether this widget renders into its own offscreen buffer before
402    /// compositing into the parent.
403    ///
404    /// When `true`, `paint_subtree` wraps the widget (and all its descendants)
405    /// in `ctx.push_layer` / `ctx.pop_layer`.  The widget and its children draw
406    /// into a fresh transparent framebuffer; when complete, the buffer is
407    /// SrcOver-composited back into the parent render target.  This enables
408    /// per-widget alpha compositing, caching, and isolation.
409    ///
410    /// Default: `false` (pass-through rendering).
411    fn has_backbuffer(&self) -> bool {
412        false
413    }
414
415    /// Request that this widget subtree be painted into a transient
416    /// transparent compositing layer before being blended into its parent.
417    ///
418    /// Renderers that do not implement real layers ignore this hook. The
419    /// method is mutable so widgets can advance visibility tweens at the
420    /// point where the traversal knows the layer will be painted.
421    fn compositing_layer(&mut self) -> Option<CompositingLayer> {
422        None
423    }
424
425    /// Unified widget-owned backbuffer request.
426    fn backbuffer_spec(&mut self) -> BackbufferSpec {
427        let mode = self.backbuffer_mode();
428        if self.backbuffer_cache_mut().is_some() {
429            BackbufferSpec {
430                kind: match mode {
431                    BackbufferMode::Rgba => BackbufferKind::SoftwareRgba,
432                    BackbufferMode::LcdCoverage => BackbufferKind::SoftwareLcd,
433                },
434                cached: true,
435                alpha: 1.0,
436                outsets: Insets::ZERO,
437                rounded_clip: None,
438            }
439        } else {
440            BackbufferSpec::none()
441        }
442    }
443
444    /// Mutable retained backbuffer state for widgets that request a
445    /// [`BackbufferSpec`] other than [`BackbufferKind::None`].
446    fn backbuffer_state_mut(&mut self) -> Option<&mut BackbufferState> {
447        None
448    }
449
450    /// Mark this widget's own retained surface dirty, if it owns one.
451    fn mark_dirty(&mut self) {
452        if let Some(state) = self.backbuffer_state_mut() {
453            state.invalidate();
454        }
455    }
456
457    /// Opt into per-widget CPU bitmap caching with a dirty flag.
458    ///
459    /// Widgets that return `Some(&mut cache)` get their paint +
460    /// children cached as a `Vec<u8>` of RGBA8 pixels.  `paint_subtree`
461    /// re-rasterises via AGG only when `cache.dirty` is true; otherwise
462    /// it blits the existing bitmap.  GL backends key their texture
463    /// cache on the `Arc`'s pointer identity so the uploaded GPU
464    /// texture is also reused across frames.
465    ///
466    /// The widget is responsible for calling `cache.invalidate()` (or
467    /// setting `cache.dirty = true`) from any mutation that could
468    /// change the rendered output — text/color setters, focus/hover
469    /// state changes, layout size changes, etc.  The framework clears
470    /// the flag after a successful re-raster.
471    ///
472    /// Default: `None` (no caching — paint every frame directly).
473    fn backbuffer_cache_mut(&mut self) -> Option<&mut BackbufferCache> {
474        None
475    }
476
477    /// Storage format for this widget's backbuffer.  Ignored unless
478    /// [`backbuffer_cache_mut`] returns `Some`.  Default
479    /// [`BackbufferMode::Rgba`] — correct for any widget.
480    /// Opt into [`BackbufferMode::LcdCoverage`] only when the widget
481    /// paints opaque content covering its full bounds.
482    fn backbuffer_mode(&self) -> BackbufferMode {
483        BackbufferMode::Rgba
484    }
485
486    /// Whether the inspector should recurse into this widget's children.
487    ///
488    /// Returns `false` for widgets that are part of the inspector infrastructure
489    /// (e.g. the inspector's own `TreeView`) to prevent the inspector from
490    /// showing itself recursively, which would grow the node list every frame.
491    ///
492    /// The widget itself is still included in the inspector snapshot — only
493    /// its subtree is suppressed.
494    fn contributes_children_to_inspector(&self) -> bool {
495        true
496    }
497
498    /// Return `false` to hide this widget (and its subtree) from the inspector
499    /// node snapshot entirely.  Intended for zero-size utility widgets such
500    /// as layout-time watchers / tickers / invisible composers — they bloat
501    /// the inspector tree without providing user-relevant information and,
502    /// at scale, can make the inspector's per-frame tree rebuild expensive.
503    fn show_in_inspector(&self) -> bool {
504        true
505    }
506
507    /// Per-widget LCD subpixel preference for backbuffered text rendering.
508    ///
509    /// - `Some(true)`  — always raster text with LCD subpixel.
510    /// - `Some(false)` — always use grayscale AA.
511    /// - `None`        — defer to the global `font_settings::lcd_enabled()`.
512    ///
513    /// Only widgets that raster text into an offscreen backbuffer act on
514    /// this flag (today: `Label`).  Defaulting to `None` means every such
515    /// widget follows the global toggle unless the instance explicitly
516    /// opts in or out.
517    fn lcd_preference(&self) -> Option<bool> {
518        None
519    }
520
521    /// Paint decorations that must appear **on top of all children**.
522    ///
523    /// Called by [`paint_subtree`] after all children have been painted.
524    /// The default implementation is a no-op; override in widgets that need
525    /// to draw overlays (e.g. resize handles, drag previews) that must not
526    /// be occluded by child content.
527    fn paint_overlay(&mut self, _ctx: &mut dyn DrawCtx) {}
528
529    /// Called after `paint`, child painting, and optional overlay painting.
530    ///
531    /// Most widgets do not need this. It exists for widgets that intentionally
532    /// open a backend compositing scope in `paint` and must close it after all
533    /// descendants have rendered into that scope.
534    fn finish_paint(&mut self, _ctx: &mut dyn DrawCtx) {}
535
536    /// Paint app-level overlays after the entire widget tree has been painted.
537    ///
538    /// The traversal preserves this widget's local transform but skips ancestor
539    /// clips and retained parent redraw requirements. Use this for portal-style
540    /// UI that draws outside normal bounds while still participating in the
541    /// widget tree's Z order.
542    fn paint_global_overlay(&mut self, _ctx: &mut dyn DrawCtx) {}
543
544    /// Return a clip rectangle (in local coordinates) that constrains all child
545    /// painting.  `paint_subtree` applies this clip before recursing into
546    /// children, then restores the previous clip state afterward.  The clip does
547    /// **not** affect `paint_overlay`, which runs after the clip is removed.
548    ///
549    /// The default clips children to this widget's own bounds, preventing
550    /// overflow.  Override to return a narrower rect (e.g. Window clips to the
551    /// content area below the title bar, or an empty rect when collapsed).
552    fn clip_children_rect(&self) -> Option<(f64, f64, f64, f64)> {
553        let b = self.bounds();
554        Some((0.0, 0.0, b.width, b.height))
555    }
556
557    // -------------------------------------------------------------------------
558    // Layout properties (universal — every widget carries these)
559    // -------------------------------------------------------------------------
560
561    /// Outer margin around this widget in logical units.
562    ///
563    /// The parent layout reads this to compute spacing and position.
564    /// Default: [`Insets::ZERO`].
565    fn margin(&self) -> Insets {
566        Insets::ZERO
567    }
568
569    /// Horizontal anchor: how this widget sizes/positions itself horizontally
570    /// within the slot the parent assigns.
571    /// Default: [`HAnchor::FIT`] (take natural content width).
572    fn h_anchor(&self) -> HAnchor {
573        HAnchor::FIT
574    }
575
576    /// Vertical anchor: how this widget sizes/positions itself vertically
577    /// within the slot the parent assigns.
578    /// Default: [`VAnchor::FIT`] (take natural content height).
579    fn v_anchor(&self) -> VAnchor {
580        VAnchor::FIT
581    }
582
583    /// Minimum size constraint (logical units).
584    ///
585    /// The parent will never assign a slot smaller than this.
586    /// Default: [`Size::ZERO`] (no minimum).
587    fn min_size(&self) -> Size {
588        Size::ZERO
589    }
590
591    /// Maximum size constraint (logical units).
592    ///
593    /// The parent will never assign a slot larger than this.
594    /// Default: [`Size::MAX`] (no maximum).
595    fn max_size(&self) -> Size {
596        Size::MAX
597    }
598
599    /// Whether [`paint_subtree`] should snap this widget's incoming
600    /// translation to the physical pixel grid.
601    ///
602    /// Defaults to the process-wide
603    /// [`pixel_bounds::default_enforce_integer_bounds`](crate::pixel_bounds::default_enforce_integer_bounds)
604    /// flag so the common case — crisp UI text + strokes — works without
605    /// ceremony.  Widgets with a [`WidgetBase`] should delegate to
606    /// `self.base().enforce_integer_bounds` so per-instance overrides take
607    /// effect; widgets that genuinely want sub-pixel positioning (smooth
608    /// scroll markers, zoomed canvases) override to return `false`.
609    ///
610    /// Mirrors MatterCAD's `GuiWidget.EnforceIntegerBounds` accessor.
611    fn enforce_integer_bounds(&self) -> bool {
612        crate::pixel_bounds::default_enforce_integer_bounds()
613    }
614
615    /// Report the minimum height this widget needs to fully render
616    /// its content when given the supplied `available_w` for width.
617    ///
618    /// Used by parents whose layout strategy depends on a true
619    /// content-required height that's independent of the slot they
620    /// might hand the widget — most importantly by
621    /// `Window::with_tight_content_fit(true)` to enforce "no
622    /// clipping, no whitespace" on the height axis even when the
623    /// content tree contains a flex-fill widget that would
624    /// otherwise return `available.height` from `layout`.
625    ///
626    /// Default returns `min_size().height` — accurate for widgets
627    /// whose minimum doesn't depend on width.  Width-sensitive
628    /// widgets (wrapped text containers like `TextArea`, recursive
629    /// containers like `FlexColumn`) override and compute properly.
630    fn measure_min_height(&self, _available_w: f64) -> f64 {
631        self.min_size().height
632    }
633
634    /// Container widgets (notably [`crate::widgets::Stack`]) call this on each
635    /// child at the start of `layout()`.  A widget that returns `true` is
636    /// moved to the END of its parent's child list — painted last, i.e.
637    /// raised to the top of the z-order.  `take_` semantics: the call is
638    /// also expected to **clear** the request so the child doesn't keep
639    /// getting raised every frame.
640    ///
641    /// Default: no raise ever requested.  `Window` overrides to fire on the
642    /// false→true visibility transition (see its `with_visible_cell`), so
643    /// toggling a demo checkbox on in the sidebar automatically pops that
644    /// window to the front.
645    fn take_raise_request(&mut self) -> bool {
646        false
647    }
648
649    // -------------------------------------------------------------------------
650    // Visibility-gated scheduled draw propagation
651    // -------------------------------------------------------------------------
652    //
653    // The host render loop walks the widget tree from the root to decide
654    // whether a visible subtree has a scheduled draw need such as cursor blink.
655    // Ordinary visual invalidation should call `animation::request_draw`, which
656    // also advances the retained-layer invalidation epoch.  `needs_draw` stays
657    // for visibility-gated future/ongoing draw needs: invisible subtrees
658    // (collapsed Window, non-selected TabView tab, off-viewport content)
659    // must NOT keep the app in a continuous draw loop.
660
661    /// Return `true` if this widget, or any visible descendant, has an ongoing
662    /// draw need that should keep the host drawing.
663    ///
664    /// The default walks visible children.  Widgets with their own pending
665    /// state OR that state with the default walk — see `WidgetBase` helpers.
666    fn needs_draw(&self) -> bool {
667        if !self.is_visible() {
668            return false;
669        }
670        self.children().iter().any(|c| c.needs_draw())
671    }
672
673    /// Return the earliest wall-clock instant at which this widget (or any
674    /// visible descendant) wants the next draw.  `None` = no scheduled wake.
675    /// The host loop turns a `Some(t)` into `ControlFlow::WaitUntil(t)` so
676    /// e.g. a cursor blink fires without continuous polling.
677    ///
678    /// Same visibility contract as [`needs_draw`]: hidden subtrees return
679    /// `None` regardless of what the widget *would* ask for if shown.
680    fn next_draw_deadline(&self) -> Option<web_time::Instant> {
681        if !self.is_visible() {
682            return None;
683        }
684        let mut best: Option<web_time::Instant> = None;
685        for c in self.children() {
686            if let Some(t) = c.next_draw_deadline() {
687                best = Some(match best {
688                    Some(b) if b <= t => b,
689                    _ => t,
690                });
691            }
692        }
693        best
694    }
695}
696
697mod app;
698mod paint;
699mod tree;
700
701pub use app::App;
702pub use paint::{current_paint_clip, paint_global_overlays, paint_subtree};
703pub use tree::{
704    active_modal_path, collect_inspector_nodes, current_mouse_world, current_viewport,
705    dispatch_event, dispatch_unconsumed_key, find_widget_by_id, find_widget_by_id_mut,
706    find_widget_by_type, global_overlay_hit_path, hit_test_subtree, set_current_mouse_world,
707    set_current_viewport, InspectorNode,
708};