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 based on elapsed time. |
|
Create a FlashElement for a QGroupBox with configurable masking. |
|
Create a FlashElement for a list item. |
|
Create a FlashElement for a tree item. |
|
Get mask rectangle for a groupbox child widget. |
|
Get the shared flash QColor with optional opacity (0.0-1.0). |
|
Get flash color from circular palette based on scope_id. |
|
Extract corner radius from widget's stylesheet, with caching. |
|
Invalidate corner radius cache for a widget or all widgets. |
|
Resolve visible child widgets to mask. |
Classes
|
Abstract representation of a flashable UI element. |
|
Unified cache for all overlay geometry calculations. |
Mixin providing batched visual updates at 60fps. |
|
|
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.
- 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.
- __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).
- 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.
- 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.
- is_element_in_viewport(key: str) bool[source]
Check if any element for this key is visible (for viewport culling).
- 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
- 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_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.
- pyqt_reactive.animation.flash_mixin.FlashMixin
alias of
VisualUpdateMixin