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