Skip to content

fix(widgets): caption button hover, repaint, custom icon and disabled state - #598

Open
palmoni5 wants to merge 2 commits into
leanflutter:mainfrom
palmoni5:upstream/caption-button-states
Open

palmoni5 wants to merge 2 commits into
leanflutter:mainfrom
palmoni5:upstream/caption-button-states

Conversation

@palmoni5

@palmoni5 palmoni5 commented Nov 10, 2026 •

Copy link
Copy Markdown

Problem

WindowCaptionButton has several state and rendering issues:

  • Hover is tracked with MouseRegion.onHover, which calls setState on every pointer movement inside the button, so moving the mouse over a caption button rebuilds it on every event.
  • The four Chrome icon painters return false from shouldRepaint, so an icon can keep a stale color after a hover, press or brightness change.
  • The icon constructor argument is accepted but never rendered.
  • A button with onPressed: null still shows hover/press feedback, uses the normal icon color and exposes no semantics.
  • The dark-mode disabled icon color is black at 36% opacity, nearly invisible on a dark caption.

Change

  • Hover is tracked with onEnter/onExit, and setState is skipped when hover/press state does not change.
  • The painters repaint when their color differs.
  • A custom icon is rendered inside an IconTheme carrying the current state color and a 16px size, so Icon widgets follow hover/press/disabled colors.
  • Disabled buttons use the disabled icon color, show no hover/press feedback, cancel a press in progress when the button becomes disabled, and expose button + enabled semantics. Hover is still tracked while disabled, so re-enabling under a resting pointer shows the hover state immediately.
  • The dark disabled icon color is white at 36%, matching the other dark-mode icon colors.

Default colors and layout are otherwise unchanged.

Tests

New test/window_caption_button_test.dart: painter repaint on color change, hover enter/exit with no frame scheduled for in-button movement, custom icon rendering and coloring, disabled color and input handling in dark mode, disabling mid-press and re-enabling under the pointer, and semantics. These tests fail against the previous implementation.

dart format --set-exit-if-changed ., flutter analyze (package and example) and flutter test (package and example) pass locally (Flutter 3.47.7, Windows), and the full CI workflow passes on the identical commit in my fork.

Related

Part of a set of independent PRs, each based on main and mergeable on its own: widgets #598 (this), #599, #600, #601; legacy bridge #602, #603, #604, #605, #606.

Touches window_caption_button.dart like #599 (glyph Path cache); the two merge cleanly in either order. #599 makes the repaints enabled here cheaper.

… handling

- Track hover with MouseRegion onEnter/onExit instead of onHover, and skip
  setState when hover/press state is unchanged, so pointer movement inside
  a button no longer rebuilds it on every event.
- Make the four chrome icon painters repaint when their color changes;
  shouldRepaint previously returned false, so icons could keep a stale
  color after hover, press or brightness changes.
- Render the `icon` argument, which was accepted but ignored, and give it
  an IconTheme with the current state color and a 16px size.
- Treat a button without onPressed as disabled: use the disabled icon
  color, show no hover/press feedback, cancel an in-progress press when
  the button becomes disabled, and expose button/enabled semantics.
  Hover is still tracked while disabled, so re-enabling under a resting
  pointer shows the hover state immediately.
- Use a light disabled icon color in dark mode; it was black, which is
  nearly invisible on a dark title bar.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant