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};