Merge pull request #166 from irihitech/issue/162-theme
Theme toggler update
This commit is contained in:
@@ -10,6 +10,8 @@
|
|||||||
<StackPanel Grid.Column="0">
|
<StackPanel Grid.Column="0">
|
||||||
<TextBlock Text="Global"></TextBlock>
|
<TextBlock Text="Global"></TextBlock>
|
||||||
<u:ThemeToggleButton/>
|
<u:ThemeToggleButton/>
|
||||||
|
<TextBlock Text="Global Indicator"></TextBlock>
|
||||||
|
<u:ThemeToggleButton Mode="Indicator"/>
|
||||||
<TextBlock Text="Target To Scope"></TextBlock>
|
<TextBlock Text="Target To Scope"></TextBlock>
|
||||||
<u:ThemeToggleButton TargetScope="{Binding #scope}"></u:ThemeToggleButton>
|
<u:ThemeToggleButton TargetScope="{Binding #scope}"></u:ThemeToggleButton>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|||||||
@@ -92,6 +92,8 @@
|
|||||||
<u:ThemeToggleButton
|
<u:ThemeToggleButton
|
||||||
Grid.Row="0"
|
Grid.Row="0"
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
|
IsThreeState="True"
|
||||||
|
Mode="Controller"
|
||||||
HorizontalAlignment="Right" />
|
HorizontalAlignment="Right" />
|
||||||
<ContentControl
|
<ContentControl
|
||||||
Grid.Row="1"
|
Grid.Row="1"
|
||||||
|
|||||||
@@ -5,26 +5,31 @@
|
|||||||
<ControlTheme TargetType="u:ThemeToggleButton" x:Key="{x:Type u:ThemeToggleButton}">
|
<ControlTheme TargetType="u:ThemeToggleButton" x:Key="{x:Type u:ThemeToggleButton}">
|
||||||
<Setter Property="Template">
|
<Setter Property="Template">
|
||||||
<ControlTemplate TargetType="u:ThemeToggleButton">
|
<ControlTemplate TargetType="u:ThemeToggleButton">
|
||||||
<ToggleSwitch
|
<Button
|
||||||
Padding="4"
|
Padding="8"
|
||||||
Name="{x:Static u:ThemeToggleButton.PART_ThemeToggleButton}"
|
FontWeight="Regular"
|
||||||
Theme="{DynamicResource ButtonToggleSwitch}">
|
Name="{x:Static u:ThemeToggleButton.PART_ThemeButton}"
|
||||||
<ToggleSwitch.OnContent>
|
HorizontalAlignment="Center"
|
||||||
<PathIcon
|
Theme="{DynamicResource BorderlessButton}">
|
||||||
Width="16"
|
<PathIcon
|
||||||
Height="16"
|
Name="PART_Icon"
|
||||||
Data="{DynamicResource ThemeSelectorButtonLightGlyph}"
|
Width="16"
|
||||||
Foreground="{DynamicResource ButtonDefaultTertiaryForeground}" />
|
Height="16"
|
||||||
</ToggleSwitch.OnContent>
|
Foreground="{DynamicResource ButtonDefaultTertiaryForeground}" />
|
||||||
<ToggleSwitch.OffContent>
|
</Button>
|
||||||
<PathIcon
|
|
||||||
Width="16"
|
|
||||||
Height="16"
|
|
||||||
Data="{DynamicResource ThemeSelectorButtonDarkGlyph}"
|
|
||||||
Foreground="{DynamicResource ButtonDefaultTertiaryForeground}" />
|
|
||||||
</ToggleSwitch.OffContent>
|
|
||||||
</ToggleSwitch>
|
|
||||||
</ControlTemplate>
|
</ControlTemplate>
|
||||||
</Setter>
|
</Setter>
|
||||||
|
<Style Selector="^:dark /template/ PathIcon#PART_Icon">
|
||||||
|
<Setter Property="Data" Value="{DynamicResource ThemeSelectorButtonDarkGlyph}"></Setter>
|
||||||
|
<Setter Property="ToolTip.Tip" Value="{DynamicResource STRING_THEME_TOGGLE_DARK}"></Setter>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="^:light /template/ PathIcon#PART_Icon">
|
||||||
|
<Setter Property="Data" Value="{DynamicResource ThemeSelectorButtonLightGlyph}"></Setter>
|
||||||
|
<Setter Property="ToolTip.Tip" Value="{DynamicResource STRING_THEME_TOGGLE_LIGHT}"></Setter>
|
||||||
|
</Style>
|
||||||
|
<Style Selector="^ /template/ PathIcon#PART_Icon">
|
||||||
|
<Setter Property="Data" Value="{DynamicResource ThemeSelectorButtonDefaultGlyph}"></Setter>
|
||||||
|
<Setter Property="ToolTip.Tip" Value="{DynamicResource STRING_THEME_TOGGLE_SYSTEM}"></Setter>
|
||||||
|
</Style>
|
||||||
</ControlTheme>
|
</ControlTheme>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -12,4 +12,7 @@
|
|||||||
<x:String x:Key="STRING_MENU_DIALOG_CLOSE">Close</x:String>
|
<x:String x:Key="STRING_MENU_DIALOG_CLOSE">Close</x:String>
|
||||||
<x:String x:Key="STRING_PAGINATION_JUMP_TO">Jump to page</x:String>
|
<x:String x:Key="STRING_PAGINATION_JUMP_TO">Jump to page</x:String>
|
||||||
<x:String x:Key="STRING_PAGINATION_PAGE"> </x:String>
|
<x:String x:Key="STRING_PAGINATION_PAGE"> </x:String>
|
||||||
|
<x:String x:Key="STRING_THEME_TOGGLE_DARK">Dark</x:String>
|
||||||
|
<x:String x:Key="STRING_THEME_TOGGLE_LIGHT">Light</x:String>
|
||||||
|
<x:String x:Key="STRING_THEME_TOGGLE_SYSTEM">System</x:String>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -12,4 +12,7 @@
|
|||||||
<x:String x:Key="STRING_MENU_DIALOG_CLOSE">关闭</x:String>
|
<x:String x:Key="STRING_MENU_DIALOG_CLOSE">关闭</x:String>
|
||||||
<x:String x:Key="STRING_PAGINATION_JUMP_TO">跳至</x:String>
|
<x:String x:Key="STRING_PAGINATION_JUMP_TO">跳至</x:String>
|
||||||
<x:String x:Key="STRING_PAGINATION_PAGE">页</x:String>
|
<x:String x:Key="STRING_PAGINATION_PAGE">页</x:String>
|
||||||
|
<x:String x:Key="STRING_THEME_TOGGLE_DARK">暗色</x:String>
|
||||||
|
<x:String x:Key="STRING_THEME_TOGGLE_LIGHT">亮色</x:String>
|
||||||
|
<x:String x:Key="STRING_THEME_TOGGLE_SYSTEM">跟随系统</x:String>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
<!-- Add Resources Here -->
|
<!-- Add Resources Here -->
|
||||||
<StreamGeometry x:Key="ThemeSelectorButtonDarkGlyph">M12 23C18.0751 23 23 18.0751 23 12C23 5.92487 18.0751 1 12 1C5.92487 1 1 5.92487 1 12C1 18.0751 5.92487 23 12 23ZM17 15C17.476 15 17.9408 14.9525 18.3901 14.862C17.296 17.3011 14.8464 19 12 19C8.13401 19 5 15.866 5 12C5 8.60996 7.40983 5.78277 10.6099 5.13803C10.218 6.01173 10 6.98041 10 8C10 11.866 13.134 15 17 15Z</StreamGeometry>
|
<StreamGeometry x:Key="ThemeSelectorButtonDarkGlyph">M9,2C7.95,2 6.95,2.16 6,2.46C10.06,3.73 13,7.5 13,12C13,16.5 10.06,20.27 6,21.54C6.95,21.84 7.95,22 9,22A10,10 0 0,0 19,12A10,10 0 0,0 9,2Z</StreamGeometry>
|
||||||
<StreamGeometry x:Key="ThemeSelectorButtonLightGlyph">M3.55 19.09L4.96 20.5L6.76 18.71L5.34 17.29M12 6C8.69 6 6 8.69 6 12S8.69 18 12 18 18 15.31 18 12C18 8.68 15.31 6 12 6M20 13H23V11H20M17.24 18.71L19.04 20.5L20.45 19.09L18.66 17.29M20.45 5L19.04 3.6L17.24 5.39L18.66 6.81M13 1H11V4H13M6.76 5.39L4.96 3.6L3.55 5L5.34 6.81L6.76 5.39M1 13H4V11H1M13 20H11V23H13</StreamGeometry>
|
<StreamGeometry x:Key="ThemeSelectorButtonLightGlyph">M12,8A4,4 0 0,0 8,12A4,4 0 0,0 12,16A4,4 0 0,0 16,12A4,4 0 0,0 12,8M12,18A6,6 0 0,1 6,12A6,6 0 0,1 12,6A6,6 0 0,1 18,12A6,6 0 0,1 12,18M20,8.69V4H15.31L12,0.69L8.69,4H4V8.69L0.69,12L4,15.31V20H8.69L12,23.31L15.31,20H20V15.31L23.31,12L20,8.69Z</StreamGeometry>
|
||||||
|
<StreamGeometry x:Key="ThemeSelectorButtonDefaultGlyph">M12,18V6A6,6 0 0,1 18,12A6,6 0 0,1 12,18M20,15.31L23.31,12L20,8.69V4H15.31L12,0.69L8.69,4H4V8.69L0.69,12L4,15.31V20H8.69L12,23.31L15.31,20H20V15.31Z</StreamGeometry>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -23,6 +23,15 @@ public abstract class ThemeSelectorBase: TemplatedControl
|
|||||||
set => SetValue(SelectedThemeProperty, value);
|
set => SetValue(SelectedThemeProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static readonly StyledProperty<ThemeSelectorMode> ModeProperty = AvaloniaProperty.Register<ThemeSelectorBase, ThemeSelectorMode>(
|
||||||
|
nameof(Mode));
|
||||||
|
|
||||||
|
public ThemeSelectorMode Mode
|
||||||
|
{
|
||||||
|
get => GetValue(ModeProperty);
|
||||||
|
set => SetValue(ModeProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
public static readonly StyledProperty<ThemeVariantScope?> TargetScopeProperty =
|
public static readonly StyledProperty<ThemeVariantScope?> TargetScopeProperty =
|
||||||
AvaloniaProperty.Register<ThemeSelectorBase, ThemeVariantScope?>(
|
AvaloniaProperty.Register<ThemeSelectorBase, ThemeVariantScope?>(
|
||||||
nameof(TargetScope));
|
nameof(TargetScope));
|
||||||
@@ -57,15 +66,21 @@ public abstract class ThemeSelectorBase: TemplatedControl
|
|||||||
_syncFromScope = true;
|
_syncFromScope = true;
|
||||||
if (this.TargetScope is { } target)
|
if (this.TargetScope is { } target)
|
||||||
{
|
{
|
||||||
SyncThemeFromScope(target.ActualThemeVariant);
|
SyncThemeFromScope(Mode == ThemeSelectorMode.Controller
|
||||||
|
? target.RequestedThemeVariant
|
||||||
|
: target.ActualThemeVariant);
|
||||||
}
|
}
|
||||||
else if (this._scope is { } scope)
|
else if (this._scope is { } scope)
|
||||||
{
|
{
|
||||||
SyncThemeFromScope(scope.ActualThemeVariant);
|
SyncThemeFromScope(Mode == ThemeSelectorMode.Controller
|
||||||
|
? scope.RequestedThemeVariant
|
||||||
|
: scope.ActualThemeVariant);
|
||||||
}
|
}
|
||||||
else if (_application is { } app)
|
else if (_application is { } app)
|
||||||
{
|
{
|
||||||
SyncThemeFromScope(app.ActualThemeVariant);
|
SyncThemeFromScope(Mode == ThemeSelectorMode.Controller
|
||||||
|
? app.RequestedThemeVariant
|
||||||
|
: app.ActualThemeVariant);
|
||||||
}
|
}
|
||||||
_syncFromScope = false;
|
_syncFromScope = false;
|
||||||
}
|
}
|
||||||
@@ -79,21 +94,29 @@ public abstract class ThemeSelectorBase: TemplatedControl
|
|||||||
{
|
{
|
||||||
base.OnAttachedToVisualTree(e);
|
base.OnAttachedToVisualTree(e);
|
||||||
_application = Application.Current;
|
_application = Application.Current;
|
||||||
|
_syncFromScope = true;
|
||||||
if (_application is not null)
|
if (_application is not null)
|
||||||
{
|
{
|
||||||
_application.ActualThemeVariantChanged += OnScopeThemeChanged;
|
_application.ActualThemeVariantChanged += OnScopeThemeChanged;
|
||||||
SyncThemeFromScope(_application.ActualThemeVariant);
|
SyncThemeFromScope(Mode == ThemeSelectorMode.Controller
|
||||||
|
? _application.RequestedThemeVariant
|
||||||
|
: _application.ActualThemeVariant);
|
||||||
}
|
}
|
||||||
_scope = this.GetLogicalAncestors().FirstOrDefault(a => a is ThemeVariantScope) as ThemeVariantScope;
|
_scope = this.GetLogicalAncestors().FirstOrDefault(a => a is ThemeVariantScope) as ThemeVariantScope;
|
||||||
if (_scope is not null)
|
if (_scope is not null)
|
||||||
{
|
{
|
||||||
_scope.ActualThemeVariantChanged += OnScopeThemeChanged;
|
_scope.ActualThemeVariantChanged += OnScopeThemeChanged;
|
||||||
SyncThemeFromScope(_scope.ActualThemeVariant);
|
SyncThemeFromScope(Mode == ThemeSelectorMode.Controller
|
||||||
|
? _scope.RequestedThemeVariant
|
||||||
|
: _scope.ActualThemeVariant);
|
||||||
}
|
}
|
||||||
if (TargetScope is not null)
|
if (TargetScope is not null)
|
||||||
{
|
{
|
||||||
SyncThemeFromScope(TargetScope.ActualThemeVariant);
|
SyncThemeFromScope(Mode == ThemeSelectorMode.Controller
|
||||||
|
? TargetScope.RequestedThemeVariant
|
||||||
|
: TargetScope.ActualThemeVariant);
|
||||||
}
|
}
|
||||||
|
_syncFromScope = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
@@ -113,7 +136,6 @@ public abstract class ThemeSelectorBase: TemplatedControl
|
|||||||
{
|
{
|
||||||
if (_syncFromScope) return;
|
if (_syncFromScope) return;
|
||||||
ThemeVariant? newTheme = args.NewValue.Value;
|
ThemeVariant? newTheme = args.NewValue.Value;
|
||||||
if (newTheme is null) return;
|
|
||||||
if (TargetScope is not null)
|
if (TargetScope is not null)
|
||||||
{
|
{
|
||||||
TargetScope.RequestedThemeVariant = newTheme;
|
TargetScope.RequestedThemeVariant = newTheme;
|
||||||
|
|||||||
7
src/Ursa/Controls/ThemeSelector/ThemeSelectorMode.cs
Normal file
7
src/Ursa/Controls/ThemeSelector/ThemeSelectorMode.cs
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
namespace Ursa.Controls;
|
||||||
|
|
||||||
|
public enum ThemeSelectorMode
|
||||||
|
{
|
||||||
|
Controller,
|
||||||
|
Indicator,
|
||||||
|
}
|
||||||
@@ -9,42 +9,90 @@ using Ursa.Common;
|
|||||||
|
|
||||||
namespace Ursa.Controls;
|
namespace Ursa.Controls;
|
||||||
|
|
||||||
[TemplatePart(PART_ThemeToggleButton, typeof(ToggleButton))]
|
[TemplatePart(PART_ThemeButton, typeof(Button))]
|
||||||
|
[PseudoClasses(PC_Dark, PC_Light, PC_Default)]
|
||||||
public class ThemeToggleButton: ThemeSelectorBase
|
public class ThemeToggleButton: ThemeSelectorBase
|
||||||
{
|
{
|
||||||
public const string PART_ThemeToggleButton = "PART_ThemeToggleButton";
|
public const string PART_ThemeButton = "PART_ThemeButton";
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// This button IsChecked=true means ThemeVariant.Light, IsChecked=false means ThemeVariant.Dark.
|
|
||||||
/// </summary>
|
|
||||||
private ToggleButton? _button;
|
|
||||||
private ThemeVariant? _currentTheme;
|
|
||||||
|
|
||||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
public const string PC_Light = ":light";
|
||||||
{
|
public const string PC_Dark = ":dark";
|
||||||
base.OnAttachedToVisualTree(e);
|
public const string PC_Default = ":default";
|
||||||
_currentTheme = this.ActualThemeVariant;
|
|
||||||
}
|
private Button? _button;
|
||||||
|
private bool? _state;
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> IsThreeStateProperty = AvaloniaProperty.Register<ThemeToggleButton, bool>(
|
||||||
|
nameof(IsThreeState));
|
||||||
|
|
||||||
|
public bool IsThreeState
|
||||||
|
{
|
||||||
|
get => GetValue(IsThreeStateProperty);
|
||||||
|
set => SetValue(IsThreeStateProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnApplyTemplate(e);
|
base.OnApplyTemplate(e);
|
||||||
Button.ClickEvent.RemoveHandler(OnButtonClickedChanged, _button);
|
Button.ClickEvent.RemoveHandler(OnButtonClicked, _button);
|
||||||
_button = e.NameScope.Get<ToggleButton>(PART_ThemeToggleButton);
|
_button = e.NameScope.Get<Button>(PART_ThemeButton);
|
||||||
Button.ClickEvent.AddHandler(OnButtonClickedChanged, _button);
|
Button.ClickEvent.AddHandler(OnButtonClicked, _button);
|
||||||
ToggleButton.IsCheckedProperty.SetValue(_currentTheme == ThemeVariant.Light, _button);
|
// ToggleButton.IsCheckedProperty.SetValue(_currentTheme == ThemeVariant.Light, _button);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnButtonClickedChanged(object sender, RoutedEventArgs e)
|
private void OnButtonClicked(object sender, RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
var newTheme = (sender as ToggleButton)!.IsChecked;
|
bool? currentState = _state;
|
||||||
if (newTheme is null) return;
|
if (IsThreeState)
|
||||||
SetCurrentValue(SelectedThemeProperty, newTheme.Value ? ThemeVariant.Light : ThemeVariant.Dark);
|
{
|
||||||
|
_state = currentState switch
|
||||||
|
{
|
||||||
|
true => false,
|
||||||
|
false => null,
|
||||||
|
null => true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
_state = currentState switch
|
||||||
|
{
|
||||||
|
true => false,
|
||||||
|
false => true,
|
||||||
|
null => true,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (_state == true)
|
||||||
|
{
|
||||||
|
SelectedTheme = ThemeVariant.Light;
|
||||||
|
}
|
||||||
|
else if (_state == false)
|
||||||
|
{
|
||||||
|
SelectedTheme = ThemeVariant.Dark;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SelectedTheme = ThemeVariant.Default;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Mode == ThemeSelectorMode.Controller)
|
||||||
|
{
|
||||||
|
PseudoClasses.Set(PC_Light, SelectedTheme == ThemeVariant.Light);
|
||||||
|
PseudoClasses.Set(PC_Dark, SelectedTheme == ThemeVariant.Dark);
|
||||||
|
PseudoClasses.Set(PC_Default, SelectedTheme == null || SelectedTheme == ThemeVariant.Default);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void SyncThemeFromScope(ThemeVariant? theme)
|
protected override void SyncThemeFromScope(ThemeVariant? theme)
|
||||||
{
|
{
|
||||||
base.SyncThemeFromScope(theme);
|
base.SyncThemeFromScope(theme);
|
||||||
ToggleButton.IsCheckedProperty.SetValue(theme == ThemeVariant.Light, _button);
|
if (Mode == ThemeSelectorMode.Indicator)
|
||||||
|
{
|
||||||
|
PseudoClasses.Set(PC_Light, theme == ThemeVariant.Light);
|
||||||
|
PseudoClasses.Set(PC_Dark, theme == ThemeVariant.Dark);
|
||||||
|
PseudoClasses.Set(PC_Default, theme == null || SelectedTheme == ThemeVariant.Default);
|
||||||
|
if (theme == ThemeVariant.Dark) _state = false;
|
||||||
|
else if (theme == ThemeVariant.Light) _state = true;
|
||||||
|
else _state = null;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user