Skip to content

Repaint the Simple theme instead of wearing its defaults - #6

Merged
siegfriedpammer merged 1 commit into
mainfrom
soften-the-chrome
Sep 3, 2026
Merged

Repaint the Simple theme instead of wearing its defaults#6
siegfriedpammer merged 1 commit into
mainfrom
soften-the-chrome

Conversation

@christophwille

@christophwille christophwille commented Sep 1, 2026

Copy link
Copy Markdown
Member

The window read as an unstyled toolkit demo. Not because anyone chose that look, but because
nothing overrode the Simple theme's defaults: a near-black hairline around every list and text
box, a hard rectangle for every control, one white for chrome and content alike, and buttons
shaded like a Win32 dialog.

This repaints the theme. No layout moved, no control was added, no view was restructured, and
the scope accent (ScopePalette, blue / purple / orange) is untouched because it carries
meaning. Three files, +99 lines, all of it declarative.

Before

before

After

after-final

Dark

dark-probe

Dark is unreachable in the app today - nothing calls ThemeManager.UpdateTheme, so the Theme
menu was never wired up - but leaving its dictionary stock would have made it wrong the moment
something does. Captured by forcing RequestedThemeVariant="Dark" for one build, then reverting.

What actually changed

Measured off the two captures, not eyeballed:

before after
list / text-box border #888888 #D8DCE1
button face Win32 gradient flat #F6F7F9
corner radius 0 everywhere 4px controls, 5px lists, 3px rows
chrome ground #FFFFFF #F4F5F7
primary text #000000 #1F2328
secondary text #000000 at opacity #656D76
list selection stock Avalonia #119EDA #3794FF at 33 / 66 / 99%

How it is done

src/Stampeded/App.axaml carries almost all of it, because the theme resolves every colour
through a named resource key and Application.Resources is consulted before
Application.Styles. Restating the keys repaints the whole app without touching a template.

Two details that are not obvious and are the reason both halves of the palette are present:

  • Brushes, not colours, for the Simple theme. It builds each brush from its colour with a
    StaticResource, resolved once at parse time. Overriding ThemeBorderMidColor alone does
    nothing to ThemeBorderMidBrush.
  • Colours, not brushes, for Dock. It ignores the Theme*Brush keys entirely and builds its
    own Dock* brushes from ThemeBackgroundColor / ThemeForegroundColor /
    ThemeBorderLowColor / ThemeAccentColor with a DynamicResource - the opposite
    arrangement. Without the colour keys the dock chrome would have stayed stock while everything
    around it moved.

Also worth knowing: ThemeControlHighBrush is the pressed face, not a lighter shade -
ControlHigh is high emphasis, not high luminance. Setting it lighter than
ThemeControlMidBrush would have made a pressed button brighten. And ThemeAccentBrush2/3/4,
not HighlightBrush, are what paint list, tree and tab selection.

The other two files:

  • MainWindow.axaml - the root DockPanel takes the chrome tone (this is what shows behind
    the tab strips, the splitters and the menu bar), and the status bar gets a top hairline.
  • StartDocumentView.axaml - one Background on the existing three-column grid, which is what
    turns its 8px gutters into ground and makes the three lists read as three panels.

To keep every existing pane and document looking exactly as it does today, the dock's
ToolContentControl and DocumentContentControl are pinned to the content colour, so only the
frame around them carries the tone. Verified: every content surface sampled is still #FFFFFF.

Two things chased and dropped

  • A rounded document tab. Dock fills the tab with a Panel, which has no CornerRadius to
    bind, so the setter had nowhere to land. Three selectors were tried against the template and
    all reverted rather than left as dead setters. It needs a template replacement.
  • A leaking rounded corner. ListBox applies its Background to the ScrollViewer inside
    its border, so the square fill poked out of the new 5px corners. Fixed by painting
    /template/ Border#border and leaving the inside clear - visible in the pixel at (315, 64),
    which went from white to the chrome tone.

Verification

  • dotnet build Stampeded.slnx - clean, 0 warnings. Compiled bindings are on and
    TreatWarningsAsErrors is set, so a bad resource key or selector would have failed the build.
  • Light and dark both captured and checked; no content surface went grey by accident.
  • dotnet test could not run here: the test host aborts with Microsoft.NETCore.App framework_version=10.0.0 arch=arm64 missing. This reproduces on a clean main, so it is the
    machine and not this change. The tests cover Stampeded.Core, which has no Avalonia
    reference and is not touched.
  • Not verified: a diff document was not opened. No editor resource was changed -
    HighlightColor and the Stampeded.Editor* keys are untouched, and AvaloniaEdit's brushes
    are pinned to the Default variant dictionary, so the Light overrides here are invisible to
    it - but that is reasoning, not a screenshot.

Out of scope

Real work the exploration turned up, each better as its own change:

  • The colour literals. #2EA043 / #F85149 / #D29922 / #40808080 and friends are
    repeated verbatim about thirty times across ten .axaml files and about thirty more times in
    C# (OverviewDocumentViewModel.cs:142, StartDocumentViewModel.cs:90,
    Diff/DiffLineBackgroundRenderer.cs:20, Panes/ChangeMapPaneViewModel.cs:60, and others).
    They are a de-facto GitHub Primer palette that nothing names.
  • Three copies of card chrome with three different corner radii for one visual idea:
    ReviewDocumentView.axaml:25 (3), CommentsPaneView.axaml:48 (3),
    OverviewDocumentView.axaml:98 (3), and the popup border repeated at
    DiffDocumentView.axaml:39 (4), SideBySideDocumentView.axaml:15 (4), MainWindow.axaml:227 (6).
  • The dock chrome. ToolChromeControl, ToolTabStripItem and ProportionalStackPanelSplitter
    are still raw theme - the pane title bars and the bottom tab strip are the last unstyled
    surfaces. The hooks exist (PART_Grip for a tool title bar, DockSurfacePanelBrush,
    DockSplitterIdleBrush), but ToolTabStripItem does not template-bind CornerRadius at all,
    so part of it means replacing templates. Same obstacle as the document tab above.
  • A dark-mode bug in the vendored tree view. Controls/TreeView/SharpTreeView.axaml:18-26 -
    the expander's #FFB0B0B0 border, white-to-#FFC0B7A6 gradient and black arrow are
    hard-coded light-only, so it is wrong in dark. A bug rather than polish, and the file is
    vendored from ILSpy, so per src/Stampeded.Core/TreeView/README.md it wants fixing upstream too.
  • A status bar that vanishes. MainWindow.axaml:217 is IsVisible-bound to Busy.IsBusy,
    so the window's content shifts every time work starts and stops. Making it permanent is a
    behaviour change, not a style one.
  • The Theme menu. ThemeManager.UpdateTheme has no caller, so the dark palette this PR
    fills in cannot be reached from the UI.

The window read as an unstyled toolkit demo: a near-black hairline around
every list and text box, a hard rectangle for every control, one white for
chrome and content alike, and buttons shaded like a Win32 dialog. None of
that is a decision anyone made - it is what the Simple theme looks like
when nothing overrides it, and a tool someone reads diffs in for an hour at
a stretch should have a frame quiet enough to disappear.

The theme resolves every colour through a named resource key, and
Application.Resources is consulted before Application.Styles, so restating
those keys is enough to repaint the whole app without touching a template.
Only the brushes are restated, not the colours behind them: the theme
builds each brush from its colour with a StaticResource, resolved once at
parse time, so overriding a colour alone changes nothing there. The colours
are restated too, because Dock builds its own brushes from them with a
DynamicResource - which is the opposite arrangement, and the reason both
halves are needed.

The chrome is given a ground a shade off the content white, so a list reads
as a surface laid on the frame rather than as more of the same sheet. Both
theme variants are set; Dark is unreachable today, since nothing calls
ThemeManager.UpdateTheme, but leaving it stock would have made it wrong the
moment something does.

Two things a resource key cannot reach. A ListBox applies its background to
the ScrollViewer inside its border, so a square fill poked out of the newly
rounded corners - the border is painted instead and the inside left clear.
A document tab stays square: Dock fills it with a Panel, which has no corner
radius to bind, and replacing that template is a larger change than this one.

Assisted-by: Claude:claude-opus-5:Claude Code
@christophwille
christophwille marked this pull request as ready for review September 1, 2026 13:08
@siegfriedpammer
siegfriedpammer merged commit 8c6b3c6 into main Sep 3, 2026
3 checks passed
@christophwille
christophwille deleted the soften-the-chrome branch September 3, 2026 13:11
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.

2 participants