Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
307 changes: 279 additions & 28 deletions SnipIT.ps1

Large diffs are not rendered by default.

358 changes: 349 additions & 9 deletions Test-SnipIT-Interactive.ps1

Large diffs are not rendered by default.

27 changes: 27 additions & 0 deletions Test-SnipIT.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -2904,6 +2904,33 @@ It 'routes Undo and Redo only after editor and draft ownership' {
-Key Z -Modifiers Ctrl) $null
}

Describe 'Get-SnipFluentPalette'
It 'returns the locked light palette' {
$p = Get-SnipFluentPalette -Mode Light
ShouldBe $p.Accent '#035BA3'
ShouldBe $p.OnAccent '#FFFFFF'
ShouldBe $p.AccentText '#035BA3'
ShouldBe $p.Positive '#043D19'
ShouldBe $p.Destructive '#A60707'
ShouldBe $p.Neutral '#909496'
}
It 'lightens accent text and positive for dark mode contrast' {
$p = Get-SnipFluentPalette -Mode Dark
ShouldBe $p.Accent '#035BA3'
ShouldBe $p.AccentText '#5E9BD0'
ShouldBe $p.Positive '#2E6B42'
ShouldBe $p.Ground '#000000'
}
It 'maps every override key to a palette hex' {
$p = Get-SnipFluentPalette -Mode Light
foreach ($k in @('AccentFillColorDefaultBrush','AccentButtonBackground',
'CheckBoxCheckBackgroundFillChecked','ProgressBarForeground',
'TextOnAccentFillColorPrimaryBrush','AccentTextFillColorPrimaryBrush')) {
if (-not $p.ResourceMap.ContainsKey($k)) { throw "missing key $k" }
if ($p.ResourceMap[$k] -notmatch '^#[0-9A-F]{6}$') { throw "bad hex for $k" }
}
}

Write-Host ""
$total = $script:Pass + $script:Fail
$color = if ($script:Fail -eq 0) { 'Green' } else { 'Red' }
Expand Down
533 changes: 533 additions & 0 deletions docs/superpowers/plans/2026-08-13-fluent-foundation.md

Large diffs are not rendered by default.

3 changes: 2 additions & 1 deletion scripts/Export-SnipITModules.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,8 @@ $moduleFunctions = [ordered]@{
'New-SnipPreviewContext', 'Set-SnipPreviewMenuStyle',
'Connect-SnipPreviewMenuButton', 'Connect-SnipPreviewTransientContextMenu',
'New-SnipSplitControl', 'Set-SnipPropertyIsland',
'Set-SnipPreviewStatusPresentation', 'Set-SnipPreviewResponsiveMode',
'Set-SnipPreviewStatusPresentation', 'Set-SnipPreviewActiveChrome',
'Set-SnipPreviewResponsiveMode',
'Initialize-SnipPreviewAnnotations', 'New-SnipPreviewWindow',
'Show-PreviewWindow'
)
Expand Down
68 changes: 68 additions & 0 deletions src/00-Core.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -1262,6 +1262,74 @@ function Get-SnipThemeTokens {
}
}

function Get-SnipFluentPalette {
# Pure Fluent brand palette for one theme mode.
#
# Returns the locked accent/semantic hexes plus ResourceMap: the exact
# Fluent resource keys the WPF theme dictionary overrides, each mapped to
# one of those hexes. Callers apply ResourceMap verbatim — no WPF types
# are touched here so this stays loadable in -CoreOnly on any platform.
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[ValidateSet('Light','Dark')]
[string]$Mode
)

$isDark = $Mode -eq 'Dark'
$accent = '#035BA3'
$accentHover = if ($isDark) { '#0E68B4' } else { '#024B87' }
$accentPress = if ($isDark) { '#024B87' } else { '#023E70' }
$onAccent = '#FFFFFF'
$accentText = if ($isDark) { '#5E9BD0' } else { '#035BA3' }
$positive = if ($isDark) { '#2E6B42' } else { '#043D19' }

$map = @{
AccentFillColorDefaultBrush = $accent
AccentFillColorSecondaryBrush = $accentHover
AccentFillColorTertiaryBrush = $accentPress
AccentFillColorSelectedTextBackgroundBrush = $accent
TextOnAccentFillColorPrimaryBrush = $onAccent
TextOnAccentFillColorSecondaryBrush = $onAccent
AccentTextFillColorPrimaryBrush = $accentText
AccentTextFillColorSecondaryBrush = $accentText
AccentTextFillColorTertiaryBrush = $accentText
AccentButtonBackground = $accent
AccentButtonBackgroundPointerOver = $accentHover
AccentButtonBackgroundPressed = $accentPress
AccentButtonForeground = $onAccent
AccentButtonForegroundPointerOver = $onAccent
AccentButtonForegroundPressed = $onAccent
AccentButtonBorderBrush = $accent
AccentButtonBorderBrushPointerOver = $accentHover
AccentButtonBorderBrushPressed = $accentPress
CheckBoxCheckBackgroundFillChecked = $accent
CheckBoxCheckBackgroundStrokeChecked = $accent
CheckBoxCheckBackgroundFillCheckedPointerOver = $accentHover
CheckBoxCheckBackgroundStrokeCheckedPointerOver = $accentHover
CheckBoxCheckBackgroundFillCheckedPressed = $accentPress
CheckBoxCheckBackgroundStrokeCheckedPressed = $accentPress
CheckBoxCheckGlyphForegroundChecked = $onAccent
CheckBoxCheckGlyphForegroundCheckedPointerOver = $onAccent
CheckBoxCheckGlyphForegroundCheckedPressed = $onAccent
ProgressBarForeground = $positive
}

[pscustomobject][ordered]@{
Mode = $Mode
Accent = $accent
AccentHover = $accentHover
AccentPressed = $accentPress
OnAccent = $onAccent
AccentText = $accentText
Neutral = '#909496'
Positive = $positive
Destructive = '#A60707'
Ground = if ($isDark) { '#000000' } else { '#FFFFFF' }
ResourceMap = $map
}
}

function Get-SnipContrastRatio {
[CmdletBinding()]
param(
Expand Down
83 changes: 83 additions & 0 deletions src/10-Bootstrap.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -460,6 +460,89 @@ function Uninstall-SnipIT {
}
}

# Reports the Windows app theme as 'Light' or 'Dark'. The registry read is
# injected through -Reader so tests can pin a value without touching HKCU;
# a missing or unreadable value falls back to 'Light'.
function Get-SnipSystemThemeMode {
[CmdletBinding()]
param(
[scriptblock]$Reader = {
try {
Get-ItemPropertyValue `
-Path 'HKCU:\SOFTWARE\Microsoft\Windows\CurrentVersion\Themes\Personalize' `
-Name 'AppsUseLightTheme' -ErrorAction Stop
} catch { $null }
}
)

$value = & $Reader
$numeric = $value -as [int]
if ($null -ne $value -and $null -ne $numeric -and $numeric -eq 0) { 'Dark' } else { 'Light' }
}

# Applies the Fluent palette to one window and returns the mode that was used.
# Sets the experimental .NET 9 ThemeMode through reflection when the type exists
# (older runtimes silently keep the resource overrides only), grounds the window
# background, and writes every ResourceMap entry in as a frozen brush.
function Initialize-SnipWindowTheme {
[CmdletBinding()]
param(
[Parameter(Mandatory)]
[System.Windows.Window]$Window,
[ValidateSet('Light','Dark')]
[string]$Mode
)

if (-not $Mode) { $Mode = Get-SnipSystemThemeMode }
$palette = Get-SnipFluentPalette -Mode $Mode

# Experimental .NET 9 ThemeMode API — reflection only, absent-type safe.
$themeModeType = 'System.Windows.ThemeMode' -as [type]
if ($null -ne $themeModeType) {
try {
$themeMode = [Activator]::CreateInstance($themeModeType, @($Mode))
$app = [System.Windows.Application]::Current
if ($null -ne $app) {
$app.GetType().GetProperty('ThemeMode').SetValue($app, $themeMode)
}
$Window.GetType().GetProperty('ThemeMode').SetValue($Window, $themeMode)
} catch {
Write-SnipDiag -Message 'ThemeMode apply failed' -ErrorRecord $_
}
}

# Glass surfaces (WindowStyle=None + AllowsTransparency) paint their chrome with
# an inner rounded Border and rely on the window itself staying transparent.
# Grounding them would write a Local opaque value over the XAML's Transparent and
# render a square block behind the rounded corners, so skip grounding entirely.
if (-not $Window.AllowsTransparency) {
if ($Mode -eq 'Dark') {
$ground = [System.Windows.Media.SolidColorBrush]::new(
[System.Windows.Media.ColorConverter]::ConvertFromString($palette.Ground))
$ground.Freeze()
$Window.Background = $ground
} else {
$Window.SetResourceReference(
[System.Windows.Controls.Control]::BackgroundProperty,
'ApplicationBackgroundBrush')
}
}

foreach ($entry in $palette.ResourceMap.GetEnumerator()) {
$brush = [System.Windows.Media.SolidColorBrush]::new(
[System.Windows.Media.ColorConverter]::ConvertFromString($entry.Value))
$brush.Freeze()
$Window.Resources[$entry.Key] = $brush
}
$accentColor = [System.Windows.Media.ColorConverter]::ConvertFromString($palette.Accent)
foreach ($colorKey in 'SystemAccentColor','SystemAccentColorPrimary',
'SystemAccentColorSecondary','SystemAccentColorTertiary') {
$Window.Resources[$colorKey] = $accentColor
}

$Mode
}

function New-SnipThemeResources {
[CmdletBinding()]
param(
Expand Down
Loading
Loading