Repaint the Simple theme instead of wearing its defaults - #6
Merged
Conversation
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
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.
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 carriesmeaning. Three files, +99 lines, all of it declarative.
Before
After
Dark
Dark is unreachable in the app today - nothing calls
ThemeManager.UpdateTheme, so the Thememenu 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:
#888888#D8DCE1#F6F7F90everywhere#FFFFFF#F4F5F7#000000#1F2328#000000at opacity#656D76#119EDA#3794FFat 33 / 66 / 99%How it is done
src/Stampeded/App.axamlcarries almost all of it, because the theme resolves every colourthrough a named resource key and
Application.Resourcesis consulted beforeApplication.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:
StaticResource, resolved once at parse time. OverridingThemeBorderMidColoralone doesnothing to
ThemeBorderMidBrush.Theme*Brushkeys entirely and builds itsown
Dock*brushes fromThemeBackgroundColor/ThemeForegroundColor/ThemeBorderLowColor/ThemeAccentColorwith aDynamicResource- the oppositearrangement. Without the colour keys the dock chrome would have stayed stock while everything
around it moved.
Also worth knowing:
ThemeControlHighBrushis the pressed face, not a lighter shade -ControlHigh is high emphasis, not high luminance. Setting it lighter than
ThemeControlMidBrushwould have made a pressed button brighten. AndThemeAccentBrush2/3/4,not
HighlightBrush, are what paint list, tree and tab selection.The other two files:
MainWindow.axaml- the rootDockPaneltakes the chrome tone (this is what shows behindthe tab strips, the splitters and the menu bar), and the status bar gets a top hairline.
StartDocumentView.axaml- oneBackgroundon the existing three-column grid, which is whatturns 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
ToolContentControlandDocumentContentControlare pinned to the content colour, so only theframe around them carries the tone. Verified: every content surface sampled is still
#FFFFFF.Two things chased and dropped
Panel, which has noCornerRadiustobind, 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.
ListBoxapplies itsBackgroundto theScrollViewerinsideits border, so the square fill poked out of the new 5px corners. Fixed by painting
/template/ Border#borderand 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 andTreatWarningsAsErrorsis set, so a bad resource key or selector would have failed the build.dotnet testcould not run here: the test host aborts withMicrosoft.NETCore.App framework_version=10.0.0 arch=arm64missing. This reproduces on a cleanmain, so it is themachine and not this change. The tests cover
Stampeded.Core, which has no Avaloniareference and is not touched.
HighlightColorand theStampeded.Editor*keys are untouched, and AvaloniaEdit's brushesare pinned to the
Defaultvariant dictionary, so theLightoverrides here are invisible toit - but that is reasoning, not a screenshot.
Out of scope
Real work the exploration turned up, each better as its own change:
#2EA043/#F85149/#D29922/#40808080and friends arerepeated verbatim about thirty times across ten
.axamlfiles and about thirty more times inC# (
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.
ReviewDocumentView.axaml:25(3),CommentsPaneView.axaml:48(3),OverviewDocumentView.axaml:98(3), and the popup border repeated atDiffDocumentView.axaml:39(4),SideBySideDocumentView.axaml:15(4),MainWindow.axaml:227(6).ToolChromeControl,ToolTabStripItemandProportionalStackPanelSplitterare still raw theme - the pane title bars and the bottom tab strip are the last unstyled
surfaces. The hooks exist (
PART_Gripfor a tool title bar,DockSurfacePanelBrush,DockSplitterIdleBrush), butToolTabStripItemdoes not template-bindCornerRadiusat all,so part of it means replacing templates. Same obstacle as the document tab above.
Controls/TreeView/SharpTreeView.axaml:18-26-the expander's
#FFB0B0B0border, white-to-#FFC0B7A6gradient and black arrow arehard-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.mdit wants fixing upstream too.MainWindow.axaml:217isIsVisible-bound toBusy.IsBusy,so the window's content shifts every time work starts and stops. Making it permanent is a
behaviour change, not a style one.
ThemeManager.UpdateThemehas no caller, so the dark palette this PRfills in cannot be reached from the UI.