Fix faint caret in edit box after system dark-to-light theme switch (color tags)#11604
Conversation
When Theme is set to System and the OS switches from dark to light, RequestedThemeVariant stays at Default so Avalonia does not trigger a full style re-evaluation. The dark-mode style that set TextArea CaretBrushProperty to white is removed by RemoveLighterDark(), but AvaloniaEdit's caret renderer retains a stale bright brush because CaretBrushProperty falls to null rather than to an explicit value. Apply a minimal style that sets CaretBrushProperty to black when the system variant is light, stored in _lighterDarkStyle so RemoveLighterDark cleans it up on the next theme switch. This fires a clean white-to-black property-changed notification that the caret renderer responds to.
niksedk
left a comment
There was a problem hiding this comment.
Review: looks correct and well-scoped
The diagnosis holds up against the code. ApplyLighterDark() sets TextArea.CaretBrushProperty to the white foreground (UiTheme.cs:514), and the System path keeps RequestedThemeVariant = Default on an OS switch (:72), so the full re-evaluation that fixes explicit theme changes never fires. Forcing an explicit White → Black transition is a sound, minimal way to kick the AvaloniaEdit caret renderer.
What's right
- Symmetry is complete. dark→light now sets caret Black (new
elsebranch); light→dark sets caret White (existingApplyLighterDark). Both directions fire an explicit property change, so re-toggling works repeatedly. - No style leak.
SetCurrentThemealways callsRemoveLighterDark()first (:67), which removes whatever is in_lighterDarkStyle, so the caret-fix style is cleaned up on the next switch like any other. - Correctly scoped. The style targets
TextArea(AvaloniaEdit only), so the plain-TextBoxpath (color tags off) is an untouched no-op — matching the "only with color tags" repro. - Targets only
CaretBrush. In light mode the default Fluent theme already supplies the right foreground; only the caret was stale, so forcing just the caret is the right call.
Minor nits (non-blocking)
- Field name is misleading. Storing a light-theme caret fix in
_lighterDarkStylereads oddly. Functionally fine because removal is centralized, but a short comment on the assignment (or renaming the field to e.g._themeOverrideStyle) would stop a future reader assuming it only ever holds dark styling. - Prefer
Brushes.Black.new SolidColorBrush(Colors.Black)allocates each switch;Brushes.Blackis a cached immutable brush. Trivial. - Runs on first startup too. In System+light at launch the fix is applied even though there's no stale state yet — harmless (caret is black anyway), just slightly redundant.
On the architectural note
Agree with the framing. Take this targeted fix now; if a third component shows stale state after a system switch, do the unified SetLayout()-on-OnActualThemeVariantChanged rebuild rather than accumulating more per-component patches.
Suggested manual check
No tests, which is acceptable for theme/visual behavior. Worth a manual pass on Windows as well as macOS: toggle OS dark→light→dark with color tags on and confirm the caret stays visible each direction, and confirm switching Theme away from System to explicit Light/Dark still leaves a correct caret (should be fine, since RemoveLighterDark runs first).
The field now holds light-theme overrides too, so the old name was misleading. Also replace new SolidColorBrush(Colors.Black) with the cached Brushes.Black singleton in ApplyLightThemeCaretFix.
|
Thanks for the review. Addressed both nits in a follow-up commit:
Agreed on the startup redundancy and the architectural note — leaving those for a future pass if a third component ends up needing the same treatment. |
Problem
When Theme is set to System and macOS switches from dark to light mode, the text cursor (caret) in the subtitle edit box remains faint, hardly visible. This only occurs when color tags are enabled in Settings, because that activates the AvaloniaEdit
TextEditorinstead of a plainTextBox.Root cause
The system theme auto-switch and the Settings theme change follow different code paths and are not equivalent:
RequestedThemeVariantDefault→Light)DefaultSetLayout)When the dark-mode style is removed by
RemoveLighterDark(),TextArea.CaretBrushPropertydrops from an explicitly-set white brush tonull(unset). AvaloniaEdit's caret renderer retains a stale bright brush — it does not re-read theForegroundfallback cleanly when the property goes tonullrather than to an explicit new value. BecauseRequestedThemeVariantdoes not change, Avalonia never fires the re-evaluation that would correct it.Note: the earlier toolbar icon fix I made is a different class of problem — toolbar icons are PNG files loaded from a theme-specific folder at construction time, so no amount of style re-evaluation helps;
RebuildToolbar()is the correct fix for that.Fix
Apply a minimal style via
ApplyLightThemeCaretFix()that explicitly setsTextArea.CaretBrushProperty = Blackwhen the system variant switches to light. This fires a cleanWhite → Blackproperty-changed notification that the caret renderer responds to. The style is stored in_lighterDarkStylesoRemoveLighterDark()removes it automatically on the next theme switch.Architectural note
This is a targeted, per-component fix. If more components exhibit stale state after a system theme switch, the correct long-term solution is the heavy approach: hook
OnActualThemeVariantChangedto also callSetLayout()afterSetCurrentTheme(), mirroring whatApplySettings()does. This rebuilds the entire layout — DataGrid, TextEditors, toolbar — creating all controls fresh with the correct theme active. It eliminates the stale-state class of bug entirely, at the cost of a brief visual reconstruction on every OS theme switch. Further per-component patches should be avoided in favor of that approach if a third component turns up with the same problem.If you prefer to go straight for the heavy approach, let me know, we close this PR, and I'll submit one for the full layout rebuild to unify the Settings and System theme switch paths.