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
104 changes: 104 additions & 0 deletions DesktopClock/ClockTheme.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
using System.Collections.Generic;
using System.Windows.Media;
using DesktopClock.Properties;
using DesktopClock.Utilities;

namespace DesktopClock;

/// <summary>
/// A coherent visual identity for the clock — font, colors, opacity, and shape — applied in one click.
/// </summary>
public sealed class ClockTheme
{
public ClockTheme(string name, string fontFamily, string fontWeight, Color textColor, Color outerColor,
bool backgroundEnabled, double backgroundOpacity, double backgroundCornerRadius, double outlineThickness,
string fontStyle = "Normal")
{
Name = name;
FontFamily = fontFamily;
FontWeight = fontWeight;
FontStyle = fontStyle;
TextColor = textColor;
OuterColor = outerColor;
BackgroundEnabled = backgroundEnabled;
BackgroundOpacity = backgroundOpacity;
BackgroundCornerRadius = backgroundCornerRadius;
OutlineThickness = outlineThickness;
}

public string Name { get; }
public string FontFamily { get; }
public string FontWeight { get; }
public string FontStyle { get; }
public Color TextColor { get; }
public Color OuterColor { get; }
public bool BackgroundEnabled { get; }
public double BackgroundOpacity { get; }
public double BackgroundCornerRadius { get; }
public double OutlineThickness { get; }

/// <summary>
/// Built-in looks, starting with the system-seeded default.
/// </summary>
public static IReadOnlyList<ClockTheme> GetBuiltInThemes()
{
return new[]
{
CreateSystemTheme(),
new ClockTheme("Accent", "Segoe UI", "SemiBold",
Color.FromRgb(0xFF, 0xFF, 0xFF), SystemThemeService.GetSystemAccentColor(),
backgroundEnabled: true, backgroundOpacity: 1, backgroundCornerRadius: 8, outlineThickness: 0.2),
new ClockTheme("Smoke", "Segoe UI", "Normal",
Color.FromRgb(0xF2, 0xF2, 0xF2), Color.FromRgb(0x0A, 0x0A, 0x10),
backgroundEnabled: true, backgroundOpacity: 0.55, backgroundCornerRadius: 10, outlineThickness: 0.2),
new ClockTheme("Terminal", "Consolas", "Bold",
Color.FromRgb(0x00, 0xE5, 0xFF), Color.FromRgb(0x0C, 0x0C, 0x0C),
backgroundEnabled: true, backgroundOpacity: 0.85, backgroundCornerRadius: 6, outlineThickness: 0.2),
new ClockTheme("Midnight", "Segoe UI", "SemiBold",
Color.FromRgb(0x4C, 0xC2, 0xFF), Color.FromRgb(0x1B, 0x1B, 0x1B),
backgroundEnabled: true, backgroundOpacity: 0.95, backgroundCornerRadius: 8, outlineThickness: 0.2),
new ClockTheme("Paper", "Georgia", "Normal",
Color.FromRgb(0x1A, 0x1A, 0x1A), Color.FromRgb(0xFA, 0xF9, 0xF6),
backgroundEnabled: true, backgroundOpacity: 0.97, backgroundCornerRadius: 8, outlineThickness: 0.2),
new ClockTheme("Minimal", "Segoe UI", "Light",
Color.FromRgb(0xFF, 0xFF, 0xFF), Color.FromRgb(0x00, 0x00, 0x00),
backgroundEnabled: false, backgroundOpacity: 0, backgroundCornerRadius: 1, outlineThickness: 0),
new ClockTheme("Chalk", "Segoe UI", "SemiBold",
Color.FromRgb(0xFF, 0xFF, 0xFF), Color.FromRgb(0x00, 0x00, 0x00),
backgroundEnabled: false, backgroundOpacity: 1, backgroundCornerRadius: 1, outlineThickness: 1.5),
};
}

/// <summary>
/// Applies this look to the given settings; the clock updates immediately.
/// </summary>
public void Apply(Settings settings)
{
settings.FontFamily = FontFamily;
settings.FontWeight = FontWeight;
settings.FontStyle = FontStyle;
settings.TextColor = TextColor;
settings.TextOpacity = 1;
settings.OuterColor = OuterColor;
settings.BackgroundEnabled = BackgroundEnabled;
settings.BackgroundOpacity = BackgroundOpacity;
settings.BackgroundCornerRadius = BackgroundCornerRadius;
settings.OutlineThickness = OutlineThickness;
}

/// <summary>
/// The factory look: accent-colored text seeded from the Windows theme, like a fresh install.
/// </summary>
private static ClockTheme CreateSystemTheme()
{
if (!SystemThemeService.TryGetThemeDefaults(out var textColor, out var outerColor))
{
// Match the hardcoded defaults in Settings when the system theme can't be read.
textColor = Color.FromRgb(33, 33, 33);
outerColor = Color.FromRgb(247, 247, 247);
}

return new ClockTheme("System", "Consolas", "Normal", textColor, outerColor,
backgroundEnabled: true, backgroundOpacity: 0.9, backgroundCornerRadius: 1, outlineThickness: 0.2);
}
}
14 changes: 14 additions & 0 deletions DesktopClock/SettingsWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,10 @@
Style="{StaticResource SidebarButton}"
Tag="{Binding ElementName=WindowSection}"
Click="NavigateToSection" />
<Button Content="T_heme Presets"
Style="{StaticResource SidebarButton}"
Tag="{Binding ElementName=ThemeSection}"
Click="NavigateToSection" />
<Button Content="_Typography"
Style="{StaticResource SidebarButton}"
Tag="{Binding ElementName=TypographySection}"
Expand Down Expand Up @@ -244,6 +248,16 @@
</StackPanel>
</GroupBox>

<GroupBox x:Name="ThemeSection"
Header="Theme Presets"
Style="{StaticResource CardGroupBox}">
<StackPanel Style="{StaticResource CardBodyPanel}">
<local:ThemePresetPicker />
<TextBlock Text="Pick a starting look for the clock. Every detail can still be adjusted in the sections below."
Style="{StaticResource DescriptionTextBlock}" />
</StackPanel>
</GroupBox>

<GroupBox x:Name="TypographySection"
Header="Typography"
Style="{StaticResource CardGroupBox}">
Expand Down
5 changes: 5 additions & 0 deletions DesktopClock/ThemePresetPicker.xaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<UserControl x:Class="DesktopClock.ThemePresetPicker"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<WrapPanel x:Name="PresetsPanel" />
</UserControl>
109 changes: 109 additions & 0 deletions DesktopClock/ThemePresetPicker.xaml.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Media;
using DesktopClock.Properties;

namespace DesktopClock;

/// <summary>
/// A row of one-click clock looks, each chip rendering a true miniature of the
/// clock — same fonts, colors, and shape the real window would use.
/// </summary>
public partial class ThemePresetPicker : UserControl
{
private static readonly FontWeightConverter _fontWeightConverter = new();
private static readonly FontStyleConverter _fontStyleConverter = new();

public ThemePresetPicker()
{
InitializeComponent();

foreach (var theme in ClockTheme.GetBuiltInThemes())
{
PresetsPanel.Children.Add(CreatePresetButton(theme));
}
}

private Button CreatePresetButton(ClockTheme theme)
{
var name = new TextBlock
{
Text = theme.Name,
FontSize = 11.5,
HorizontalAlignment = HorizontalAlignment.Center,
Margin = new Thickness(0, 4, 0, 0),
};
name.SetResourceReference(TextBlock.ForegroundProperty, "TextSecondaryBrush");

var content = new StackPanel();
content.Children.Add(CreateMiniClock(theme));
content.Children.Add(name);

var button = new Button
{
Content = content,
Padding = new Thickness(5),
Margin = new Thickness(0, 0, 8, 8),
ToolTip = "Sets the font, colors, opacity, and corners. Fine-tune in Typography and Appearance.",
};
button.Click += (_, _) => theme.Apply(Settings.Default);

return button;
}

/// <summary>
/// Renders the theme the same way the clock window would, at chip scale.
/// </summary>
private static FrameworkElement CreateMiniClock(ClockTheme theme)
{
// Rendered as geometry, so keep the sample large enough that thin weights
// don't dissolve into anti-aliasing at small stroke widths. The control
// defaults to a 1px black stroke, so clear it unless the theme wants one.
var text = new OutlinedTextBlock
{
Text = "10:08",
FontSize = 21,
FontFamily = new FontFamily(theme.FontFamily),
FontWeight = (FontWeight)_fontWeightConverter.ConvertFromString(theme.FontWeight),
FontStyle = (FontStyle)_fontStyleConverter.ConvertFromString(theme.FontStyle),
Fill = new SolidColorBrush(theme.TextColor),
Stroke = System.Windows.Media.Brushes.Transparent,
StrokeThickness = 0,
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
};

var clock = new Border
{
Child = text,
Padding = new Thickness(10, 4, 10, 4),
HorizontalAlignment = HorizontalAlignment.Center,
VerticalAlignment = VerticalAlignment.Center,
};

if (theme.BackgroundEnabled)
{
// Solid background, scaled down like the clock itself would be in a small window.
clock.Background = new SolidColorBrush(theme.OuterColor) { Opacity = theme.BackgroundOpacity };
clock.CornerRadius = new CornerRadius(theme.BackgroundCornerRadius * 0.75);
}
else if (theme.OutlineThickness > 0)
{
text.Stroke = new SolidColorBrush(theme.OuterColor) { Opacity = theme.BackgroundOpacity };
text.StrokeThickness = theme.OutlineThickness;
}

// Neutral backdrop standing in for the desktop behind the clock.
var backdrop = new Border
{
Child = clock,
Width = 140,
Height = 64,
CornerRadius = new CornerRadius(5),
SnapsToDevicePixels = true,
};
backdrop.SetResourceReference(Border.BackgroundProperty, "SubtleBrush");

return backdrop;
}
}