widget/redraw.go

Functions

Functions

func ClearRedrawInTree

ClearRedrawInTree clears the needsRedraw flag on all widgets in the

subtree rooted at w.

 

This is called after a successful draw pass to mark the entire tree

as visually up to date. The next frame will skip rendering unless

a signal change marks widgets dirty again.

func ClearRedrawInTree(w Widget) {
	if w == nil {
		return
	}

	// Clear this widget's flag.
	type redrawClearer interface {
		ClearRedraw()
	}
	if rc, ok := w.(redrawClearer); ok {
		rc.ClearRedraw()
	}

	// Recurse into children.
	for _, child := range w.Children() {
		ClearRedrawInTree(child)
	}
}

func MarkRedrawInTree

MarkRedrawInTree sets the needsRedraw flag on all widgets in the

subtree rooted at w.

 

This is used when a full redraw is required, such as after SetRoot,

theme changes, or window resize. It ensures the next draw pass will

render all widgets.

func MarkRedrawInTree(w Widget) {
	if w == nil {
		return
	}

	// Mark this widget.
	type redrawSetter interface {
		SetNeedsRedraw(bool)
	}
	if rs, ok := w.(redrawSetter); ok {
		rs.SetNeedsRedraw(true)
	}

	// If widget is a PARENTLESS RepaintBoundary (root), invalidate its scene.
	// Root has no parent → propagateDirtyUpward from SetNeedsRedraw is no-op →
	// scene stays clean → stale scene replayed. Only invalidate parentless
	// boundaries, not child boundaries — over-invalidation causes full
	// repaint every frame (entire cyan overlay).
	type boundaryInvalidator interface {
		IsRepaintBoundary() bool
		InvalidateScene()
	}
	if bi, ok := w.(boundaryInvalidator); ok && bi.IsRepaintBoundary() {
		isRoot := true
		if pc, ok2 := w.(interface{ Parent() Widget }); ok2 && pc.Parent() != nil {
			isRoot = false
		}
		if isRoot {
			bi.InvalidateScene()
		}
	}

	// Recurse into children.
	for _, child := range w.Children() {
		MarkRedrawInTree(child)
	}
}

func NeedsRedrawInTree

NeedsRedrawInTree reports whether any widget in the subtree rooted at w

needs re-rendering.

 

This walks the tree starting from w, checking each widget's NeedsRedraw

flag. Returns true as soon as any dirty widget is found, false if the

entire subtree is clean.

 

This is used by the retained-mode rendering system to determine whether

a draw pass is necessary. When NeedsRedrawInTree returns false for the

root widget, the entire frame can be skipped because the previous frame's

output is still valid in the GPU framebuffer.

 

Widgets that do not embed [WidgetBase] (and thus lack the NeedsRedraw

method) are treated as always needing redraw, ensuring correct rendering

for custom widget implementations.

func NeedsRedrawInTree(w Widget) bool {
	if w == nil {
		return false
	}

	// Check this widget's redraw flag.
	type redrawChecker interface {
		NeedsRedraw() bool
	}
	if rc, ok := w.(redrawChecker); ok {
		if rc.NeedsRedraw() {
			return true
		}
	} else {
		// Widget doesn't implement NeedsRedraw — assume it needs redraw
		// for safety (custom widgets without WidgetBase).
		return true
	}

	// Recurse into children.
	for _, child := range w.Children() {
		if NeedsRedrawInTree(child) {
			return true
		}
	}

	return false
}

func NeedsRedrawInTreeNonBoundary

NeedsRedrawInTreeNonBoundary reports whether any NON-BOUNDARY widget in

the subtree needs re-rendering. RepaintBoundary widgets are skipped because

they manage their own dirty state independently. This prevents offscreen

animated boundaries (spinner scrolled out of view) from forcing expensive

root re-recording on every frame.

func NeedsRedrawInTreeNonBoundary(w Widget) bool {
	if w == nil {
		return false
	}

	type boundaryChecker interface {
		IsRepaintBoundary() bool
	}
	isBoundary := false
	if bc, ok := w.(boundaryChecker); ok && bc.IsRepaintBoundary() {
		isBoundary = true
	}

	// Only count non-boundary widgets as dirty triggers.
	// Boundaries manage their own dirty state independently.
	if !isBoundary {
		type redrawChecker interface {
			NeedsRedraw() bool
		}
		if rc, ok := w.(redrawChecker); ok && rc.NeedsRedraw() {
			return true
		}
	}

	// Always recurse into children (including through boundaries)
	// to find dirty non-boundary descendants.
	for _, child := range w.Children() {
		if NeedsRedrawInTreeNonBoundary(child) {
			return true
		}
	}

	return false
}