diff --git a/CompactGUI/MainWindow.xaml.vb b/CompactGUI/MainWindow.xaml.vb
index c0d4beca..13d63ee1 100644
--- a/CompactGUI/MainWindow.xaml.vb
+++ b/CompactGUI/MainWindow.xaml.vb
@@ -1,4 +1,5 @@
Imports System.ComponentModel
+Imports System.Windows.Threading
Imports CompactGUI.Core.Settings
@@ -20,6 +21,8 @@ Class MainWindow : Implements INavigationWindow, INotifyPropertyChanged
Public Sub New(settingsService As ISettingsService, navigationService As INavigationService, serviceProvider As IServiceProvider, snackbarService As CustomSnackBarService, viewmodel As MainWindowViewModel)
+ CharcoalTheme.ApplyApplicationResources()
+
' This call is required by the designer.
InitializeComponent()
' Add any initialization after the InitializeComponent() call.
@@ -34,9 +37,11 @@ Class MainWindow : Implements INavigationWindow, INotifyPropertyChanged
DataContext = viewmodel
ConfigureCompressNavigationItem()
+ CharcoalTheme.ApplyRootBackground(RootContentDialog)
NotifyIconTrayMenu.DataContext = viewmodel
+ AddHandler Loaded, AddressOf MainWindow_Loaded
AddHandler Application.GetService(Of HomeViewModel)().PropertyChanged, AddressOf HVPropertyChanged
AddHandler navigationService.GetNavigationControl.Navigated, AddressOf OnNavigated
@@ -56,6 +61,17 @@ Class MainWindow : Implements INavigationWindow, INotifyPropertyChanged
End Sub
+ Private Sub MainWindow_Loaded(sender As Object, e As RoutedEventArgs)
+ ApplyCharcoalPaletteAfterLayout()
+ End Sub
+
+ Private Sub ApplyCharcoalPaletteAfterLayout()
+ Dispatcher.BeginInvoke(
+ DispatcherPriority.Loaded,
+ New Action(Sub() CharcoalTheme.ApplyToVisualTree(Me)))
+ End Sub
+
+
Private Sub ConfigureCompressNavigationItem()
If NavigationView.MenuItems.Count = 0 Then Return
@@ -82,6 +98,8 @@ Class MainWindow : Implements INavigationWindow, INotifyPropertyChanged
_MainWindowViewModel.IsActive = False
ProgTitle.Visibility = Visibility.Visible
End If
+
+ ApplyCharcoalPaletteAfterLayout()
End Sub
Private Sub HVPropertyChanged(sender As Object, e As PropertyChangedEventArgs)
diff --git a/CompactGUI/Services/CharcoalTheme.vb b/CompactGUI/Services/CharcoalTheme.vb
new file mode 100644
index 00000000..7e8d8b02
--- /dev/null
+++ b/CompactGUI/Services/CharcoalTheme.vb
@@ -0,0 +1,135 @@
+Imports System.Windows.Controls
+Imports System.Windows.Media
+Imports System.Windows.Media.Imaging
+Imports System.Windows.Shapes
+
+Public Module CharcoalTheme
+
+ Private ReadOnly MutedColorMap As New Dictionary(Of Color, Color) From {
+ {Color.FromRgb(&H98, &HA9, &HB9), Color.FromRgb(&HB8, &HB8, &HB8)},
+ {Color.FromRgb(&H4E, &H63, &H79), Color.FromRgb(&H50, &H50, &H50)},
+ {Color.FromRgb(&H59, &H71, &H86), Color.FromRgb(&H68, &H68, &H68)},
+ {Color.FromRgb(&H4B, &H61, &H75), Color.FromRgb(&H5A, &H5A, &H5A)},
+ {Color.FromRgb(&H1F, &H34, &H48), Color.FromRgb(&H20, &H20, &H20)},
+ {Color.FromRgb(&HD, &H2A, &H41), Color.FromRgb(&H18, &H18, &H18)}
+ }
+
+ Public Sub ApplyApplicationResources()
+ If Application.Current Is Nothing Then Return
+
+ 'Secondary/status text stays neutral gray, while interactive selection accents use
+ 'the same crisp blue already present elsewhere in the CompactGUI navigation.
+ Application.Current.Resources("AccentFillColorDisabledBrush") =
+ New SolidColorBrush(Color.FromRgb(&HAA, &HAA, &HAA))
+
+ Dim selectionBlue As New SolidColorBrush(Color.FromRgb(&H4C, &HA5, &HFF))
+ Application.Current.Resources("CheckBoxCheckGlyphForeground") = selectionBlue
+ Application.Current.Resources("CheckBoxCheckBorderBrush") = selectionBlue
+ End Sub
+
+ Public Sub ApplyRootBackground(root As Grid)
+ If root Is Nothing Then Return
+
+ Dim backgroundLayers = root.Children.OfType(Of Border)().Take(3).ToArray()
+ If backgroundLayers.Length < 3 Then Return
+
+ backgroundLayers(0).Background = New SolidColorBrush(Color.FromRgb(&H1A, &H1A, &H1A))
+
+ 'Keep the softly-lit charcoal look from the previous pass, but make the falloff much
+ 'broader and lower-contrast. ScRGB interpolation plus a one-level dither layer below
+ 'breaks up the visible WPF gradient contouring that showed up as large streaks.
+ Dim topGlow As New RadialGradientBrush With {
+ .Center = New Point(0.48, 0.02),
+ .GradientOrigin = New Point(0.48, -0.12),
+ .RadiusX = 1.35,
+ .RadiusY = 1.7,
+ .ColorInterpolationMode = ColorInterpolationMode.ScRgbLinearInterpolation
+ }
+ topGlow.GradientStops.Add(New GradientStop(Color.FromRgb(&H20, &H20, &H20), 0))
+ topGlow.GradientStops.Add(New GradientStop(Color.FromRgb(&H1E, &H1E, &H1E), 0.46))
+ topGlow.GradientStops.Add(New GradientStop(Color.FromRgb(&H1C, &H1C, &H1C), 0.78))
+ topGlow.GradientStops.Add(New GradientStop(Color.FromRgb(&H1A, &H1A, &H1A), 1))
+ backgroundLayers(1).Background = topGlow
+ backgroundLayers(1).Opacity = 1
+
+ backgroundLayers(2).Background = CreateDitherBrush()
+ backgroundLayers(2).Opacity = 1
+ End Sub
+
+ Private Function CreateDitherBrush() As Brush
+ Const tileSize As Integer = 64
+ Const bytesPerPixel As Integer = 4
+ Dim pixels(tileSize * tileSize * bytesPerPixel - 1) As Byte
+ Dim random As New Random(&H434755)
+
+ For pixelIndex = 0 To tileSize * tileSize - 1
+ Dim offset = pixelIndex * bytesPerPixel
+ Dim isLight = random.Next(0, 2) = 0
+
+ pixels(offset) = If(isLight, CByte(255), CByte(0))
+ pixels(offset + 1) = pixels(offset)
+ pixels(offset + 2) = pixels(offset)
+ pixels(offset + 3) = 1
+ Next
+
+ Dim bitmap As New WriteableBitmap(tileSize, tileSize, 96, 96, PixelFormats.Bgra32, Nothing)
+ bitmap.WritePixels(New Int32Rect(0, 0, tileSize, tileSize), pixels, tileSize * bytesPerPixel, 0)
+ bitmap.Freeze()
+
+ Dim brush As New ImageBrush(bitmap) With {
+ .TileMode = TileMode.Tile,
+ .ViewportUnits = BrushMappingMode.Absolute,
+ .ViewboxUnits = BrushMappingMode.Absolute,
+ .Viewport = New Rect(0, 0, tileSize, tileSize),
+ .Viewbox = New Rect(0, 0, tileSize, tileSize),
+ .Stretch = Stretch.None
+ }
+ brush.Freeze()
+ Return brush
+ End Function
+
+ Public Sub ApplyToVisualTree(root As DependencyObject)
+ If root Is Nothing Then Return
+
+ ApplyMutedPalette(root)
+
+ For index = 0 To VisualTreeHelper.GetChildrenCount(root) - 1
+ ApplyToVisualTree(VisualTreeHelper.GetChild(root, index))
+ Next
+ End Sub
+
+ Private Sub ApplyMutedPalette(element As DependencyObject)
+ Dim textBlock = TryCast(element, TextBlock)
+ If textBlock IsNot Nothing Then textBlock.Foreground = MapBrush(textBlock.Foreground)
+
+ Dim control = TryCast(element, Control)
+ If control IsNot Nothing Then
+ control.Foreground = MapBrush(control.Foreground)
+ control.Background = MapBrush(control.Background)
+ control.BorderBrush = MapBrush(control.BorderBrush)
+ End If
+
+ Dim border = TryCast(element, Border)
+ If border IsNot Nothing Then
+ border.Background = MapBrush(border.Background)
+ border.BorderBrush = MapBrush(border.BorderBrush)
+ End If
+
+ Dim shape = TryCast(element, Shape)
+ If shape IsNot Nothing Then
+ shape.Fill = MapBrush(shape.Fill)
+ shape.Stroke = MapBrush(shape.Stroke)
+ End If
+ End Sub
+
+ Private Function MapBrush(brush As Brush) As Brush
+ Dim solid = TryCast(brush, SolidColorBrush)
+ If solid Is Nothing Then Return brush
+
+ Dim replacement As Color
+ If Not MutedColorMap.TryGetValue(solid.Color, replacement) Then Return brush
+
+ Return New SolidColorBrush(Color.FromArgb(solid.Color.A, replacement.R, replacement.G, replacement.B))
+ End Function
+
+End Module
diff --git a/CompactGUI/Views/Components/CompressionMode_Radio.xaml b/CompactGUI/Views/Components/CompressionMode_Radio.xaml
index 85dfc359..a4a0dae3 100644
--- a/CompactGUI/Views/Components/CompressionMode_Radio.xaml
+++ b/CompactGUI/Views/Components/CompressionMode_Radio.xaml
@@ -156,17 +156,6 @@
-
-
-
-
-
-
-
-
-
@@ -189,8 +178,6 @@
Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)" To="0.3"
Duration="0:0:0.2" />
-
-
@@ -240,7 +227,6 @@
Storyboard.TargetProperty="(UIElement.RenderTransform).(ScaleTransform.ScaleY)" To="1"
Duration="0:0:0.2" />
-
@@ -276,6 +262,16 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/CompactGUI/Views/Components/CompressionMode_Radio.xaml.vb b/CompactGUI/Views/Components/CompressionMode_Radio.xaml.vb
index be00820e..1eefa03d 100644
--- a/CompactGUI/Views/Components/CompressionMode_Radio.xaml.vb
+++ b/CompactGUI/Views/Components/CompressionMode_Radio.xaml.vb
@@ -1,6 +1,8 @@
Public Class CompressionMode_Radio
Inherits RadioButton
+ Private Shared ReadOnly SelectionBlueBorder As Brush = New SolidColorBrush(Color.FromRgb(&H4C, &HA5, &HFF))
+ Private Shared ReadOnly UnselectedModeBorder As Brush = New SolidColorBrush(Color.FromArgb(&H38, &HFF, &HFF, &HFF))
Public Shared ReadOnly CompressionModeProperty As DependencyProperty = DependencyProperty.RegisterAttached(
NameOf(CompressionMode),
@@ -170,17 +172,49 @@
InitializeComponent()
AddHandler Me.Loaded, AddressOf OnLoaded
AddHandler Me.Unloaded, AddressOf OnUnloaded
+ AddHandler Me.Checked, AddressOf OnSelectionStateChanged
+ AddHandler Me.Unchecked, AddressOf OnSelectionStateChanged
End Sub
Private Sub OnLoaded(sender As Object, e As RoutedEventArgs)
RemoveHandler GlobalHoverChanged, AddressOf OnGlobalHoverChanged
AddHandler GlobalHoverChanged, AddressOf OnGlobalHoverChanged
+ ApplySelectionBorder()
End Sub
Private Sub OnUnloaded(sender As Object, e As RoutedEventArgs)
RemoveHandler GlobalHoverChanged, AddressOf OnGlobalHoverChanged
End Sub
+ Private Sub OnSelectionStateChanged(sender As Object, e As RoutedEventArgs)
+ ApplySelectionBorder()
+ End Sub
+
+ Private Sub ApplySelectionBorder()
+ If Template Is Nothing Then Return
+
+ Dim card = TryCast(Template.FindName("CheckMark", Me), Wpf.Ui.Controls.Card)
+ If card Is Nothing Then Return
+
+ Dim selected = IsChecked.GetValueOrDefault(False)
+ Dim borderBrush = If(selected, SelectionBlueBorder, UnselectedModeBorder)
+ Dim borderThickness = If(selected, New Thickness(2), New Thickness(1))
+
+ 'Set the Card itself, then also set its rendered ContentBorder directly. The latter
+ 'avoids WPF-UI template/style precedence swallowing the selected outline on some states.
+ card.BorderBrush = borderBrush
+ card.BorderThickness = borderThickness
+ card.ApplyTemplate()
+
+ If card.Template IsNot Nothing Then
+ Dim renderedBorder = TryCast(card.Template.FindName("ContentBorder", card), Border)
+ If renderedBorder IsNot Nothing Then
+ renderedBorder.BorderBrush = borderBrush
+ renderedBorder.BorderThickness = borderThickness
+ End If
+ End If
+ End Sub
+
Private Sub OnGlobalHoverChanged(sender As Object, e As EventArgs)
' Update the local dependency property to trigger visual state
SetValue(IsGloballyHoveredProperty, IsAnyHovered)
@@ -226,6 +260,7 @@
Public Overrides Sub OnApplyTemplate()
MyBase.OnApplyTemplate()
+ ApplySelectionBorder()
If IsForcedDetailed Then
VisualStateManager.GoToState(Me, "MouseOver", False)
End If
diff --git a/CompactGUI/Views/Pages/PendingCompression.xaml.vb b/CompactGUI/Views/Pages/PendingCompression.xaml.vb
index 7893f8bd..5d799e0d 100644
--- a/CompactGUI/Views/Pages/PendingCompression.xaml.vb
+++ b/CompactGUI/Views/Pages/PendingCompression.xaml.vb
@@ -7,6 +7,11 @@ Public Class PendingCompression
Sub New()
InitializeComponent()
_settingsService = Application.GetService(Of ISettingsService)
+ AddHandler Loaded, AddressOf PendingCompression_Loaded
+ End Sub
+
+ Private Sub PendingCompression_Loaded(sender As Object, e As RoutedEventArgs)
+ CharcoalTheme.ApplyToVisualTree(Me)
End Sub
Private Sub CompressionMode_Radio_Checked(sender As Object, e As RoutedEventArgs)