pyqt_reactive.animation.flash_mixin

Unified visual update mixin for PyQt widgets.

GAME ENGINE ARCHITECTURE (TRUE O(1) PER WINDOW): - ONE WindowFlashOverlay per top-level window renders ALL flash effects - ALL element types (groupboxes, tree items, list items) register with it - Single paintEvent draws ALL flash rectangles regardless of element count/type - Scales O(1) per window, O(k) per flashing element, regardless of total elements

BATCH COLOR COMPUTATION: - Global 60fps coordinator pre-computes ALL colors in ONE pass - Overlays just do O(1) dict lookups during paintEvent - Total work: O(k) per tick where k = number of flashing elements

ALGEBRAIC SIMPLIFICATIONS (OpenHCS-style): FIX 1: Eliminated global/local flash duality

  • Before: 2 parallel systems (_flash_start_times + _window_flash_start_times)

  • After: 1 unified system (all keys auto-scoped via _get_scoped_flash_key)

  • Reduction: 2 → 1 (50% simpler, 100+ lines removed)

FIX 2: Simplified dirty tracking
  • Before: 4 prev-color dicts, 30 lines of comparison logic

  • After: 0 dicts, direct dict comparison (Qt batches update() calls anyway)

  • Reduction: Removed complex dirty flag system (30 lines → 2 lines)

FIX 3: Unified geometry cache
  • Before: 2 separate caches (_scroll_area_clip_rects + _cached_element_rects)

  • After: 1 OverlayGeometryCache dataclass with single invalidation point

  • Reduction: Single invalidate() method, clearer ownership

Functions

compute_flash_color_at_time(start_time, now)

Compute flash color based on elapsed time.

create_groupbox_element(key, groupbox[, ...])

Create a FlashElement for a QGroupBox with configurable masking.

create_list_item_element(key, list_widget, ...)

Create a FlashElement for a list item.

create_tree_item_element(key, tree, get_index)

Create a FlashElement for a tree item.

get_child_mask_rect(widget, window)

Get mask rectangle for a groupbox child widget.

get_flash_color([opacity, config, base_color])

Get the shared flash QColor with optional opacity (0.0-1.0).

get_flash_color_from_palette(scope_id[, ...])

Get flash color from circular palette based on scope_id.

get_widget_corner_radius(widget)

Extract corner radius from widget's stylesheet, with caching.

invalidate_corner_radius_cache([widget])

Invalidate corner radius cache for a widget or all widgets.

resolve_mask_widgets(widget, preferred_types)

Resolve visible child widgets to mask.

Classes

FlashElement(key, get_rect_in_window[, ...])

Abstract representation of a flashable UI element.

FlashMixin

OverlayGeometryCache(valid, ...)

Unified cache for all overlay geometry calculations.

VisualUpdateMixin()

Mixin providing batched visual updates at 60fps.

WindowFlashOverlay(window)

Transparent overlay that renders ALL flash effects for an entire window.

pyqt_reactive.animation.flash_mixin.get_widget_corner_radius(widget: QWidget) float[source]

Extract corner radius from widget’s stylesheet, with caching.

Searches the widget and its ancestors for border-radius in stylesheets. Returns 0 if no border-radius found (sharp corners).

pyqt_reactive.animation.flash_mixin.invalidate_corner_radius_cache(widget: QWidget | None = None) None[source]

Invalidate corner radius cache for a widget or all widgets.

pyqt_reactive.animation.flash_mixin.get_flash_color_from_palette(scope_id: str, alpha: int = 255, use_parent_scope: bool = True) QColor[source]

Get flash color from circular palette based on scope_id.

Parameters:
  • scope_id – Scope identifier (e.g., “plate::config_field”)

  • alpha – Alpha channel (0-255)

  • use_parent_scope – If True, hash only parent scope (plate path) so all elements in same plate get same color. If False, hash full scope_id.

Returns:

QColor from pre-computed WCAG-compliant palette

pyqt_reactive.animation.flash_mixin.get_flash_color(opacity: float = 1.0, config: FlashConfig | None = None, base_color: QColor | None = None) QColor[source]

Get the shared flash QColor with optional opacity (0.0-1.0).

pyqt_reactive.animation.flash_mixin.compute_flash_color_at_time(start_time: float, now: float, config: FlashConfig | None = None, base_color: QColor | None = None) QColor | None[source]

Compute flash color based on elapsed time. Returns None if animation complete.

PAINT-TIME COMPUTATION: Called during paint, not during timer tick. This moves O(n) color computation from timer to paint (which Qt batches).

class pyqt_reactive.animation.flash_mixin.OverlayGeometryCache(valid: bool = False, scroll_clip_rects: List[QRect] = <factory>, element_rects: Dict[str, ~typing.List[~typing.Tuple[~PyQt6.QtCore.QRect, float] | None]]=<factory>, element_regions: Dict[str, ~typing.List[~PyQt6.QtGui.QPainterPath | None]]=<factory>)[source]

Unified cache for all overlay geometry calculations.

FIX 3: Single cache object with single invalidation point. Replaces separate scroll_area + element caches.

valid: bool = False
scroll_clip_rects: List[QRect]
element_rects: Dict[str, List[Tuple[QRect, float] | None]]
element_regions: Dict[str, List[QPainterPath | None]]
invalidate()[source]

Invalidate entire cache - called on scroll/resize.

__init__(valid: bool = False, scroll_clip_rects: List[QRect] = <factory>, element_rects: Dict[str, ~typing.List[~typing.Tuple[~PyQt6.QtCore.QRect, float] | None]]=<factory>, element_regions: Dict[str, ~typing.List[~PyQt6.QtGui.QPainterPath | None]]=<factory>) None
class pyqt_reactive.animation.flash_mixin.FlashElement(key: str, get_rect_in_window: Callable[[QWidget], QRect | None], get_child_rects: Callable[[QWidget], List[Tuple[QRect, bool]]] | None = None, needs_scroll_clipping: bool = True, source_id: str | None = None, corner_radius: float = 0.0, skip_overlay_paint: bool = False, delegate_widget: QWidget | None = None, get_model_index: Callable[[], Any] | None = None)[source]

Abstract representation of a flashable UI element.

Provides a geometry callback that returns the element’s rect in window coords. Works for ANY element type: groupboxes, tree items, list items, etc.

key: str
get_rect_in_window: Callable[[QWidget], QRect | None]
get_child_rects: Callable[[QWidget], List[Tuple[QRect, bool]]] | None = None
needs_scroll_clipping: bool = True
source_id: str | None = None
corner_radius: float = 0.0
skip_overlay_paint: bool = False
delegate_widget: QWidget | None = None
get_model_index: Callable[[], Any] | None = None
__init__(key: str, get_rect_in_window: Callable[[QWidget], QRect | None], get_child_rects: Callable[[QWidget], List[Tuple[QRect, bool]]] | None = None, needs_scroll_clipping: bool = True, source_id: str | None = None, corner_radius: float = 0.0, skip_overlay_paint: bool = False, delegate_widget: QWidget | None = None, get_model_index: Callable[[], Any] | None = None) None
pyqt_reactive.animation.flash_mixin.get_child_mask_rect(widget: QWidget, window: QWidget) QRect[source]

Get mask rectangle for a groupbox child widget.

This is the single source of truth for child masking geometry used by both STANDARD and INVERSE groupbox flashes. Checkboxes and labels are masked tightly; all other widgets use their full rect size.

Parameters:
  • widget – Widget to mask

  • window – Reference window for coordinate transformation

Returns:

QRect with position and size for masking

pyqt_reactive.animation.flash_mixin.resolve_mask_widgets(widget: QWidget | None, preferred_types: tuple) List[QWidget][source]

Resolve visible child widgets to mask.

If the given widget isn’t a preferred type, attempts to find visible children of preferred types. Falls back to the original widget.

pyqt_reactive.animation.flash_mixin.create_groupbox_element(key: str, groupbox: QGroupBox, leaf_widget: QWidget | None = None, label_widget: QWidget | None = None, use_full_rect: bool = False) FlashElement[source]

Create a FlashElement for a QGroupBox with configurable masking.

Maps groupbox position to WINDOW coordinates (not scroll content coordinates). This accounts for scroll position so rects are in visible window space.

Masking modes (determined by leaf_widget parameter): - leaf_widget=None: STANDARD mode - mask ALL children, flash only frame/background - leaf_widget=widget: INVERSE mode - mask ONLY title + leaf_widget + label_widget, flash frame + all siblings

Parameters:
  • key – Flash key

  • groupbox – The QGroupBox to flash

  • leaf_widget – If provided, use inverse masking (flash siblings, mask this widget)

  • label_widget – Optional label widget to mask (used with leaf_widget in INVERSE mode)

pyqt_reactive.animation.flash_mixin.create_tree_item_element(key: str, tree: QTreeWidget, get_index: Callable[[], Any]) FlashElement[source]

Create a FlashElement for a tree item.

Parameters:
  • key – Flash key

  • tree – The QTreeWidget

  • get_index – Callback that returns the current QModelIndex (handles item recreation)

Note: Uses skip_overlay_paint=True because TreeItemFlashDelegate handles drawing flash BEHIND text (same pattern as list items).

pyqt_reactive.animation.flash_mixin.create_list_item_element(key: str, list_widget: QListWidget, get_row: Callable[[], int]) FlashElement[source]

Create a FlashElement for a list item.

Parameters:
  • key – Flash key

  • list_widget – The QListWidget

  • get_row – Callback that returns the current row index (handles item recreation)

The flash rect is inset from the item rect by the border width so the flash appears behind the text, not behind the borders.

class pyqt_reactive.animation.flash_mixin.WindowFlashOverlay(window: QWidget)[source]

Transparent overlay that renders ALL flash effects for an entire window.

TRUE GAME ENGINE ARCHITECTURE: - ONE instance per top-level window (QMainWindow/QDialog) - Renders ALL element types (groupboxes, tree items, list items) in ONE paintEvent - Elements register via FlashElement with geometry callbacks - Scales O(1) per window regardless of element count or type

VIEWPORT CULLING: Elements outside visible scroll areas return None from their geometry callback and are skipped.

classmethod get_for_window(widget: QWidget) WindowFlashOverlay | None[source]

Get or create the overlay for a top-level window (factory method).

Automatically chooses between OpenGL and QPainter based on config and availability.

Returns None if: - Widget is not yet in a proper window hierarchy - Widget has been deleted (RuntimeError from Qt C++ layer)

classmethod cleanup_window(window: QWidget) None[source]

Remove overlay for a window (call when window closes).

__init__(window: QWidget)[source]
register_element(element: FlashElement) None[source]

Register a flashable element. Multiple elements can share the same key.

CRITICAL: Deduplicate based on (key, source_id) to prevent duplicate registrations while allowing multiple element types (tree item + groupbox) for the same key.

unregister_element(key: str) None[source]

Unregister all elements for a key.

eventFilter(obj, event)[source]

Catch scroll/resize/layout events to invalidate geometry cache.

invalidate_cache()[source]

Public method to invalidate geometry cache.

Call this when programmatically scrolling (e.g., scroll_to_section via tree item click).

classmethod invalidate_cache_for_widget(widget: QWidget) None[source]

Invalidate geometry cache for the overlay covering a widget’s window.

Convenience method for programmatic scroll/resize operations.

resizeEvent(event) None[source]

Resize to cover entire window.

is_element_in_viewport(key: str) bool[source]

Check if any element for this key is visible (for viewport culling).

get_visible_keys() Set[str][source]

Get set of keys for elements currently visible in viewport.

get_visible_keys_for(keys: Set[str]) Set[str][source]

Get visible keys from a specific subset (avoids scanning all elements).

PERFORMANCE FIX: Use cached geometry instead of recalculating every frame. This eliminates expensive coordinate transformations during animation.

paintEvent(event) None[source]

GAME ENGINE: Render ALL flash effects in ONE paint call.

CARMACK OPTIMIZATION: Cache ALL geometry and QRegion objects. Recompute ONLY on scroll/resize events. During smooth animation: ZERO coordinate transformations, ZERO QRegion operations.

class pyqt_reactive.animation.flash_mixin.VisualUpdateMixin[source]

Mixin providing batched visual updates at 60fps.

TRUE O(1) ARCHITECTURE: - Flash timing owned by global coordinator - get_flash_color_for_key() returns pre-computed colors (O(1) lookup) - Window-level overlay renders ALL elements in ONE paintEvent

scope_id: str | None
register_flash_groupbox(key: str, groupbox: QWidget) None[source]

Register a groupbox for flash rendering.

register_flash_groupbox_full(key: str, groupbox: QWidget) None[source]

Register a groupbox for full-rect flash rendering.

Uses the widget’s full geometry (no margin-top offset).

register_flash_tree_item(key: str, tree: QTreeWidget, get_index: Callable[[], Any]) None[source]

Register a tree item for flash rendering.

register_flash_leaf(key: str, groupbox: QWidget, leaf_widget: QWidget, label_widget: QWidget | None = None) None[source]

Register a leaf field for INVERSE flash rendering.

Flashes the groupbox INCLUDING all sibling fields, but masks out: - The groupbox title - The specific leaf widget that changed - The label associated with the leaf widget (if provided)

This highlights “all fields that inherited the change” while keeping the actual changed widget visible.

Uses the unified create_groupbox_element with leaf_widget and label_widget parameters.

reregister_flash_elements() None[source]

Re-register all previously registered flash elements (after overlay cleanup).

queue_visual_update() None[source]

Queue text/placeholder update (debounced).

queue_flash(key: str, timestamp: float | None = None) None[source]

Start or retrigger flash for key (GLOBAL - all windows with this key flash).

Parameters:
  • key – The flash key

  • timestamp – Optional shared timestamp for batch sync (all keys in batch use same time)

queue_flash_local(key: str, *, scoped: bool = True) None[source]

Start flash for key in THIS WINDOW ONLY.

Unlike queue_flash(), this only flashes the element in the current window’s overlay. Used for: - Scroll-to-section navigation (local feedback) - ParameterFormManager resolved value changes (scope-aware, window-local)

Key is automatically scoped to prevent cross-window contamination.

get_flash_color_for_key(key: str) QColor | None[source]

Get pre-computed flash color for key. O(1) dict lookup.

Used by delegates during paint - returns color from global coordinator.

pyqt_reactive.animation.flash_mixin.FlashMixin

alias of VisualUpdateMixin