Add S-100 Night chrome theme variant#145
Merged
Merged
Conversation
Introduces an S-100-tuned near-black chrome theme variant alongside the stock Avalonia Light and Dark variants. The new variant is the first phase of the chrome-theming initiative scoped in docs/design/s100-chrome-theme-spike.md. - ChromeTheme enum + ChromeThemes static registry that exposes the custom ThemeVariant (inherits from Dark for resource-key fallback). - App.axaml ThemeDictionary x:Key="S100Night" with ~12 Color overrides for window / panel / dialog surfaces, text, and borders. Status colours intentionally inherit from Dark for now. - IThemeService extended with Current/SetTheme/ThemeChanged; ToggleTheme and IsDarkTheme kept for back-compat. ThemeService routes through Application.Current.RequestedThemeVariant. - ViewerSettings.ChromeTheme persists the user's selection. - SettingsViewModel.SelectedChromeTheme + ChromeThemeChanged event. Ctor signature unchanged so existing tests keep compiling. - App.axaml.cs restores chrome on startup and wires the chrome→map palette coupling: picking a chrome variant resets SelectedPalette to its default per ChromeThemes.GetDefaultPaletteFor (Light/Dark→Day, S100Night→Night). Users can then override the map palette manually. - MainViewModel subscribes to ThemeChanged so settings-driven changes keep IsDarkTheme in sync (not just the title-bar toggle). - CompassRoseView generalises its inline ThemeVariant.Dark check so S100Night does not pale-out on near-black backgrounds. - SettingsView.axaml gains a Chrome Theme ComboBox with help text and ToolTip.Tip; new ChromeThemeNameConverter mirrors the existing PaletteTypeNameConverter pattern. - Strings.resx/Strings.cs additions for the new selector + labels. - 8 new tests in SettingsViewModelChromeThemeTests cover persistence, parsing, event semantics, and the ChromeThemes helper. All 428 viewer tests pass. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…0-chrome-themes # Conflicts: # src/EncDotNet.S100.Viewer/Services/IThemeService.cs # src/EncDotNet.S100.Viewer/Services/ThemeService.cs
Contributor
Performance Gate✅ PASSED — no regressions. Threshold: 10.0%, MAD multiplier (k): 3.0, retry-zone mult: 2.0× Scenario summary
exchange-set-openIteration statistics
Spans (sum of all iterations)
Metrics
s101-portray-coldIteration statistics
Spans (sum of all iterations)
Metrics
s101-portray-warmIteration statistics
Spans (sum of all iterations)
Metrics
s101-render-warmIteration statistics
Spans (sum of all iterations)
Metrics
s102-coverageIteration statistics
Spans (sum of all iterations)
Metrics
s102-coverage-openIteration statistics
Spans (sum of all iterations)
Metrics
s102-coverage-render-largeIteration statistics
Spans (sum of all iterations)
Metrics
s124-vectorIteration statistics
Spans (sum of all iterations)
Metrics
s201-vectorIteration statistics
Spans (sum of all iterations)
Metrics
Generated by EncDotNet.S100.PerfReport gate command |
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.
Summary
Adds an
S100NightAvaloniaThemeVariantalongside the stock Light and Dark, giving the viewer chrome (panels, title bar, dialogs, buttons) a near-black, red-shifted look that preserves dark-adapted vision. The chrome theme is the user's primary axis: picking S-100 Night also flips the map portrayal palette to Night by default, while the map dropdown remains independently overridable for one-off inspection. Light and Dark are treated as system-level preferences and map to the Day portrayal palette.This is Phase 2a of the chrome-theming initiative scoped in
docs/design/s100-chrome-theme-spike.md(also included in this PR). The spike concluded with a "go" recommendation for the bounded S100Night-only scope; Day / Dusk variants are deferred.Approach
ChromeThemeenum +ChromeThemesstatic registry.S100Night = new ThemeVariant("S100Night", inheritVariant: ThemeVariant.Dark)so any resource key we don't override falls back through Dark — keeps the override dictionary small (~12 surface/text/borderColortokens inApp.axaml).IThemeServiceextended withCurrent,SetTheme(ChromeTheme), andThemeChanged. LegacyIsDarkTheme/ToggleThemeretained for back-compat and treat S100Night as a dark variant.ViewerSettings.ChromeThemepersists the selection.SettingsViewModel.SelectedChromeTheme+ChromeThemeChangedevent mirror the existingSelectedPalettepattern. The SettingsViewModel constructor signature is unchanged so the existing tests keep compiling; chrome→map coupling is wired inApp.axaml.csvia the event instead of via a constructor dependency.ToolTip.Tip(perviewer.instructions.md), localised through new strings and aChromeThemeNameConverterthat mirrorsPaletteTypeNameConverter.Application.Current.ActualThemeVariantdirectly because the control-levelActualThemeVariantdoes not always reflect custom variants for overlays sitting on top of the Mapsui control, and gets a dedicated S100Night palette so it disappears into the chrome rather than glowing.Things worth a careful look
App.axaml: the custom variant must be keyed viax:Key="{x:Static services:ChromeThemes.S100Night}". Avalonia'sThemeVariantTypeConverteronly accepts the built-in variant names by string and throwsNotSupportedExceptionat startup otherwise. The error doesn't show up until first launch.InfoColor,SuccessColor,WarningColor,ErrorColor) are intentionally inherited from Dark in this first cut. Retuning them for night is filed as a known follow-up in the spike doc.StubThemeServicetest fakes across the test project were extended in-place to satisfy the larger interface. Two of them already had statefulIsDarkThemesetters and now threadSetThemethroughChromeThemes.IsDarkso they remain useful as test doubles.Spec alignment
Check each spec this PR touches and confirm the relevant skill was
consulted (
.github/skills/<spec>/SKILL.md):s100-framework)s101-enc)s102-bathymetry)s104-water-level)s111-surface-currents)s124-nav-warnings)s129-ukc)Spec section references cited in code/docs:
The chrome theme variant motivation references S-100 Part 9 colour-profile semantics (Day / Dusk / Night portrayal palettes) for map content; the chrome itself is not spec-derived. See
docs/design/s100-chrome-theme-spike.mdfor the full design rationale.Tests
tests/SkippableFactdotnet test --configuration Releasepasses locally8 new tests in
SettingsViewModelChromeThemeTestscover persistence round-tripping, case-insensitive parsing, fallback for invalid values, event semantics, and theChromeThemes.GetDefaultPaletteFor/IsDarkhelpers. Full viewer test suite: 428/428 passing.Documentation
src/<project>/README.mddocs/if user-facing behaviourchanged
docs/design/s100-chrome-theme-spike.mdis the design note that motivated and bounds this work. The Viewer README was not updated as the chrome selector is self-explanatory; happy to add a screenshot/blurb if reviewers prefer.Dependencies
Directory.Packages.props(not in the.csproj)gh-advisory-databasesecurity check run for any new dependencyNo new dependencies.
Breaking changes
IThemeServicegainsCurrent,SetTheme(ChromeTheme), andThemeChanged.IsDarkThemeandToggleThemeare preserved with the same semantics (S100Night counts as a dark variant for the binary signal). Internal-only interface, but any externalIThemeServiceimplementer would need to be updated.ChromeThemeispublicto match the precedent set byPaletteType.