feat: Theme toggler

This commit is contained in:
rabbitism
2024-02-06 19:57:11 +08:00
committed by rabbitism
parent 84ad981297
commit 5f35f574a9
11 changed files with 182 additions and 5 deletions

View File

@@ -77,13 +77,10 @@
</u:NavigationMenu.ItemTemplate> </u:NavigationMenu.ItemTemplate>
</u:NavigationMenu> </u:NavigationMenu>
</Border> </Border>
<u:ThemeToggleButton
<ToggleButton
Grid.Row="0" Grid.Row="0"
Grid.Column="1" Grid.Column="1"
HorizontalAlignment="Right" HorizontalAlignment="Right"/>
Content="Update Theme"
IsCheckedChanged="ToggleButton_OnIsCheckedChanged" />
<ContentControl <ContentControl
Grid.Row="1" Grid.Row="1"
Grid.Column="1" Grid.Column="1"

View File

@@ -0,0 +1,30 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:u="https://irihi.tech/ursa">
<!-- Add Resources Here -->
<ControlTheme TargetType="u:ThemeToggleButton" x:Key="{x:Type u:ThemeToggleButton}">
<Setter Property="Template">
<ControlTemplate TargetType="u:ThemeToggleButton">
<ToggleSwitch
Padding="4"
Name="{x:Static u:ThemeToggleButton.PART_ThemeToggleButton}"
Theme="{DynamicResource ButtonToggleSwitch}">
<ToggleSwitch.OnContent>
<PathIcon
Width="16"
Height="16"
Data="{DynamicResource ThemeSelectorButtonLightGlyph}"
Foreground="{DynamicResource ButtonDefaultTertiaryForeground}" />
</ToggleSwitch.OnContent>
<ToggleSwitch.OffContent>
<PathIcon
Width="16"
Height="16"
Data="{DynamicResource ThemeSelectorButtonDarkGlyph}"
Foreground="{DynamicResource ButtonDefaultTertiaryForeground}" />
</ToggleSwitch.OffContent>
</ToggleSwitch>
</ControlTemplate>
</Setter>
</ControlTheme>
</ResourceDictionary>

View File

@@ -22,6 +22,7 @@
<ResourceInclude Source="Pagination.axaml" /> <ResourceInclude Source="Pagination.axaml" />
<ResourceInclude Source="RangeSlider.axaml" /> <ResourceInclude Source="RangeSlider.axaml" />
<ResourceInclude Source="TagInput.axaml" /> <ResourceInclude Source="TagInput.axaml" />
<ResourceInclude Source="ThemeSelector.axaml" />
<ResourceInclude Source="Timeline.axaml" /> <ResourceInclude Source="Timeline.axaml" />
<ResourceInclude Source="TwoTonePathIcon.axaml" /> <ResourceInclude Source="TwoTonePathIcon.axaml" />
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>

View File

@@ -0,0 +1,6 @@
<ResourceDictionary xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<!-- 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="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>
</ResourceDictionary>

View File

@@ -14,5 +14,6 @@
<MergeResourceInclude Source="NavigationMenu.axaml" /> <MergeResourceInclude Source="NavigationMenu.axaml" />
<MergeResourceInclude Source="Pagination.axaml" /> <MergeResourceInclude Source="Pagination.axaml" />
<MergeResourceInclude Source="TagInput.axaml" /> <MergeResourceInclude Source="TagInput.axaml" />
<MergeResourceInclude Source="ThemeSelector.axaml" />
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
</ResourceDictionary> </ResourceDictionary>

View File

@@ -20,4 +20,22 @@ internal static class EventHelper
if(button is not null) button.Click -= handler; if(button is not null) button.Click -= handler;
} }
} }
public static void RegisterEvent<TArgs>(RoutedEvent<TArgs> routedEvent, EventHandler<TArgs> handler, params Button?[] controls)
where TArgs : RoutedEventArgs
{
foreach (var control in controls)
{
control?.AddHandler(routedEvent, handler);
}
}
public static void UnregisterEvent<TArgs>(RoutedEvent<TArgs> routedEvent, EventHandler<TArgs> handler, params Button?[] controls)
where TArgs : RoutedEventArgs
{
foreach (var control in controls)
{
control?.RemoveHandler(routedEvent, handler);
}
}
} }

View File

@@ -0,0 +1,14 @@
using Avalonia;
namespace Ursa.Common;
public static class PropertyHelper
{
public static void SetValue<TValue>(AvaloniaProperty<TValue> property, TValue value, params AvaloniaObject?[] elements)
{
foreach (var element in elements)
{
element?.SetValue(property, value);
}
}
}

View File

@@ -0,0 +1,65 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.LogicalTree;
using Avalonia.Styling;
namespace Ursa.Controls;
public abstract class ThemeSelectorBase: TemplatedControl
{
private Application? _application;
private ThemeVariantScope? _scope;
public static readonly StyledProperty<ThemeVariant?> SelectedThemeProperty = AvaloniaProperty.Register<ThemeSelectorBase, ThemeVariant?>(
nameof(SelectedTheme));
public ThemeVariant? SelectedTheme
{
get => GetValue(SelectedThemeProperty);
set => SetValue(SelectedThemeProperty, value);
}
public static readonly StyledProperty<ThemeVariantScope?> TargetScopeProperty =
AvaloniaProperty.Register<ThemeSelectorBase, ThemeVariantScope?>(
nameof(TargetScope));
public ThemeVariantScope? TargetScope
{
get => GetValue(TargetScopeProperty);
set => SetValue(TargetScopeProperty, value);
}
static ThemeSelectorBase()
{
SelectedThemeProperty.Changed.AddClassHandler<ThemeSelectorBase, ThemeVariant?>((s, e) => s.OnSelectedThemeChanged(e));
}
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
_application = Application.Current;
_scope = this.GetLogicalAncestors().FirstOrDefault(a => a is ThemeVariantScope) as ThemeVariantScope;
}
protected virtual void OnSelectedThemeChanged(AvaloniaPropertyChangedEventArgs<ThemeVariant?> args)
{
ThemeVariant? newTheme = args.NewValue.Value;
if (newTheme is null) return;
if (TargetScope is not null)
{
TargetScope.RequestedThemeVariant = newTheme;
return;
}
if (_scope is not null)
{
_scope.RequestedThemeVariant = newTheme;
return;
}
if (_application is not null)
{
_application.RequestedThemeVariant = newTheme;
return;
}
}
}

View File

@@ -0,0 +1,45 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Controls.Primitives;
using Avalonia.Interactivity;
using Avalonia.Styling;
using Ursa.Common;
namespace Ursa.Controls;
[TemplatePart(PART_ThemeToggleButton, typeof(ToggleButton))]
public class ThemeToggleButton: ThemeSelectorBase
{
public const string PART_ThemeToggleButton = "PART_ThemeToggleButton";
/// <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)
{
base.OnAttachedToVisualTree(e);
_currentTheme = this.ActualThemeVariant;
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
EventHelper.UnregisterEvent(ToggleButton.IsCheckedChangedEvent, OnButtonCheckedChanged, _button);
_button = e.NameScope.Get<ToggleButton>(PART_ThemeToggleButton);
EventHelper.RegisterEvent(ToggleButton.IsCheckedChangedEvent, OnButtonCheckedChanged, _button);
PropertyHelper.SetValue(ToggleButton.IsCheckedProperty, _currentTheme == ThemeVariant.Light, _button);
}
private void OnButtonCheckedChanged(object sender, RoutedEventArgs e)
{
var newTheme = (sender as ToggleButton)!.IsChecked;
if (newTheme is null) return;
SelectedTheme = newTheme.Value ? ThemeVariant.Light : ThemeVariant.Dark;
}
}