diff --git a/src/PayBeat.App/App.xaml.cs b/src/PayBeat.App/App.xaml.cs
index 26e6436..c157fcb 100644
--- a/src/PayBeat.App/App.xaml.cs
+++ b/src/PayBeat.App/App.xaml.cs
@@ -1,9 +1,9 @@
+using Microsoft.Win32;
using PayBeat.App.Helpers;
using PayBeat.App.Models;
using PayBeat.App.Services;
using PayBeat.App.ViewModels;
using PayBeat.App.Views;
-using Microsoft.Win32;
using System.Windows.Interop;
namespace PayBeat.App;
@@ -62,6 +62,7 @@ protected override void OnStartup(StartupEventArgs e)
var settings = _settingsService.Load();
_startupSettings = settings;
LocalizationService.Apply(settings.Language);
+ ThemeService.Apply(settings.Theme);
_singleInstanceMutex = new Mutex(initiallyOwned: true, "PayBeat_SingleInstance", out var createdNew);
if (!createdNew)
diff --git a/src/PayBeat.App/GlobalUsings.cs b/src/PayBeat.App/GlobalUsings.cs
index 40085ba..143c271 100644
--- a/src/PayBeat.App/GlobalUsings.cs
+++ b/src/PayBeat.App/GlobalUsings.cs
@@ -1,5 +1,4 @@
global using System.Windows;
-global using System.Windows.Controls;
global using System.Windows.Input;
global using System.Windows.Threading;
global using System.IO;
diff --git a/src/PayBeat.App/Models/SalarySettings.cs b/src/PayBeat.App/Models/SalarySettings.cs
index a57e7b7..1574432 100644
--- a/src/PayBeat.App/Models/SalarySettings.cs
+++ b/src/PayBeat.App/Models/SalarySettings.cs
@@ -8,28 +8,44 @@ public record SalarySettings
{
public const decimal MaxDailySalary = 99_999_999m;
- /// Daily gross salary used to compute the per-second earnings rate.
+ ///
+ /// Daily gross salary used to compute the per-second earnings rate.
+ ///
public decimal DailySalary { get; init; } = 500m;
- /// Work day start time; earnings accrue from this moment.
+ ///
+ /// Work day start time; earnings accrue from this moment.
+ ///
public TimeOnly WorkStart { get; init; } = new(9, 0);
- /// Work day end time; earnings are capped at after this.
+ ///
+ /// Work day end time; earnings are capped at after this.
+ ///
public TimeOnly WorkEnd { get; init; } = new(18, 0);
- /// Currency symbol prepended to the formatted earnings string.
+ ///
+ /// Currency symbol prepended to the formatted earnings string.
+ ///
public string Currency { get; init; } = "¥";
- /// Active widget display mode, determining which view template is shown.
+ ///
+ /// Active widget display mode, determining which view template is shown.
+ ///
public DisplayMode DisplayMode { get; init; } = DisplayMode.Normal;
- /// Whether the widget window is pinned above all other windows.
+ ///
+ /// Whether the widget window is pinned above all other windows.
+ ///
public bool AlwaysOnTop { get; init; } = true;
- /// Timer tick interval in seconds for refreshing the earnings display.
+ ///
+ /// Timer tick interval in seconds for refreshing the earnings display.
+ ///
public int RefreshInterval { get; init; } = 1;
- /// Window opacity when the mouse is not hovering (0.1–1.0).
+ ///
+ /// Window opacity when the mouse is not hovering (0.1–1.0).
+ ///
public double Opacity { get; init; } = 1.0;
///
@@ -44,48 +60,78 @@ public record SalarySettings
///
public int HotkeyModifiers { get; init; } = 0x0003;
- /// Virtual-key code for the global show/hide hotkey. Default 0x58 = X.
+ ///
+ /// Virtual-key code for the global show/hide hotkey. Default 0x58 = X.
+ ///
public int HotkeyVirtualKey { get; init; } = 0x58;
- /// Last saved position for mode.
+ ///
+ /// Last saved position for mode.
+ ///
public WindowPosition? NormalPosition
{
get; init;
}
- /// Last saved position for mode.
+ ///
+ /// Last saved position for mode.
+ ///
public WindowPosition? MiniPosition
{
get; init;
}
- /// Last saved monitor for mode. Only is used.
+ ///
+ /// Last saved monitor for mode. Only is used.
+ ///
public WindowPosition? FlexPosition
{
get; init;
}
- /// Whether earnings should be deducted for a daily lunch break.
+ ///
+ /// Whether earnings should be deducted for a daily lunch break.
+ ///
public bool LunchBreakEnabled { get; init; } = false;
- /// Lunch break start time; only used when is true.
+ ///
+ /// Lunch break start time; only used when is true.
+ ///
public TimeOnly LunchBreakStart { get; init; } = new(12, 0);
- /// Lunch break end time; only used when is true.
+ ///
+ /// Lunch break end time; only used when is true.
+ ///
public TimeOnly LunchBreakEnd { get; init; } = new(13, 0);
- /// Whether earnings accrue on Saturdays and Sundays. When false, weekends earn nothing.
+ ///
+ /// Whether earnings accrue on Saturdays and Sundays. When false, weekends earn nothing.
+ ///
public bool WorkOnWeekends { get; init; } = false;
- /// Whether to show a tray balloon notification shortly before work ends.
+ ///
+ /// Whether to show a tray balloon notification shortly before work ends.
+ ///
public bool EnableEndOfDayReminder { get; init; } = false;
- /// How many minutes before the end-of-day reminder fires.
+ ///
+ /// How many minutes before the end-of-day reminder fires.
+ ///
public int EndOfDayReminderMinutes { get; init; } = 5;
- /// Whether to show a tray balloon notification each time earnings cross a increment.
+ ///
+ /// Whether to show a tray balloon notification each time earnings cross a increment.
+ ///
public bool EnableMilestoneNotifications { get; init; } = false;
- /// Earnings increment that triggers a milestone notification (e.g. every ¥100).
+ ///
+ /// Earnings increment that triggers a milestone notification (e.g. every ¥100).
+ ///
public decimal MilestoneAmount { get; init; } = 100m;
+
+ ///
+ /// UI color theme. "auto" resolves from the Windows apps theme setting;
+ /// supported explicit values are "light" and "dark".
+ ///
+ public string Theme { get; init; } = "auto";
}
\ No newline at end of file
diff --git a/src/PayBeat.App/Resources/Strings.en.xaml b/src/PayBeat.App/Resources/Strings.en.xaml
index 318afba..81106be 100644
--- a/src/PayBeat.App/Resources/Strings.en.xaml
+++ b/src/PayBeat.App/Resources/Strings.en.xaml
@@ -64,6 +64,10 @@
Run at Startup
Language
Auto
+ Theme
+ Auto
+ Light
+ Dark
Toggle Visibility Hotkey
Click, then press a key combination
Press a key combination...
diff --git a/src/PayBeat.App/Resources/Strings.zh-CN.xaml b/src/PayBeat.App/Resources/Strings.zh-CN.xaml
index b3b796a..52f9c9e 100644
--- a/src/PayBeat.App/Resources/Strings.zh-CN.xaml
+++ b/src/PayBeat.App/Resources/Strings.zh-CN.xaml
@@ -64,6 +64,10 @@
开机自动启动
语言
自动
+ 主题
+ 自动
+ 浅色
+ 深色
显示/隐藏快捷键
点击后输入快捷键组合
请输入快捷键组合...
diff --git a/src/PayBeat.App/Resources/Styles.xaml b/src/PayBeat.App/Resources/Styles.xaml
index 95e7e53..ec576f5 100644
--- a/src/PayBeat.App/Resources/Styles.xaml
+++ b/src/PayBeat.App/Resources/Styles.xaml
@@ -1,27 +1,6 @@
-
- #1E1E2E
- #313244
- #45475A
- #CDD6F4
- #9399B2
- #6C7086
- #A6E3A1
- #89B4FA
- #F38BA8
-
-
-
-
-
-
-
-
-
-
-
@@ -65,12 +65,12 @@
Margin="0,0,0,12"
RenderOptions.BitmapScalingMode="HighQuality"/>
@@ -79,7 +79,7 @@
@@ -100,11 +100,11 @@
@@ -116,11 +116,11 @@
@@ -132,11 +132,11 @@
@@ -150,15 +150,15 @@
Content="{DynamicResource About.Close}"
HorizontalAlignment="Center"
Padding="32,10"
- Background="#313244"
- Foreground="#CDD6F4"
+ Background="{DynamicResource SurfaceBrush}"
+ Foreground="{DynamicResource TextBrush}"
BorderThickness="0"
FontSize="13"
Cursor="Hand"
Click="CloseButton_Click">
diff --git a/src/PayBeat.App/Views/Controls/TimePickerControl.xaml b/src/PayBeat.App/Views/Controls/TimePickerControl.xaml
index 8118ce5..2627cc1 100644
--- a/src/PayBeat.App/Views/Controls/TimePickerControl.xaml
+++ b/src/PayBeat.App/Views/Controls/TimePickerControl.xaml
@@ -4,31 +4,31 @@
-
+
+ Background="Transparent" Foreground="{DynamicResource SubtextBrush}" FontSize="7"/>
+ Background="Transparent" Foreground="{DynamicResource SubtextBrush}" FontSize="7"/>
+ Background="Transparent" Foreground="{DynamicResource SubtextBrush}" FontSize="7"/>
+ Background="Transparent" Foreground="{DynamicResource SubtextBrush}" FontSize="7"/>
diff --git a/src/PayBeat.App/Views/FlexView.xaml b/src/PayBeat.App/Views/FlexView.xaml
index d04ff2e..efb15e1 100644
--- a/src/PayBeat.App/Views/FlexView.xaml
+++ b/src/PayBeat.App/Views/FlexView.xaml
@@ -5,16 +5,16 @@
-
-
-
+
+
+
@@ -25,7 +25,7 @@
-
+
@@ -55,11 +55,11 @@
Width="700"
Height="14"
Margin="0,24,0,8"
- Background="#313244"
- Foreground="#89B4FA"
+ Background="{DynamicResource SurfaceBrush}"
+ Foreground="{DynamicResource BlueBrush}"
BorderThickness="0"/>
+ Foreground="{DynamicResource MutedBrush}" FontFamily="Segoe UI" FontSize="13" HorizontalAlignment="Center"/>
+ Foreground="{DynamicResource MutedBrush}" FontFamily="Segoe UI" FontSize="13" HorizontalAlignment="Center"/>
+ Foreground="{DynamicResource MutedBrush}" FontFamily="Segoe UI" FontSize="13" HorizontalAlignment="Center"/>
+ Foreground="{DynamicResource TextBrush}" FontFamily="Segoe UI" FontSize="26" FontWeight="SemiBold"/>
+ Foreground="{DynamicResource TextBrush}" FontFamily="Segoe UI" FontSize="26" FontWeight="SemiBold"/>
+ Foreground="{DynamicResource MutedBrush}" FontFamily="Segoe UI" FontSize="13" HorizontalAlignment="Center"/>
+ Foreground="{DynamicResource TextBrush}" FontFamily="Segoe UI" FontSize="26" FontWeight="SemiBold"/>
+ Foreground="{DynamicResource TextBrush}" FontFamily="Segoe UI" FontSize="26" FontWeight="SemiBold"/>
+ Foreground="{DynamicResource TextBrush}" FontFamily="Segoe UI" FontSize="26" FontWeight="SemiBold"/>
diff --git a/src/PayBeat.App/Views/FlexView.xaml.cs b/src/PayBeat.App/Views/FlexView.xaml.cs
index e3ee48a..7eb3624 100644
--- a/src/PayBeat.App/Views/FlexView.xaml.cs
+++ b/src/PayBeat.App/Views/FlexView.xaml.cs
@@ -23,8 +23,8 @@ private void StartAnimations()
{
var backgroundAnimation = new ColorAnimation
{
- From = System.Windows.Media.Color.FromRgb(0x18, 0x18, 0x25),
- To = System.Windows.Media.Color.FromRgb(0x31, 0x32, 0x44),
+ From = (System.Windows.Media.Color)FindResource("FlexGradientMidColor"),
+ To = (System.Windows.Media.Color)FindResource("SurfaceColor"),
Duration = TimeSpan.FromSeconds(6),
AutoReverse = true,
RepeatBehavior = RepeatBehavior.Forever,
diff --git a/src/PayBeat.App/Views/MiniView.xaml b/src/PayBeat.App/Views/MiniView.xaml
index c9c0737..83f48d0 100644
--- a/src/PayBeat.App/Views/MiniView.xaml
+++ b/src/PayBeat.App/Views/MiniView.xaml
@@ -2,9 +2,9 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
-
@@ -12,7 +12,7 @@
diff --git a/src/PayBeat.App/Views/NormalView.xaml b/src/PayBeat.App/Views/NormalView.xaml
index d0534be..e0bf318 100644
--- a/src/PayBeat.App/Views/NormalView.xaml
+++ b/src/PayBeat.App/Views/NormalView.xaml
@@ -2,11 +2,11 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
-
@@ -16,14 +16,14 @@
@@ -46,28 +46,28 @@
-
+
@@ -77,16 +77,16 @@
diff --git a/src/PayBeat.App/Views/SettingsWindow.xaml b/src/PayBeat.App/Views/SettingsWindow.xaml
index 78adbad..8dc2820 100644
--- a/src/PayBeat.App/Views/SettingsWindow.xaml
+++ b/src/PayBeat.App/Views/SettingsWindow.xaml
@@ -18,7 +18,7 @@
-
@@ -154,7 +154,7 @@
@@ -162,7 +162,7 @@
@@ -176,13 +176,13 @@
Command="{Binding CancelCommand}"
Margin="0,0,10,0"
Padding="20,10"
- Background="#313244"
- Foreground="#CDD6F4" />
+ Background="{DynamicResource SurfaceBrush}"
+ Foreground="{DynamicResource TextBrush}" />
@@ -199,7 +199,7 @@
@@ -210,9 +210,9 @@
-
+
@@ -260,7 +260,7 @@
@@ -269,7 +269,7 @@
VerticalAlignment="Center"
IsEnabled="{Binding LunchBreakEnabled}">
-
+
@@ -277,7 +277,7 @@
@@ -293,16 +293,16 @@
@@ -312,7 +312,7 @@
Value="{Binding RefreshInterval}"
TickFrequency="1"
IsSnapToTickEnabled="True"
- Foreground="#89B4FA" />
+ Foreground="{DynamicResource BlueBrush}" />
@@ -323,7 +323,7 @@
@@ -335,16 +335,16 @@
@@ -367,16 +367,16 @@
@@ -453,12 +453,12 @@
@@ -467,15 +467,54 @@
Value="{Binding Opacity}"
TickFrequency="0.05"
IsSnapToTickEnabled="True"
- Foreground="#89B4FA"
+ Foreground="{DynamicResource BlueBrush}"
Margin="0,0,0,16" />
+ Cursor="Hand"
+ Margin="0,0,0,16" />
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -486,22 +525,22 @@
+ HorizontalContentAlignment="Left"
+ Margin="0,0,0,16">
diff --git a/src/PayBeat.App/Views/SettingsWindow.xaml.cs b/src/PayBeat.App/Views/SettingsWindow.xaml.cs
index af65407..b5eb944 100644
--- a/src/PayBeat.App/Views/SettingsWindow.xaml.cs
+++ b/src/PayBeat.App/Views/SettingsWindow.xaml.cs
@@ -49,28 +49,6 @@ private static string BuildModifierPreview(int modifiers)
return string.Join("+", parts);
}
- private static void SetHotkeyBoxCapturingStyle(TextBox box, bool capturing)
- {
- if (capturing)
- {
- box.Background = new System.Windows.Media.SolidColorBrush(
- (System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString("#1C3A5E"));
- box.BorderBrush = new System.Windows.Media.SolidColorBrush(
- (System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString("#89B4FA"));
- box.Foreground = new System.Windows.Media.SolidColorBrush(
- (System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString("#89B4FA"));
- }
- else
- {
- box.Background = new System.Windows.Media.SolidColorBrush(
- (System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString("#313244"));
- box.BorderBrush = new System.Windows.Media.SolidColorBrush(
- (System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString("#45475A"));
- box.Foreground = new System.Windows.Media.SolidColorBrush(
- (System.Windows.Media.Color)System.Windows.Media.ColorConverter.ConvertFromString("#CDD6F4"));
- }
- }
-
private void HotkeyBox_GotFocus(object sender, RoutedEventArgs e)
{
((App)Application.Current).SuspendHotkey();
@@ -172,12 +150,12 @@ or Key.LeftShift or Key.RightShift or Key.LWin or Key.RWin
box.MoveFocus(new TraversalRequest(FocusNavigationDirection.Next));
}
- private void Salary_Pasting(object sender, DataObjectPastingEventArgs e)
+ private void Integer_Pasting(object sender, DataObjectPastingEventArgs e)
{
if (e.DataObject.GetDataPresent(typeof(string)))
{
var text = (string)e.DataObject.GetData(typeof(string))!;
- if (!DecimalPattern.IsMatch(text))
+ if (!IntegerPattern.IsMatch(text))
{
e.CancelCommand();
}
@@ -188,21 +166,21 @@ private void Salary_Pasting(object sender, DataObjectPastingEventArgs e)
}
}
- private void Salary_PreviewTextInput(object sender, TextCompositionEventArgs e)
+ private void Integer_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
var box = (TextBox)sender;
var prospective =
box.Text.Remove(box.SelectionStart, box.SelectionLength).Insert(box.SelectionStart, e.Text);
- e.Handled = !DecimalPattern.IsMatch(prospective);
+ e.Handled = !IntegerPattern.IsMatch(prospective);
}
- private void Integer_Pasting(object sender, DataObjectPastingEventArgs e)
+ private void Salary_Pasting(object sender, DataObjectPastingEventArgs e)
{
if (e.DataObject.GetDataPresent(typeof(string)))
{
var text = (string)e.DataObject.GetData(typeof(string))!;
- if (!IntegerPattern.IsMatch(text))
+ if (!DecimalPattern.IsMatch(text))
{
e.CancelCommand();
}
@@ -213,13 +191,29 @@ private void Integer_Pasting(object sender, DataObjectPastingEventArgs e)
}
}
- private void Integer_PreviewTextInput(object sender, TextCompositionEventArgs e)
+ private void Salary_PreviewTextInput(object sender, TextCompositionEventArgs e)
{
var box = (TextBox)sender;
var prospective =
box.Text.Remove(box.SelectionStart, box.SelectionLength).Insert(box.SelectionStart, e.Text);
- e.Handled = !IntegerPattern.IsMatch(prospective);
+ e.Handled = !DecimalPattern.IsMatch(prospective);
+ }
+
+ private void SetHotkeyBoxCapturingStyle(TextBox box, bool capturing)
+ {
+ if (capturing)
+ {
+ box.Background = (System.Windows.Media.Brush)FindResource("CaptureBackgroundBrush");
+ box.BorderBrush = (System.Windows.Media.Brush)FindResource("BlueBrush");
+ box.Foreground = (System.Windows.Media.Brush)FindResource("BlueBrush");
+ }
+ else
+ {
+ box.Background = (System.Windows.Media.Brush)FindResource("SurfaceBrush");
+ box.BorderBrush = (System.Windows.Media.Brush)FindResource("OverlayBrush");
+ box.Foreground = (System.Windows.Media.Brush)FindResource("TextBrush");
+ }
}
private void Window_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)