masonry_core: Add first-class pseudo-class state on WidgetState#1669
Open
waywardmonkeys wants to merge 2 commits intolinebender:mainfrom
Open
masonry_core: Add first-class pseudo-class state on WidgetState#1669waywardmonkeys wants to merge 2 commits intolinebender:mainfrom
waywardmonkeys wants to merge 2 commits intolinebender:mainfrom
Conversation
Contributor
Author
|
@PoignardAzur This is a demonstration of what we talked about yesterday. |
012a432 to
9304d79
Compare
waywardmonkeys
commented
Feb 19, 2026
| // Checked state impacts appearance and accessibility node | ||
| this.ctx.request_render(); | ||
| this.ctx.set_pseudo(PSEUDO_TOGGLED, checked); | ||
| this.ctx.request_accessibility_update(); |
Contributor
Author
There was a problem hiding this comment.
This was calling request_render before, but now relies on what set_pseudo does, the accessibility (and a post-paint) are the missing bits. This widget doesn't need a post-paint.
Masonry tracks interaction states (hovered, active, focused, disabled) as individual booleans on `WidgetState`. When these change, the framework sets `request_pre_paint` and `needs_paint`, then calls `widget.update()`. But `request_paint` (for `Widget::paint()`) is **not** set by the framework— widgets must call `ctx.request_paint_only()` in their `update()` to get `paint()` re-run. This is fragile: every widget that changes appearance on hover/focus/active must remember to do this, and forgetting causes rendering bugs that are easy to miss. There's also no unified state representation that a future style/selector system could match against. CSS-like selectors (`:hover`, `:focus`, `:disabled`, `:checked`) need a single queryable bitfield, not scattered booleans. Introduce `PseudoId(u8)` and `PseudoSet(u64)` as a compact bitfield on `WidgetState` that mirrors the existing boolean interaction flags: - **Built-in pseudo IDs:** `HOVER(0)`, `ACTIVE(1)`, `FOCUS(2)`, `FOCUS_WITHIN(3)`, `DISABLED(4)`. Bits 5–63 are available for widget-defined states. - **Context API:** `ctx.set_pseudo(id, value)` on mutable contexts auto-invalidates `pre_paint` + `paint` when state changes. `ctx.pseudos()` and `ctx.has_pseudo(id)` on all contexts for reads. - **Framework wiring:** The update pass now sets pseudo bits alongside the existing boolean flags at all five interaction sites (hover, active, focus, focus-within, disabled). Also adds the missing `request_paint = true` at each site, fixing the root fragility. - **Widget-defined state:** `PSEUDO_TOGGLED(5)` for checkboxes and switches. `Checkbox::set_checked` and `Switch::set_on` now use `ctx.set_pseudo()` instead of `request_render()`, and their `update()` methods no longer need manual `request_paint_only()` calls. - **Style/selector matching:** With pseudo state unified in a bitfield, a selector engine can match `:hover`, `:active`, `:focus`, `:disabled`, `:checked` etc. against `widget_state.pseudos` in a single bitmask comparison. - **Pseudo registry:** A runtime registry for widget/app-defined pseudo IDs (allocating from bits 5+) so names can be used in stylesheets. - **Migrate remaining booleans:** The existing `is_hovered`, `is_active`, etc. booleans could eventually be replaced by pseudo-set queries, reducing `WidgetState` size. - **Broader widget adoption:** Other toggle-like widgets (disclosure panels, radio buttons) can adopt `PSEUDO_TOGGLED` or define their own pseudo states.
9304d79 to
c46240e
Compare
waywardmonkeys
commented
Feb 19, 2026
| self.widget_state.request_paint = true; | ||
| self.widget_state.needs_paint = true; | ||
| } | ||
| } |
Contributor
Author
There was a problem hiding this comment.
In the future, it would be up to the style engine to decide what needs requesting. This is just a step along the way.
waywardmonkeys
commented
Feb 19, 2026
| /// Pseudo-class for toggled widgets (checkboxes, switches, etc.). | ||
| /// | ||
| /// Widget-defined pseudo state at bit 5. | ||
| pub const PSEUDO_TOGGLED: core::PseudoId = core::PseudoId(5); |
Contributor
Author
There was a problem hiding this comment.
This is an example of doing it outside of PseudoId and would, in the future, be done with a registry. But not sure where to hang that registry from.
Now that the framework's update passes set `request_paint` alongside `request_pre_paint` and `needs_paint` for all interaction state changes (including `ChildFocusChanged`, `ChildHoveredChanged`, and `ChildActiveChanged` on ancestor widgets), individual widgets no longer need to manually call `request_paint_only()` or `request_pre_paint()` in their `update()` methods for these events. Widgets removed from: - `Label`: `DisabledChanged` → `request_paint_only()` - `Badge`: `DisabledChanged` → `request_paint_only()` - `TextInput`: `ChildFocusChanged` → `request_pre_paint()` - `Split`: `FocusChanged/HoveredChanged/ActiveChanged/DisabledChanged` → `request_paint_only()` Widgets NOT changed (these are widget-specific logic, not interaction state responses): - `Button`: pointer capture in `on_pointer_event` - `Spinner`: animation frame paint - `TextArea`: cursor blink, window focus selection color - `Label`/`TextArea`: `set_hint()` mutators - `Split`: `set_draggable()`/`set_bar_solid()` mutators - Property-change handlers in checkbox, progress_bar, divider, slider
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Motivation
Masonry tracks interaction states (hovered, active, focused, disabled) as individual booleans on
WidgetState. When these change, the framework setsrequest_pre_paintandneeds_paint, then callswidget.update(). Butrequest_paint(forWidget::paint()) is not set by the framework— widgets must callctx.request_paint_only()in theirupdate()to getpaint()re-run. This is fragile: every widget that changes appearance on hover/focus/active must remember to do this, and forgetting causes rendering bugs that are easy to miss.There's also no unified state representation that a future style/selector system could match against. CSS-like selectors (
:hover,:focus,:disabled,:checked) need a single queryable bitfield, not scattered booleans.Changes
Introduce
PseudoId(u8)andPseudoSet(u64)as a compact bitfield onWidgetStatethat mirrors the existing boolean interaction flags:HOVER(0),ACTIVE(1),FOCUS(2),FOCUS_WITHIN(3),DISABLED(4). Bits 5–63 are available for widget-defined states.ctx.set_pseudo(id, value)on mutable contexts auto-invalidatespre_paint+paintwhen state changes.ctx.pseudos()andctx.has_pseudo(id)on all contexts for reads.request_paint = trueat each site, fixing the root fragility.PSEUDO_TOGGLED(5)for checkboxes and switches.Checkbox::set_checkedandSwitch::set_onnow usectx.set_pseudo()instead ofrequest_render(), and theirupdate()methods no longer need manualrequest_paint_only()calls.Next steps
:hover,:active,:focus,:disabled,:checkedetc. againstwidget_state.pseudosin a single bitmask comparison.is_hovered,is_active, etc. booleans could eventually be replaced by pseudo-set queries, reducingWidgetStatesize.PSEUDO_TOGGLEDor define their own pseudo states.