diff --git a/demo/Ursa.Demo/Pages/AutoCompleteBoxDemo.axaml b/demo/Ursa.Demo/Pages/AutoCompleteBoxDemo.axaml
new file mode 100644
index 0000000..f2e11fe
--- /dev/null
+++ b/demo/Ursa.Demo/Pages/AutoCompleteBoxDemo.axaml
@@ -0,0 +1,48 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/demo/Ursa.Demo/Pages/AutoCompleteBoxDemo.axaml.cs b/demo/Ursa.Demo/Pages/AutoCompleteBoxDemo.axaml.cs
new file mode 100644
index 0000000..d66816c
--- /dev/null
+++ b/demo/Ursa.Demo/Pages/AutoCompleteBoxDemo.axaml.cs
@@ -0,0 +1,13 @@
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+
+namespace Ursa.Demo.Pages;
+
+public partial class AutoCompleteBoxDemo : UserControl
+{
+ public AutoCompleteBoxDemo()
+ {
+ InitializeComponent();
+ }
+}
\ No newline at end of file
diff --git a/demo/Ursa.Demo/ViewModels/AutoCompleteBoxDemoViewModel.cs b/demo/Ursa.Demo/ViewModels/AutoCompleteBoxDemoViewModel.cs
new file mode 100644
index 0000000..6d0e1df
--- /dev/null
+++ b/demo/Ursa.Demo/ViewModels/AutoCompleteBoxDemoViewModel.cs
@@ -0,0 +1,86 @@
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using CommunityToolkit.Mvvm.ComponentModel;
+
+namespace Ursa.Demo.ViewModels;
+
+public class AutoCompleteBoxDemoViewModel : ObservableObject
+{
+ public AutoCompleteBoxDemoViewModel()
+ {
+ States = new ObservableCollection(GetStates());
+ }
+
+ public ObservableCollection States { get; set; }
+
+ private static List GetStates()
+ {
+ return new List
+ {
+ new("Alabama", "AL", "Montgomery"),
+ new("Alaska", "AK", "Juneau"),
+ new("Arizona", "AZ", "Phoenix"),
+ new("Arkansas", "AR", "Little Rock"),
+ new("California", "CA", "Sacramento"),
+ new("Colorado", "CO", "Denver"),
+ new("Connecticut", "CT", "Hartford"),
+ new("Delaware", "DE", "Dover"),
+ new("Florida", "FL", "Tallahassee"),
+ new("Georgia", "GA", "Atlanta"),
+ new("Hawaii", "HI", "Honolulu"),
+ new("Idaho", "ID", "Boise"),
+ new("Illinois", "IL", "Springfield"),
+ new("Indiana", "IN", "Indianapolis"),
+ new("Iowa", "IA", "Des Moines"),
+ new("Kansas", "KS", "Topeka"),
+ new("Kentucky", "KY", "Frankfort"),
+ new("Louisiana", "LA", "Baton Rouge"),
+ new("Maine", "ME", "Augusta"),
+ new("Maryland", "MD", "Annapolis"),
+ new("Massachusetts", "MA", "Boston"),
+ new("Michigan", "MI", "Lansing"),
+ new("Minnesota", "MN", "St. Paul"),
+ new("Mississippi", "MS", "Jackson"),
+ new("Missouri", "MO", "Jefferson City"),
+ new("Montana", "MT", "Helena"),
+ new("Nebraska", "NE", "Lincoln"),
+ new("Nevada", "NV", "Carson City"),
+ new("New Hampshire", "NH", "Concord"),
+ new("New Jersey", "NJ", "Trenton"),
+ new("New Mexico", "NM", "Santa Fe"),
+ new("New York", "NY", "Albany"),
+ new("North Carolina", "NC", "Raleigh"),
+ new("North Dakota", "ND", "Bismarck"),
+ new("Ohio", "OH", "Columbus"),
+ new("Oklahoma", "OK", "Oklahoma City"),
+ new("Oregon", "OR", "Salem"),
+ new("Pennsylvania", "PA", "Harrisburg"),
+ new("Rhode Island", "RI", "Providence"),
+ new("South Carolina", "SC", "Columbia"),
+ new("South Dakota", "SD", "Pierre"),
+ new("Tennessee", "TN", "Nashville"),
+ new("Texas", "TX", "Austin"),
+ new("Utah", "UT", "Salt Lake City"),
+ new("Vermont", "VT", "Montpelier"),
+ new("Virginia", "VA", "Richmond"),
+ new("Washington", "WA", "Olympia"),
+ new("West Virginia", "WV", "Charleston"),
+ new("Wisconsin", "WI", "Madison"),
+ new("Wyoming", "WY", "Cheyenne")
+ };
+ }
+}
+
+public class StateData
+{
+ public StateData(string name, string abbreviation, string capital)
+ {
+ Name = name;
+ Abbreviation = abbreviation;
+ Capital = capital;
+ }
+
+ public string Name { get; private set; }
+ public string Abbreviation { get; private set; }
+ public string Capital { get; private set; }
+}
\ No newline at end of file
diff --git a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs
index 3397a9d..87a92ac 100644
--- a/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs
+++ b/demo/Ursa.Demo/ViewModels/MainViewViewModel.cs
@@ -1,4 +1,5 @@
using System;
+using Avalonia.Controls;
using CommunityToolkit.Mvvm.Messaging;
namespace Ursa.Demo.ViewModels;
@@ -26,6 +27,7 @@ public class MainViewViewModel : ViewModelBase
Content = s switch
{
MenuKeys.MenuKeyIntroduction => new IntroductionDemoViewModel(),
+ MenuKeys.MenuKeyAutoCompleteBox => new AutoCompleteBoxDemoViewModel(),
MenuKeys.MenuKeyAvatar => new AvatarDemoViewModel(),
MenuKeys.MenuKeyBadge => new BadgeDemoViewModel(),
MenuKeys.MenuKeyBanner => new BannerDemoViewModel(),
diff --git a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs
index 494489e..bf9bb6e 100644
--- a/demo/Ursa.Demo/ViewModels/MenuViewModel.cs
+++ b/demo/Ursa.Demo/ViewModels/MenuViewModel.cs
@@ -12,6 +12,7 @@ public class MenuViewModel: ViewModelBase
{
new() { MenuHeader = "Introduction", Key = MenuKeys.MenuKeyIntroduction, IsSeparator = false },
new() { MenuHeader = "Controls", IsSeparator = true },
+ new() { MenuHeader = "AutoCompleteBox", Key = MenuKeys.MenuKeyAutoCompleteBox, Status = "WIP" },
new() { MenuHeader = "Avatar", Key = MenuKeys.MenuKeyAvatar, Status = "WIP"},
new() { MenuHeader = "Badge", Key = MenuKeys.MenuKeyBadge },
new() { MenuHeader = "Banner", Key = MenuKeys.MenuKeyBanner },
@@ -61,6 +62,7 @@ public class MenuViewModel: ViewModelBase
public static class MenuKeys
{
public const string MenuKeyIntroduction = "Introduction";
+ public const string MenuKeyAutoCompleteBox = "AutoCompleteBox";
public const string MenuKeyAvatar = "Avatar";
public const string MenuKeyBadge = "Badge";
public const string MenuKeyBanner = "Banner";
diff --git a/src/Ursa.Themes.Semi/Behaviors/ClassHelper.cs b/src/Ursa.Themes.Semi/Behaviors/ClassHelper.cs
index 04b7c36..ade597c 100644
--- a/src/Ursa.Themes.Semi/Behaviors/ClassHelper.cs
+++ b/src/Ursa.Themes.Semi/Behaviors/ClassHelper.cs
@@ -1,25 +1,69 @@
-using Avalonia;
+using System.Collections.Specialized;
+using Avalonia;
+using Avalonia.Collections;
namespace Ursa.Themes.Semi;
-internal class ClassHelper: AvaloniaObject
+internal class ClassHelper : AvaloniaObject
{
- static ClassHelper()
- {
- ClassesProperty.Changed.AddClassHandler(OnClassesChanged);
- }
-
public static readonly AttachedProperty ClassesProperty =
AvaloniaProperty.RegisterAttached("Classes");
- public static void SetClasses(AvaloniaObject obj, string value) => obj.SetValue(ClassesProperty, value);
- public static string GetClasses(AvaloniaObject obj) => obj.GetValue(ClassesProperty);
-
+ public static readonly AttachedProperty ClassSourceProperty =
+ AvaloniaProperty.RegisterAttached("ClassSource");
+
+ static ClassHelper()
+ {
+ ClassesProperty.Changed.AddClassHandler(OnClassesChanged);
+ ClassSourceProperty.Changed.AddClassHandler(OnClassSourceChanged);
+ }
+
+ private static void OnClassSourceChanged(StyledElement arg1, AvaloniaPropertyChangedEventArgs arg2)
+ {
+ if (arg2.NewValue is StyledElement styledElement)
+ {
+ arg1.Classes.Clear();
+ var nonPseudoClasses = styledElement.Classes.Where(c => !c.StartsWith(":"));
+ arg1.Classes.AddRange(nonPseudoClasses);
+ styledElement.Classes.WeakSubscribe((o, e) => OnSourceClassesChanged(o, e, arg1));
+ }
+ }
+
+ private static void OnSourceClassesChanged(object sender, NotifyCollectionChangedEventArgs e, StyledElement target)
+ {
+ if (sender is AvaloniaList classes)
+ {
+ target.Classes.Clear();
+ var nonPseudoClasses = classes.Where(c => !c.StartsWith(":"));
+ target.Classes.AddRange(nonPseudoClasses);
+ }
+ }
+
+ public static void SetClasses(AvaloniaObject obj, string value)
+ {
+ obj.SetValue(ClassesProperty, value);
+ }
+
+ public static string GetClasses(AvaloniaObject obj)
+ {
+ return obj.GetValue(ClassesProperty);
+ }
+
private static void OnClassesChanged(StyledElement sender, AvaloniaPropertyChangedEventArgs value)
{
- string? classes = value.GetNewValue();
+ var classes = value.GetNewValue();
if (classes is null) return;
sender.Classes.Clear();
sender.Classes.Add(classes);
}
+
+ public static void SetClassSource(StyledElement obj, StyledElement value)
+ {
+ obj.SetValue(ClassSourceProperty, value);
+ }
+
+ public static StyledElement GetClassSource(StyledElement obj)
+ {
+ return obj.GetValue(ClassSourceProperty);
+ }
}
\ No newline at end of file
diff --git a/src/Ursa.Themes.Semi/Controls/AutoCompleteBox.axaml b/src/Ursa.Themes.Semi/Controls/AutoCompleteBox.axaml
new file mode 100644
index 0000000..30ea961
--- /dev/null
+++ b/src/Ursa.Themes.Semi/Controls/AutoCompleteBox.axaml
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Ursa.Themes.Semi/Controls/_index.axaml b/src/Ursa.Themes.Semi/Controls/_index.axaml
index 6ab88a9..36e75e7 100644
--- a/src/Ursa.Themes.Semi/Controls/_index.axaml
+++ b/src/Ursa.Themes.Semi/Controls/_index.axaml
@@ -1,6 +1,7 @@
+
diff --git a/src/Ursa/Controls/AutoCompleteBox/AutoCompleteBox.cs b/src/Ursa/Controls/AutoCompleteBox/AutoCompleteBox.cs
new file mode 100644
index 0000000..0f496a2
--- /dev/null
+++ b/src/Ursa/Controls/AutoCompleteBox/AutoCompleteBox.cs
@@ -0,0 +1,47 @@
+using Avalonia.Controls;
+using Avalonia.Controls.Primitives;
+using Avalonia.Input;
+using Avalonia.Interactivity;
+using Irihi.Avalonia.Shared.Contracts;
+
+namespace Ursa.Controls;
+
+public class AutoCompleteBox: Avalonia.Controls.AutoCompleteBox, IClearControl
+{
+ // protected override Type StyleKeyOverride { get; } = typeof(Avalonia.Controls.AutoCompleteBox);
+ private TextBox? _text;
+ static AutoCompleteBox()
+ {
+ MinimumPrefixLengthProperty.OverrideDefaultValue(0);
+ }
+
+ public AutoCompleteBox()
+ {
+ this.AddHandler(PointerPressedEvent, OnBoxPointerPressed, RoutingStrategies.Tunnel);
+ }
+
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
+ {
+ base.OnApplyTemplate(e);
+ _text = e.NameScope.Get("PART_TextBox");
+ }
+
+ private void OnBoxPointerPressed(object? sender, PointerPressedEventArgs e)
+ {
+ if (Equals(sender, this) && e.GetCurrentPoint(this).Properties.IsLeftButtonPressed)
+ {
+ SetCurrentValue(IsDropDownOpenProperty, true);
+ }
+ }
+
+ protected override void OnGotFocus(GotFocusEventArgs e)
+ {
+ base.OnGotFocus(e);
+ SetCurrentValue(IsDropDownOpenProperty, true);
+ }
+
+ public void Clear()
+ {
+ SetCurrentValue(SelectedItemProperty, null);
+ }
+}
\ No newline at end of file