From 5f35f574a97b8a8a0905bc154ae6c1b5e5c41ff0 Mon Sep 17 00:00:00 2001 From: rabbitism Date: Tue, 6 Feb 2024 19:57:11 +0800 Subject: [PATCH] feat: Theme toggler --- demo/Ursa.Demo/Views/MainView.axaml | 7 +- .../Controls/ThemeSelector.axaml | 30 +++++++++ src/Ursa.Themes.Semi/Controls/_index.axaml | 1 + .../Themes/Shared/ThemeSelector.axaml | 6 ++ .../Themes/Shared/_index.axaml | 1 + src/Ursa/Common/EventHelper.cs | 18 +++++ src/Ursa/Common/PropertyHelper.cs | 14 ++++ .../Controls/{ => Buttons}/ButtonGroup.cs | 0 src/Ursa/Controls/{ => Buttons}/IconButton.cs | 0 .../ThemeSelector/ThemeSelectorBase.cs | 65 +++++++++++++++++++ .../ThemeSelector/ThemeToggleButton.cs | 45 +++++++++++++ 11 files changed, 182 insertions(+), 5 deletions(-) create mode 100644 src/Ursa.Themes.Semi/Controls/ThemeSelector.axaml create mode 100644 src/Ursa.Themes.Semi/Themes/Shared/ThemeSelector.axaml create mode 100644 src/Ursa/Common/PropertyHelper.cs rename src/Ursa/Controls/{ => Buttons}/ButtonGroup.cs (100%) rename src/Ursa/Controls/{ => Buttons}/IconButton.cs (100%) create mode 100644 src/Ursa/Controls/ThemeSelector/ThemeSelectorBase.cs create mode 100644 src/Ursa/Controls/ThemeSelector/ThemeToggleButton.cs diff --git a/demo/Ursa.Demo/Views/MainView.axaml b/demo/Ursa.Demo/Views/MainView.axaml index 375bf68..31d58a1 100644 --- a/demo/Ursa.Demo/Views/MainView.axaml +++ b/demo/Ursa.Demo/Views/MainView.axaml @@ -77,13 +77,10 @@ - - + HorizontalAlignment="Right"/> + + + + + + + + + + + + + + + + diff --git a/src/Ursa.Themes.Semi/Controls/_index.axaml b/src/Ursa.Themes.Semi/Controls/_index.axaml index 920052e..f770a28 100644 --- a/src/Ursa.Themes.Semi/Controls/_index.axaml +++ b/src/Ursa.Themes.Semi/Controls/_index.axaml @@ -22,6 +22,7 @@ + diff --git a/src/Ursa.Themes.Semi/Themes/Shared/ThemeSelector.axaml b/src/Ursa.Themes.Semi/Themes/Shared/ThemeSelector.axaml new file mode 100644 index 0000000..7ba95b8 --- /dev/null +++ b/src/Ursa.Themes.Semi/Themes/Shared/ThemeSelector.axaml @@ -0,0 +1,6 @@ + + + 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 + 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 + diff --git a/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml b/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml index af198fa..d06590f 100644 --- a/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml +++ b/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml @@ -14,5 +14,6 @@ + diff --git a/src/Ursa/Common/EventHelper.cs b/src/Ursa/Common/EventHelper.cs index 3d9342d..4b1dd09 100644 --- a/src/Ursa/Common/EventHelper.cs +++ b/src/Ursa/Common/EventHelper.cs @@ -20,4 +20,22 @@ internal static class EventHelper if(button is not null) button.Click -= handler; } } + + public static void RegisterEvent(RoutedEvent routedEvent, EventHandler handler, params Button?[] controls) + where TArgs : RoutedEventArgs + { + foreach (var control in controls) + { + control?.AddHandler(routedEvent, handler); + } + } + + public static void UnregisterEvent(RoutedEvent routedEvent, EventHandler handler, params Button?[] controls) + where TArgs : RoutedEventArgs + { + foreach (var control in controls) + { + control?.RemoveHandler(routedEvent, handler); + } + } } \ No newline at end of file diff --git a/src/Ursa/Common/PropertyHelper.cs b/src/Ursa/Common/PropertyHelper.cs new file mode 100644 index 0000000..c673e8b --- /dev/null +++ b/src/Ursa/Common/PropertyHelper.cs @@ -0,0 +1,14 @@ +using Avalonia; + +namespace Ursa.Common; + +public static class PropertyHelper +{ + public static void SetValue(AvaloniaProperty property, TValue value, params AvaloniaObject?[] elements) + { + foreach (var element in elements) + { + element?.SetValue(property, value); + } + } +} \ No newline at end of file diff --git a/src/Ursa/Controls/ButtonGroup.cs b/src/Ursa/Controls/Buttons/ButtonGroup.cs similarity index 100% rename from src/Ursa/Controls/ButtonGroup.cs rename to src/Ursa/Controls/Buttons/ButtonGroup.cs diff --git a/src/Ursa/Controls/IconButton.cs b/src/Ursa/Controls/Buttons/IconButton.cs similarity index 100% rename from src/Ursa/Controls/IconButton.cs rename to src/Ursa/Controls/Buttons/IconButton.cs diff --git a/src/Ursa/Controls/ThemeSelector/ThemeSelectorBase.cs b/src/Ursa/Controls/ThemeSelector/ThemeSelectorBase.cs new file mode 100644 index 0000000..20f240a --- /dev/null +++ b/src/Ursa/Controls/ThemeSelector/ThemeSelectorBase.cs @@ -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 SelectedThemeProperty = AvaloniaProperty.Register( + nameof(SelectedTheme)); + + public ThemeVariant? SelectedTheme + { + get => GetValue(SelectedThemeProperty); + set => SetValue(SelectedThemeProperty, value); + } + + public static readonly StyledProperty TargetScopeProperty = + AvaloniaProperty.Register( + nameof(TargetScope)); + + public ThemeVariantScope? TargetScope + { + get => GetValue(TargetScopeProperty); + set => SetValue(TargetScopeProperty, value); + } + + static ThemeSelectorBase() + { + SelectedThemeProperty.Changed.AddClassHandler((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 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; + } + } +} \ No newline at end of file diff --git a/src/Ursa/Controls/ThemeSelector/ThemeToggleButton.cs b/src/Ursa/Controls/ThemeSelector/ThemeToggleButton.cs new file mode 100644 index 0000000..80cead7 --- /dev/null +++ b/src/Ursa/Controls/ThemeSelector/ThemeToggleButton.cs @@ -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"; + + /// + /// This button IsChecked=true means ThemeVariant.Light, IsChecked=false means ThemeVariant.Dark. + /// + 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(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; + } + + +} \ No newline at end of file