added interface themes
This commit is contained in:
+5
-1
@@ -3,6 +3,10 @@
|
|||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
ShutdownMode="OnMainWindowClose">
|
ShutdownMode="OnMainWindowClose">
|
||||||
<Application.Resources>
|
<Application.Resources>
|
||||||
|
<ResourceDictionary>
|
||||||
|
<ResourceDictionary.MergedDictionaries>
|
||||||
|
<ResourceDictionary Source="Themes/Controls.xaml" />
|
||||||
|
</ResourceDictionary.MergedDictionaries>
|
||||||
|
</ResourceDictionary>
|
||||||
</Application.Resources>
|
</Application.Resources>
|
||||||
</Application>
|
</Application>
|
||||||
|
|||||||
@@ -22,6 +22,8 @@ public partial class App : Application
|
|||||||
ConfigureServices(services);
|
ConfigureServices(services);
|
||||||
_services = services.BuildServiceProvider();
|
_services = services.BuildServiceProvider();
|
||||||
|
|
||||||
|
_services.GetRequiredService<ThemeService>();
|
||||||
|
|
||||||
MainWindow = _services.GetRequiredService<MainWindow>();
|
MainWindow = _services.GetRequiredService<MainWindow>();
|
||||||
MainWindow.Show();
|
MainWindow.Show();
|
||||||
|
|
||||||
@@ -43,6 +45,9 @@ public partial class App : Application
|
|||||||
services.AddSingleton<SettingsService>();
|
services.AddSingleton<SettingsService>();
|
||||||
services.AddSingleton(provider => provider.GetRequiredService<SettingsService>().Load());
|
services.AddSingleton(provider => provider.GetRequiredService<SettingsService>().Load());
|
||||||
|
|
||||||
|
services.AddSingleton<ThemeService>();
|
||||||
|
services.AddSingleton<IThemeService>(provider => provider.GetRequiredService<ThemeService>());
|
||||||
|
|
||||||
services.AddSingleton<ILocalizationService, LocalizationService>();
|
services.AddSingleton<ILocalizationService, LocalizationService>();
|
||||||
services.AddSingleton<IKeyboardLayoutService, KeyboardLayoutService>();
|
services.AddSingleton<IKeyboardLayoutService, KeyboardLayoutService>();
|
||||||
services.AddSingleton<ILayoutPopupService, LayoutPopupService>();
|
services.AddSingleton<ILayoutPopupService, LayoutPopupService>();
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace CursorLang.Interop;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Оформление рамки окна средствами системы: заголовок рисует Windows,
|
||||||
|
/// и в тёмной теме его нужно переключать отдельно от содержимого окна.
|
||||||
|
/// </summary>
|
||||||
|
internal static class WindowThemeNative
|
||||||
|
{
|
||||||
|
[DllImport("dwmapi.dll")]
|
||||||
|
private static extern int DwmSetWindowAttribute(IntPtr hWnd, int attribute, ref int value, int size);
|
||||||
|
|
||||||
|
private const int DWMWA_USE_IMMERSIVE_DARK_MODE = 20;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Перекрашивает заголовок окна. На сборках Windows 10 до 2004 атрибут
|
||||||
|
/// не поддерживается — заголовок просто останется светлым.
|
||||||
|
/// </summary>
|
||||||
|
internal static void SetDarkTitleBar(IntPtr hWnd, bool isDark)
|
||||||
|
{
|
||||||
|
if (hWnd == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int value = isDark ? 1 : 0;
|
||||||
|
DwmSetWindowAttribute(hWnd, DWMWA_USE_IMMERSIVE_DARK_MODE, ref value, sizeof(int));
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -14,6 +14,10 @@ public sealed partial class AppSettings : ObservableObject
|
|||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private string _language = "en";
|
private string _language = "en";
|
||||||
|
|
||||||
|
/// <summary>Оформление окна настроек.</summary>
|
||||||
|
[ObservableProperty]
|
||||||
|
private AppTheme _theme = AppTheme.System;
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private PopupPlacementMode _placementMode = PopupPlacementMode.AtCursor;
|
private PopupPlacementMode _placementMode = PopupPlacementMode.AtCursor;
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,12 @@
|
|||||||
|
namespace CursorLang.Models;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Оформление окна настроек. По умолчанию приложение следует теме Windows,
|
||||||
|
/// но пользователь может закрепить светлую или тёмную.
|
||||||
|
/// </summary>
|
||||||
|
public enum AppTheme
|
||||||
|
{
|
||||||
|
System,
|
||||||
|
Light,
|
||||||
|
Dark
|
||||||
|
}
|
||||||
@@ -67,6 +67,18 @@
|
|||||||
<data name="LanguageLabel" xml:space="preserve">
|
<data name="LanguageLabel" xml:space="preserve">
|
||||||
<value>Interface language</value>
|
<value>Interface language</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ThemeLabel" xml:space="preserve">
|
||||||
|
<value>Theme</value>
|
||||||
|
</data>
|
||||||
|
<data name="AppTheme_System" xml:space="preserve">
|
||||||
|
<value>Same as Windows</value>
|
||||||
|
</data>
|
||||||
|
<data name="AppTheme_Light" xml:space="preserve">
|
||||||
|
<value>Light</value>
|
||||||
|
</data>
|
||||||
|
<data name="AppTheme_Dark" xml:space="preserve">
|
||||||
|
<value>Dark</value>
|
||||||
|
</data>
|
||||||
<data name="SectionPlacement" xml:space="preserve">
|
<data name="SectionPlacement" xml:space="preserve">
|
||||||
<value>Placement</value>
|
<value>Placement</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -67,6 +67,18 @@
|
|||||||
<data name="LanguageLabel" xml:space="preserve">
|
<data name="LanguageLabel" xml:space="preserve">
|
||||||
<value>Язык интерфейса</value>
|
<value>Язык интерфейса</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="ThemeLabel" xml:space="preserve">
|
||||||
|
<value>Тема</value>
|
||||||
|
</data>
|
||||||
|
<data name="AppTheme_System" xml:space="preserve">
|
||||||
|
<value>Как в Windows</value>
|
||||||
|
</data>
|
||||||
|
<data name="AppTheme_Light" xml:space="preserve">
|
||||||
|
<value>Светлая</value>
|
||||||
|
</data>
|
||||||
|
<data name="AppTheme_Dark" xml:space="preserve">
|
||||||
|
<value>Тёмная</value>
|
||||||
|
</data>
|
||||||
<data name="SectionPlacement" xml:space="preserve">
|
<data name="SectionPlacement" xml:space="preserve">
|
||||||
<value>Расположение</value>
|
<value>Расположение</value>
|
||||||
</data>
|
</data>
|
||||||
|
|||||||
@@ -0,0 +1,19 @@
|
|||||||
|
using System.Windows;
|
||||||
|
using CursorLang.Models;
|
||||||
|
|
||||||
|
namespace CursorLang.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Применяет светлое или тёмное оформление к окнам приложения.
|
||||||
|
/// </summary>
|
||||||
|
public interface IThemeService
|
||||||
|
{
|
||||||
|
/// <summary>Тема, действующая сейчас.</summary>
|
||||||
|
AppTheme CurrentTheme { get; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Подключает окно к смене темы: заголовок окна рисует Windows,
|
||||||
|
/// и его цвет приходится переключать для каждого окна отдельно.
|
||||||
|
/// </summary>
|
||||||
|
void Register(Window window);
|
||||||
|
}
|
||||||
@@ -90,12 +90,10 @@ public sealed class SettingsService : IDisposable
|
|||||||
}
|
}
|
||||||
catch (Exception e) when (e is IOException or UnauthorizedAccessException or JsonException)
|
catch (Exception e) when (e is IOException or UnauthorizedAccessException or JsonException)
|
||||||
{
|
{
|
||||||
// Испорченный файл не должен мешать запуску: начнём с умолчаний
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Язык по умолчанию берём системный, если он поддерживается
|
|
||||||
private static AppSettings CreateDefault()
|
private static AppSettings CreateDefault()
|
||||||
{
|
{
|
||||||
string uiLanguage = System.Globalization.CultureInfo.CurrentUICulture.TwoLetterISOLanguageName;
|
string uiLanguage = System.Globalization.CultureInfo.CurrentUICulture.TwoLetterISOLanguageName;
|
||||||
|
|||||||
@@ -0,0 +1,144 @@
|
|||||||
|
using System.ComponentModel;
|
||||||
|
using System.Windows;
|
||||||
|
using System.Windows.Interop;
|
||||||
|
using CursorLang.Interop;
|
||||||
|
using CursorLang.Models;
|
||||||
|
using Microsoft.Win32;
|
||||||
|
|
||||||
|
namespace CursorLang.Services;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Держит в ресурсах приложения палитру выбранной темы и подменяет её
|
||||||
|
/// при смене настройки — окна перекрашиваются без перезапуска.
|
||||||
|
/// </summary>
|
||||||
|
public sealed class ThemeService : IThemeService, IDisposable
|
||||||
|
{
|
||||||
|
private const string PersonalizeKey =
|
||||||
|
@"Software\Microsoft\Windows\CurrentVersion\Themes\Personalize";
|
||||||
|
|
||||||
|
private readonly AppSettings _settings;
|
||||||
|
private readonly List<Window> _windows = [];
|
||||||
|
private ResourceDictionary? _palette;
|
||||||
|
private AppTheme _current;
|
||||||
|
|
||||||
|
public ThemeService(AppSettings settings)
|
||||||
|
{
|
||||||
|
_settings = settings;
|
||||||
|
_settings.PropertyChanged += OnSettingsChanged;
|
||||||
|
SystemEvents.UserPreferenceChanged += OnUserPreferenceChanged;
|
||||||
|
Apply();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Тема, которую видит пользователь: <c>System</c> здесь уже разрешён.</summary>
|
||||||
|
public AppTheme CurrentTheme => _current;
|
||||||
|
|
||||||
|
public void Register(Window window)
|
||||||
|
{
|
||||||
|
_windows.Add(window);
|
||||||
|
window.Closed += OnWindowClosed;
|
||||||
|
|
||||||
|
if (new WindowInteropHelper(window).Handle == IntPtr.Zero)
|
||||||
|
{
|
||||||
|
window.SourceInitialized += OnWindowSourceInitialized;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ApplyTitleBar(window);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
_settings.PropertyChanged -= OnSettingsChanged;
|
||||||
|
SystemEvents.UserPreferenceChanged -= OnUserPreferenceChanged;
|
||||||
|
|
||||||
|
foreach (Window window in _windows)
|
||||||
|
{
|
||||||
|
window.Closed -= OnWindowClosed;
|
||||||
|
window.SourceInitialized -= OnWindowSourceInitialized;
|
||||||
|
}
|
||||||
|
|
||||||
|
_windows.Clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>Тема приложений в настройках Windows.</summary>
|
||||||
|
private static AppTheme DetectSystemTheme()
|
||||||
|
{
|
||||||
|
try
|
||||||
|
{
|
||||||
|
using RegistryKey? key = Registry.CurrentUser.OpenSubKey(PersonalizeKey);
|
||||||
|
return key?.GetValue("AppsUseLightTheme") is int light && light == 0
|
||||||
|
? AppTheme.Dark
|
||||||
|
: AppTheme.Light;
|
||||||
|
}
|
||||||
|
catch (Exception e) when (e is System.Security.SecurityException or UnauthorizedAccessException)
|
||||||
|
{
|
||||||
|
return AppTheme.Light;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnSettingsChanged(object? sender, PropertyChangedEventArgs e)
|
||||||
|
{
|
||||||
|
if (e.PropertyName == nameof(AppSettings.Theme))
|
||||||
|
{
|
||||||
|
Apply();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Windows сообщает о смене оформления не из потока интерфейса
|
||||||
|
private void OnUserPreferenceChanged(object? sender, UserPreferenceChangedEventArgs e) =>
|
||||||
|
Application.Current?.Dispatcher.InvokeAsync(Apply);
|
||||||
|
|
||||||
|
private void Apply()
|
||||||
|
{
|
||||||
|
AppTheme theme = _settings.Theme == AppTheme.System ? DetectSystemTheme() : _settings.Theme;
|
||||||
|
if (_palette is not null && theme == _current)
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
_current = theme;
|
||||||
|
|
||||||
|
var next = new ResourceDictionary
|
||||||
|
{
|
||||||
|
Source = new Uri($"pack://application:,,,/Themes/{theme}.xaml", UriKind.Absolute),
|
||||||
|
};
|
||||||
|
|
||||||
|
ICollection<ResourceDictionary> dictionaries = Application.Current.Resources.MergedDictionaries;
|
||||||
|
dictionaries.Add(next);
|
||||||
|
|
||||||
|
if (_palette is not null)
|
||||||
|
{
|
||||||
|
dictionaries.Remove(_palette);
|
||||||
|
}
|
||||||
|
|
||||||
|
_palette = next;
|
||||||
|
|
||||||
|
foreach (Window window in _windows)
|
||||||
|
{
|
||||||
|
ApplyTitleBar(window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ApplyTitleBar(Window window) =>
|
||||||
|
WindowThemeNative.SetDarkTitleBar(
|
||||||
|
new WindowInteropHelper(window).Handle,
|
||||||
|
_current == AppTheme.Dark);
|
||||||
|
|
||||||
|
private void OnWindowSourceInitialized(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Window window)
|
||||||
|
{
|
||||||
|
window.SourceInitialized -= OnWindowSourceInitialized;
|
||||||
|
ApplyTitleBar(window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnWindowClosed(object? sender, EventArgs e)
|
||||||
|
{
|
||||||
|
if (sender is Window window)
|
||||||
|
{
|
||||||
|
window.Closed -= OnWindowClosed;
|
||||||
|
_windows.Remove(window);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,294 @@
|
|||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
|
||||||
|
<!-- ======================= GroupBox ======================= -->
|
||||||
|
|
||||||
|
<Style TargetType="GroupBox">
|
||||||
|
<Setter Property="Padding" Value="12" />
|
||||||
|
<Setter Property="Margin" Value="0,0,0,12" />
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource Theme.Foreground}" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="GroupBox">
|
||||||
|
<Grid>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="*" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
<ContentPresenter ContentSource="Header"
|
||||||
|
Margin="2,0,0,6"
|
||||||
|
TextBlock.FontWeight="SemiBold" />
|
||||||
|
<Border Grid.Row="1"
|
||||||
|
Background="{DynamicResource Theme.Surface}"
|
||||||
|
BorderBrush="{DynamicResource Theme.ControlBorder}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="6"
|
||||||
|
Padding="{TemplateBinding Padding}"
|
||||||
|
SnapsToDevicePixels="True">
|
||||||
|
<ContentPresenter />
|
||||||
|
</Border>
|
||||||
|
</Grid>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- ======================= ComboBox ======================= -->
|
||||||
|
|
||||||
|
<Style x:Key="ComboBoxToggleStyle" TargetType="ToggleButton">
|
||||||
|
<Setter Property="OverridesDefaultStyle" Value="True" />
|
||||||
|
<Setter Property="Focusable" Value="False" />
|
||||||
|
<Setter Property="IsTabStop" Value="False" />
|
||||||
|
<Setter Property="ClickMode" Value="Press" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="ToggleButton">
|
||||||
|
<Border x:Name="Border"
|
||||||
|
Background="{DynamicResource Theme.ControlBackground}"
|
||||||
|
BorderBrush="{DynamicResource Theme.ControlBorder}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="4"
|
||||||
|
SnapsToDevicePixels="True">
|
||||||
|
<Path x:Name="Arrow"
|
||||||
|
HorizontalAlignment="Right" VerticalAlignment="Center"
|
||||||
|
Margin="0,0,10,0"
|
||||||
|
Data="M0,0 L4,4 L8,0"
|
||||||
|
Stroke="{DynamicResource Theme.SecondaryForeground}"
|
||||||
|
StrokeThickness="1.4" />
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter TargetName="Border" Property="Background"
|
||||||
|
Value="{DynamicResource Theme.ControlHoverBackground}" />
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsChecked" Value="True">
|
||||||
|
<Setter TargetName="Border" Property="BorderBrush"
|
||||||
|
Value="{DynamicResource Theme.Accent}" />
|
||||||
|
<Setter TargetName="Arrow" Property="Stroke"
|
||||||
|
Value="{DynamicResource Theme.Accent}" />
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsEnabled" Value="False">
|
||||||
|
<Setter TargetName="Border" Property="Opacity" Value="0.5" />
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="ComboBoxItem">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource Theme.Foreground}" />
|
||||||
|
<Setter Property="Padding" Value="8,5" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="ComboBoxItem">
|
||||||
|
<Border x:Name="Border"
|
||||||
|
Background="Transparent"
|
||||||
|
CornerRadius="3"
|
||||||
|
Margin="3,1"
|
||||||
|
Padding="{TemplateBinding Padding}">
|
||||||
|
<ContentPresenter />
|
||||||
|
</Border>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsHighlighted" Value="True">
|
||||||
|
<Setter TargetName="Border" Property="Background"
|
||||||
|
Value="{DynamicResource Theme.SelectionBackground}" />
|
||||||
|
</Trigger>
|
||||||
|
<Trigger Property="IsSelected" Value="True">
|
||||||
|
<Setter TargetName="Border" Property="Background"
|
||||||
|
Value="{DynamicResource Theme.SelectionBackground}" />
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="ComboBox">
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource Theme.Foreground}" />
|
||||||
|
<!-- Правый отступ оставляет место под стрелку -->
|
||||||
|
<Setter Property="Padding" Value="9,5,28,5" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="ComboBox">
|
||||||
|
<Grid>
|
||||||
|
<ToggleButton Style="{StaticResource ComboBoxToggleStyle}"
|
||||||
|
IsChecked="{Binding IsDropDownOpen, Mode=TwoWay,
|
||||||
|
RelativeSource={RelativeSource TemplatedParent}}" />
|
||||||
|
<ContentPresenter Margin="{TemplateBinding Padding}"
|
||||||
|
Content="{TemplateBinding SelectionBoxItem}"
|
||||||
|
ContentTemplate="{TemplateBinding SelectionBoxItemTemplate}"
|
||||||
|
ContentStringFormat="{TemplateBinding SelectionBoxItemStringFormat}"
|
||||||
|
HorizontalAlignment="Left" VerticalAlignment="Center"
|
||||||
|
IsHitTestVisible="False" />
|
||||||
|
<Popup x:Name="PART_Popup"
|
||||||
|
Placement="Bottom"
|
||||||
|
IsOpen="{TemplateBinding IsDropDownOpen}"
|
||||||
|
AllowsTransparency="True"
|
||||||
|
Focusable="False"
|
||||||
|
PopupAnimation="Fade">
|
||||||
|
<Border Background="{DynamicResource Theme.PopupBackground}"
|
||||||
|
BorderBrush="{DynamicResource Theme.ControlBorder}"
|
||||||
|
BorderThickness="1"
|
||||||
|
CornerRadius="4"
|
||||||
|
Margin="0,2,0,0"
|
||||||
|
MinWidth="{Binding ActualWidth,
|
||||||
|
RelativeSource={RelativeSource TemplatedParent}}"
|
||||||
|
MaxHeight="{TemplateBinding MaxDropDownHeight}">
|
||||||
|
<ScrollViewer>
|
||||||
|
<ItemsPresenter KeyboardNavigation.DirectionalNavigation="Contained" />
|
||||||
|
</ScrollViewer>
|
||||||
|
</Border>
|
||||||
|
</Popup>
|
||||||
|
</Grid>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsEnabled" Value="False">
|
||||||
|
<Setter Property="Opacity" Value="0.5" />
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- ======================= Slider ======================= -->
|
||||||
|
|
||||||
|
<Style x:Key="SliderThumbStyle" TargetType="Thumb">
|
||||||
|
<Setter Property="OverridesDefaultStyle" Value="True" />
|
||||||
|
<Setter Property="Width" Value="14" />
|
||||||
|
<Setter Property="Height" Value="14" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="Thumb">
|
||||||
|
<Ellipse x:Name="Circle"
|
||||||
|
Fill="{DynamicResource Theme.Accent}"
|
||||||
|
Stroke="{DynamicResource Theme.ControlBackground}"
|
||||||
|
StrokeThickness="2" />
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<Trigger Property="IsMouseOver" Value="True">
|
||||||
|
<Setter TargetName="Circle" Property="StrokeThickness" Value="1" />
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- Пройденная часть шкалы: закрашивается акцентом -->
|
||||||
|
<Style x:Key="SliderDecreaseStyle" TargetType="RepeatButton">
|
||||||
|
<Setter Property="OverridesDefaultStyle" Value="True" />
|
||||||
|
<Setter Property="Focusable" Value="False" />
|
||||||
|
<Setter Property="IsTabStop" Value="False" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="RepeatButton">
|
||||||
|
<Border Height="4" CornerRadius="2"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Background="{DynamicResource Theme.Accent}" />
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style x:Key="TransparentRepeatStyle" TargetType="RepeatButton">
|
||||||
|
<Setter Property="OverridesDefaultStyle" Value="True" />
|
||||||
|
<Setter Property="Focusable" Value="False" />
|
||||||
|
<Setter Property="IsTabStop" Value="False" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="RepeatButton">
|
||||||
|
<Border Background="Transparent" />
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="Slider">
|
||||||
|
<Setter Property="VerticalAlignment" Value="Center" />
|
||||||
|
<Setter Property="IsSnapToTickEnabled" Value="True" />
|
||||||
|
<Setter Property="MinHeight" Value="24" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="Slider">
|
||||||
|
<Grid Background="Transparent">
|
||||||
|
<Border Height="4" CornerRadius="2"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Background="{DynamicResource Theme.SliderTrack}" />
|
||||||
|
<Track x:Name="PART_Track">
|
||||||
|
<Track.DecreaseRepeatButton>
|
||||||
|
<RepeatButton Command="Slider.DecreaseLarge"
|
||||||
|
Style="{StaticResource SliderDecreaseStyle}" />
|
||||||
|
</Track.DecreaseRepeatButton>
|
||||||
|
<Track.Thumb>
|
||||||
|
<Thumb Style="{StaticResource SliderThumbStyle}" />
|
||||||
|
</Track.Thumb>
|
||||||
|
<Track.IncreaseRepeatButton>
|
||||||
|
<RepeatButton Command="Slider.IncreaseLarge"
|
||||||
|
Style="{StaticResource TransparentRepeatStyle}" />
|
||||||
|
</Track.IncreaseRepeatButton>
|
||||||
|
</Track>
|
||||||
|
</Grid>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<!-- ======================= ScrollBar ======================= -->
|
||||||
|
|
||||||
|
<Style x:Key="ScrollBarThumbStyle" TargetType="Thumb">
|
||||||
|
<Setter Property="OverridesDefaultStyle" Value="True" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="Thumb">
|
||||||
|
<Border Background="{DynamicResource Theme.ScrollBarThumb}"
|
||||||
|
CornerRadius="3" Margin="3" />
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
<Style TargetType="ScrollBar">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="Width" Value="12" />
|
||||||
|
<Setter Property="MinWidth" Value="12" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<Setter.Value>
|
||||||
|
<ControlTemplate TargetType="ScrollBar">
|
||||||
|
<Grid Background="{TemplateBinding Background}">
|
||||||
|
<Track x:Name="PART_Track"
|
||||||
|
Orientation="{TemplateBinding Orientation}"
|
||||||
|
IsDirectionReversed="True">
|
||||||
|
<Track.DecreaseRepeatButton>
|
||||||
|
<RepeatButton Command="ScrollBar.PageUpCommand"
|
||||||
|
Style="{StaticResource TransparentRepeatStyle}" />
|
||||||
|
</Track.DecreaseRepeatButton>
|
||||||
|
<Track.Thumb>
|
||||||
|
<Thumb Style="{StaticResource ScrollBarThumbStyle}" />
|
||||||
|
</Track.Thumb>
|
||||||
|
<Track.IncreaseRepeatButton>
|
||||||
|
<RepeatButton Command="ScrollBar.PageDownCommand"
|
||||||
|
Style="{StaticResource TransparentRepeatStyle}" />
|
||||||
|
</Track.IncreaseRepeatButton>
|
||||||
|
</Track>
|
||||||
|
</Grid>
|
||||||
|
<ControlTemplate.Triggers>
|
||||||
|
<!-- У горизонтальной полосы направление трека обычное -->
|
||||||
|
<Trigger Property="Orientation" Value="Horizontal">
|
||||||
|
<Setter TargetName="PART_Track" Property="IsDirectionReversed" Value="False" />
|
||||||
|
</Trigger>
|
||||||
|
</ControlTemplate.Triggers>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter.Value>
|
||||||
|
</Setter>
|
||||||
|
<Style.Triggers>
|
||||||
|
<Trigger Property="Orientation" Value="Horizontal">
|
||||||
|
<Setter Property="Width" Value="Auto" />
|
||||||
|
<Setter Property="MinWidth" Value="0" />
|
||||||
|
<Setter Property="Height" Value="12" />
|
||||||
|
<Setter Property="MinHeight" Value="12" />
|
||||||
|
</Trigger>
|
||||||
|
</Style.Triggers>
|
||||||
|
</Style>
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="Theme.WindowBackground" Color="#1E1E21" />
|
||||||
|
<SolidColorBrush x:Key="Theme.Foreground" Color="#E8E8EA" />
|
||||||
|
<SolidColorBrush x:Key="Theme.SecondaryForeground" Color="#9A9AA2" />
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="Theme.Surface" Color="#26262A" />
|
||||||
|
<SolidColorBrush x:Key="Theme.SurfaceStrong" Color="#303036" />
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="Theme.ControlBackground" Color="#2C2C31" />
|
||||||
|
<SolidColorBrush x:Key="Theme.ControlHoverBackground" Color="#38383E" />
|
||||||
|
<SolidColorBrush x:Key="Theme.ControlBorder" Color="#46464C" />
|
||||||
|
<SolidColorBrush x:Key="Theme.PopupBackground" Color="#2A2A2F" />
|
||||||
|
<SolidColorBrush x:Key="Theme.SelectionBackground" Color="#3A4665" />
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="Theme.Accent" Color="#5B8CF7" />
|
||||||
|
<SolidColorBrush x:Key="Theme.SliderTrack" Color="#46464C" />
|
||||||
|
<SolidColorBrush x:Key="Theme.ScrollBarThumb" Color="#55555C" />
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
<ResourceDictionary xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="Theme.WindowBackground" Color="#FFFFFF" />
|
||||||
|
<SolidColorBrush x:Key="Theme.Foreground" Color="#1B1B1F" />
|
||||||
|
<SolidColorBrush x:Key="Theme.SecondaryForeground" Color="#6B6B70" />
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="Theme.Surface" Color="#FAFAFB" />
|
||||||
|
<SolidColorBrush x:Key="Theme.SurfaceStrong" Color="#EFEFF1" />
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="Theme.ControlBackground" Color="#FFFFFF" />
|
||||||
|
<SolidColorBrush x:Key="Theme.ControlHoverBackground" Color="#F2F2F4" />
|
||||||
|
<SolidColorBrush x:Key="Theme.ControlBorder" Color="#D0D0D4" />
|
||||||
|
<SolidColorBrush x:Key="Theme.PopupBackground" Color="#FFFFFF" />
|
||||||
|
<SolidColorBrush x:Key="Theme.SelectionBackground" Color="#E4EBFB" />
|
||||||
|
|
||||||
|
<SolidColorBrush x:Key="Theme.Accent" Color="#2563EB" />
|
||||||
|
<SolidColorBrush x:Key="Theme.SliderTrack" Color="#DCDCE0" />
|
||||||
|
<SolidColorBrush x:Key="Theme.ScrollBarThumb" Color="#C2C2C7" />
|
||||||
|
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -62,6 +62,7 @@ public sealed partial class SettingsViewModel : ObservableObject, IDisposable
|
|||||||
Settings.PropertyChanged += OnSettingsChanged;
|
Settings.PropertyChanged += OnSettingsChanged;
|
||||||
Localization.PropertyChanged += OnLocalizationChanged;
|
Localization.PropertyChanged += OnLocalizationChanged;
|
||||||
|
|
||||||
|
Themes = CreateOptions<AppTheme>();
|
||||||
PlacementModes = CreateOptions<PopupPlacementMode>();
|
PlacementModes = CreateOptions<PopupPlacementMode>();
|
||||||
AnchorSides = CreateOptions<AnchorSide>();
|
AnchorSides = CreateOptions<AnchorSide>();
|
||||||
ScreenPositions = CreateOptions<ScreenPosition>();
|
ScreenPositions = CreateOptions<ScreenPosition>();
|
||||||
@@ -73,6 +74,8 @@ public sealed partial class SettingsViewModel : ObservableObject, IDisposable
|
|||||||
|
|
||||||
public IReadOnlyList<Color> BackgroundPalette { get; } = Palette;
|
public IReadOnlyList<Color> BackgroundPalette { get; } = Palette;
|
||||||
|
|
||||||
|
public IReadOnlyList<EnumOption<AppTheme>> Themes { get; }
|
||||||
|
|
||||||
public IReadOnlyList<EnumOption<PopupPlacementMode>> PlacementModes { get; }
|
public IReadOnlyList<EnumOption<PopupPlacementMode>> PlacementModes { get; }
|
||||||
|
|
||||||
public IReadOnlyList<EnumOption<AnchorSide>> AnchorSides { get; }
|
public IReadOnlyList<EnumOption<AnchorSide>> AnchorSides { get; }
|
||||||
@@ -102,6 +105,7 @@ public sealed partial class SettingsViewModel : ObservableObject, IDisposable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Translate(Themes);
|
||||||
Translate(PlacementModes);
|
Translate(PlacementModes);
|
||||||
Translate(AnchorSides);
|
Translate(AnchorSides);
|
||||||
Translate(ScreenPositions);
|
Translate(ScreenPositions);
|
||||||
|
|||||||
@@ -9,7 +9,9 @@
|
|||||||
d:DataContext="{d:DesignInstance Type=vm:SettingsViewModel}"
|
d:DataContext="{d:DesignInstance Type=vm:SettingsViewModel}"
|
||||||
Title="{Binding Localization[SettingsTitle]}"
|
Title="{Binding Localization[SettingsTitle]}"
|
||||||
Width="560" SizeToContent="Height" MaxHeight="900"
|
Width="560" SizeToContent="Height" MaxHeight="900"
|
||||||
ResizeMode="CanMinimize">
|
ResizeMode="CanMinimize"
|
||||||
|
Background="{DynamicResource Theme.WindowBackground}"
|
||||||
|
Foreground="{DynamicResource Theme.Foreground}">
|
||||||
<Window.Resources>
|
<Window.Resources>
|
||||||
<local:EnumToVisibilityConverter x:Key="EnumToVisibility" />
|
<local:EnumToVisibilityConverter x:Key="EnumToVisibility" />
|
||||||
<local:ColorToBrushConverter x:Key="ColorToBrush" />
|
<local:ColorToBrushConverter x:Key="ColorToBrush" />
|
||||||
@@ -24,18 +26,7 @@
|
|||||||
<Setter Property="Margin" Value="12,0,0,0" />
|
<Setter Property="Margin" Value="12,0,0,0" />
|
||||||
<Setter Property="MinWidth" Value="48" />
|
<Setter Property="MinWidth" Value="48" />
|
||||||
<Setter Property="TextAlignment" Value="Right" />
|
<Setter Property="TextAlignment" Value="Right" />
|
||||||
<Setter Property="Foreground" Value="#666" />
|
<Setter Property="Foreground" Value="{DynamicResource Theme.SecondaryForeground}" />
|
||||||
</Style>
|
|
||||||
<Style TargetType="GroupBox">
|
|
||||||
<Setter Property="Padding" Value="12" />
|
|
||||||
<Setter Property="Margin" Value="0,0,0,12" />
|
|
||||||
</Style>
|
|
||||||
<Style TargetType="ComboBox">
|
|
||||||
<Setter Property="VerticalAlignment" Value="Center" />
|
|
||||||
</Style>
|
|
||||||
<Style TargetType="Slider">
|
|
||||||
<Setter Property="VerticalAlignment" Value="Center" />
|
|
||||||
<Setter Property="IsSnapToTickEnabled" Value="True" />
|
|
||||||
</Style>
|
</Style>
|
||||||
</Window.Resources>
|
</Window.Resources>
|
||||||
|
|
||||||
@@ -48,6 +39,11 @@
|
|||||||
<ColumnDefinition Width="180" />
|
<ColumnDefinition Width="180" />
|
||||||
<ColumnDefinition Width="*" />
|
<ColumnDefinition Width="*" />
|
||||||
</Grid.ColumnDefinitions>
|
</Grid.ColumnDefinitions>
|
||||||
|
<Grid.RowDefinitions>
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
<RowDefinition Height="Auto" />
|
||||||
|
</Grid.RowDefinitions>
|
||||||
|
|
||||||
<TextBlock Style="{StaticResource FieldLabel}"
|
<TextBlock Style="{StaticResource FieldLabel}"
|
||||||
Text="{Binding Localization[LanguageLabel]}" />
|
Text="{Binding Localization[LanguageLabel]}" />
|
||||||
<ComboBox Grid.Column="1"
|
<ComboBox Grid.Column="1"
|
||||||
@@ -55,6 +51,15 @@
|
|||||||
DisplayMemberPath="DisplayName"
|
DisplayMemberPath="DisplayName"
|
||||||
SelectedValuePath="Code"
|
SelectedValuePath="Code"
|
||||||
SelectedValue="{Binding Settings.Language}" />
|
SelectedValue="{Binding Settings.Language}" />
|
||||||
|
|
||||||
|
<TextBlock Grid.Row="1" Margin="0,12,12,0"
|
||||||
|
Style="{StaticResource FieldLabel}"
|
||||||
|
Text="{Binding Localization[ThemeLabel]}" />
|
||||||
|
<ComboBox Grid.Row="1" Grid.Column="1" Margin="0,12,0,0"
|
||||||
|
ItemsSource="{Binding Themes}"
|
||||||
|
DisplayMemberPath="Display"
|
||||||
|
SelectedValuePath="Value"
|
||||||
|
SelectedValue="{Binding Settings.Theme}" />
|
||||||
</Grid>
|
</Grid>
|
||||||
</GroupBox>
|
</GroupBox>
|
||||||
|
|
||||||
@@ -245,7 +250,7 @@
|
|||||||
Text="{Binding Localization[PreviewLabel]}" />
|
Text="{Binding Localization[PreviewLabel]}" />
|
||||||
<Border Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2"
|
<Border Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2"
|
||||||
Margin="0,12,0,0" Padding="16"
|
Margin="0,12,0,0" Padding="16"
|
||||||
Background="#F0F0F0" CornerRadius="4"
|
Background="{DynamicResource Theme.SurfaceStrong}" CornerRadius="4"
|
||||||
HorizontalAlignment="Stretch">
|
HorizontalAlignment="Stretch">
|
||||||
<Border CornerRadius="4" Padding="10,4"
|
<Border CornerRadius="4" Padding="10,4"
|
||||||
HorizontalAlignment="Center"
|
HorizontalAlignment="Center"
|
||||||
@@ -273,7 +278,8 @@
|
|||||||
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center">
|
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center">
|
||||||
<TextBlock Style="{StaticResource FieldValue}"
|
<TextBlock Style="{StaticResource FieldValue}"
|
||||||
Text="{Binding Settings.DurationMilliseconds, StringFormat={}{0:F0}}" />
|
Text="{Binding Settings.DurationMilliseconds, StringFormat={}{0:F0}}" />
|
||||||
<TextBlock Margin="4,0,0,0" Foreground="#666"
|
<TextBlock Margin="4,0,0,0"
|
||||||
|
Foreground="{DynamicResource Theme.SecondaryForeground}"
|
||||||
Text="{Binding Localization[MillisecondsSuffix]}" />
|
Text="{Binding Localization[MillisecondsSuffix]}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</Grid>
|
</Grid>
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
using System.Windows;
|
using System.Windows;
|
||||||
|
using CursorLang.Services;
|
||||||
using CursorLang.ViewModels;
|
using CursorLang.ViewModels;
|
||||||
|
|
||||||
namespace CursorLang.Views;
|
namespace CursorLang.Views;
|
||||||
@@ -8,9 +9,10 @@ namespace CursorLang.Views;
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public partial class MainWindow : Window
|
public partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
public MainWindow(SettingsViewModel viewModel)
|
public MainWindow(SettingsViewModel viewModel, IThemeService theme)
|
||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
DataContext = viewModel;
|
DataContext = viewModel;
|
||||||
|
theme.Register(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user