added interface themes
This commit is contained in:
+5
-1
@@ -3,6 +3,10 @@
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
ShutdownMode="OnMainWindowClose">
|
||||
<Application.Resources>
|
||||
|
||||
<ResourceDictionary>
|
||||
<ResourceDictionary.MergedDictionaries>
|
||||
<ResourceDictionary Source="Themes/Controls.xaml" />
|
||||
</ResourceDictionary.MergedDictionaries>
|
||||
</ResourceDictionary>
|
||||
</Application.Resources>
|
||||
</Application>
|
||||
|
||||
@@ -22,6 +22,8 @@ public partial class App : Application
|
||||
ConfigureServices(services);
|
||||
_services = services.BuildServiceProvider();
|
||||
|
||||
_services.GetRequiredService<ThemeService>();
|
||||
|
||||
MainWindow = _services.GetRequiredService<MainWindow>();
|
||||
MainWindow.Show();
|
||||
|
||||
@@ -43,6 +45,9 @@ public partial class App : Application
|
||||
services.AddSingleton<SettingsService>();
|
||||
services.AddSingleton(provider => provider.GetRequiredService<SettingsService>().Load());
|
||||
|
||||
services.AddSingleton<ThemeService>();
|
||||
services.AddSingleton<IThemeService>(provider => provider.GetRequiredService<ThemeService>());
|
||||
|
||||
services.AddSingleton<ILocalizationService, LocalizationService>();
|
||||
services.AddSingleton<IKeyboardLayoutService, KeyboardLayoutService>();
|
||||
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]
|
||||
private string _language = "en";
|
||||
|
||||
/// <summary>Оформление окна настроек.</summary>
|
||||
[ObservableProperty]
|
||||
private AppTheme _theme = AppTheme.System;
|
||||
|
||||
[ObservableProperty]
|
||||
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">
|
||||
<value>Interface language</value>
|
||||
</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">
|
||||
<value>Placement</value>
|
||||
</data>
|
||||
|
||||
@@ -67,6 +67,18 @@
|
||||
<data name="LanguageLabel" xml:space="preserve">
|
||||
<value>Язык интерфейса</value>
|
||||
</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">
|
||||
<value>Расположение</value>
|
||||
</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)
|
||||
{
|
||||
// Испорченный файл не должен мешать запуску: начнём с умолчаний
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// Язык по умолчанию берём системный, если он поддерживается
|
||||
private static AppSettings CreateDefault()
|
||||
{
|
||||
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;
|
||||
Localization.PropertyChanged += OnLocalizationChanged;
|
||||
|
||||
Themes = CreateOptions<AppTheme>();
|
||||
PlacementModes = CreateOptions<PopupPlacementMode>();
|
||||
AnchorSides = CreateOptions<AnchorSide>();
|
||||
ScreenPositions = CreateOptions<ScreenPosition>();
|
||||
@@ -73,6 +74,8 @@ public sealed partial class SettingsViewModel : ObservableObject, IDisposable
|
||||
|
||||
public IReadOnlyList<Color> BackgroundPalette { get; } = Palette;
|
||||
|
||||
public IReadOnlyList<EnumOption<AppTheme>> Themes { get; }
|
||||
|
||||
public IReadOnlyList<EnumOption<PopupPlacementMode>> PlacementModes { get; }
|
||||
|
||||
public IReadOnlyList<EnumOption<AnchorSide>> AnchorSides { get; }
|
||||
@@ -102,6 +105,7 @@ public sealed partial class SettingsViewModel : ObservableObject, IDisposable
|
||||
return;
|
||||
}
|
||||
|
||||
Translate(Themes);
|
||||
Translate(PlacementModes);
|
||||
Translate(AnchorSides);
|
||||
Translate(ScreenPositions);
|
||||
|
||||
@@ -9,7 +9,9 @@
|
||||
d:DataContext="{d:DesignInstance Type=vm:SettingsViewModel}"
|
||||
Title="{Binding Localization[SettingsTitle]}"
|
||||
Width="560" SizeToContent="Height" MaxHeight="900"
|
||||
ResizeMode="CanMinimize">
|
||||
ResizeMode="CanMinimize"
|
||||
Background="{DynamicResource Theme.WindowBackground}"
|
||||
Foreground="{DynamicResource Theme.Foreground}">
|
||||
<Window.Resources>
|
||||
<local:EnumToVisibilityConverter x:Key="EnumToVisibility" />
|
||||
<local:ColorToBrushConverter x:Key="ColorToBrush" />
|
||||
@@ -24,18 +26,7 @@
|
||||
<Setter Property="Margin" Value="12,0,0,0" />
|
||||
<Setter Property="MinWidth" Value="48" />
|
||||
<Setter Property="TextAlignment" Value="Right" />
|
||||
<Setter Property="Foreground" Value="#666" />
|
||||
</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" />
|
||||
<Setter Property="Foreground" Value="{DynamicResource Theme.SecondaryForeground}" />
|
||||
</Style>
|
||||
</Window.Resources>
|
||||
|
||||
@@ -48,6 +39,11 @@
|
||||
<ColumnDefinition Width="180" />
|
||||
<ColumnDefinition Width="*" />
|
||||
</Grid.ColumnDefinitions>
|
||||
<Grid.RowDefinitions>
|
||||
<RowDefinition Height="Auto" />
|
||||
<RowDefinition Height="Auto" />
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<TextBlock Style="{StaticResource FieldLabel}"
|
||||
Text="{Binding Localization[LanguageLabel]}" />
|
||||
<ComboBox Grid.Column="1"
|
||||
@@ -55,6 +51,15 @@
|
||||
DisplayMemberPath="DisplayName"
|
||||
SelectedValuePath="Code"
|
||||
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>
|
||||
</GroupBox>
|
||||
|
||||
@@ -245,7 +250,7 @@
|
||||
Text="{Binding Localization[PreviewLabel]}" />
|
||||
<Border Grid.Row="3" Grid.Column="1" Grid.ColumnSpan="2"
|
||||
Margin="0,12,0,0" Padding="16"
|
||||
Background="#F0F0F0" CornerRadius="4"
|
||||
Background="{DynamicResource Theme.SurfaceStrong}" CornerRadius="4"
|
||||
HorizontalAlignment="Stretch">
|
||||
<Border CornerRadius="4" Padding="10,4"
|
||||
HorizontalAlignment="Center"
|
||||
@@ -273,7 +278,8 @@
|
||||
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center">
|
||||
<TextBlock Style="{StaticResource FieldValue}"
|
||||
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]}" />
|
||||
</StackPanel>
|
||||
</Grid>
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using System.Windows;
|
||||
using CursorLang.Services;
|
||||
using CursorLang.ViewModels;
|
||||
|
||||
namespace CursorLang.Views;
|
||||
@@ -8,9 +9,10 @@ namespace CursorLang.Views;
|
||||
/// </summary>
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow(SettingsViewModel viewModel)
|
||||
public MainWindow(SettingsViewModel viewModel, IThemeService theme)
|
||||
{
|
||||
InitializeComponent();
|
||||
DataContext = viewModel;
|
||||
theme.Register(this);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user