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