From 38e2913cabdae51844b5dad57bdeca274ffd6320 Mon Sep 17 00:00:00 2001 From: TheXamlGuy Date: Wed, 10 Jul 2024 14:36:09 +0100 Subject: [PATCH] wip --- Toolkit.UI.Avalonia/ConditionalExpression.cs | 21 ---- Toolkit.UI.Avalonia/NamedTypeConverter.cs | 23 ++++ .../Overflow.axaml} | 8 +- .../Overflow.cs} | 105 +++++++++++++++--- .../Overflow/OverflowTemplateSettings.cs | 49 ++++++++ .../Themes/ControlResources.axaml | 2 +- 6 files changed, 167 insertions(+), 41 deletions(-) create mode 100644 Toolkit.UI.Avalonia/NamedTypeConverter.cs rename Toolkit.UI.Controls.Avalonia/{OverflowListBox/OverflowListBox.axaml => Overflow/Overflow.axaml} (69%) rename Toolkit.UI.Controls.Avalonia/{OverflowListBox/OverflowListBox.cs => Overflow/Overflow.cs} (67%) create mode 100644 Toolkit.UI.Controls.Avalonia/Overflow/OverflowTemplateSettings.cs diff --git a/Toolkit.UI.Avalonia/ConditionalExpression.cs b/Toolkit.UI.Avalonia/ConditionalExpression.cs index da7223e..5a5f38f 100644 --- a/Toolkit.UI.Avalonia/ConditionalExpression.cs +++ b/Toolkit.UI.Avalonia/ConditionalExpression.cs @@ -1,28 +1,7 @@ using Avalonia; -using Avalonia.Data.Converters; -using Avalonia.Markup.Xaml; using Avalonia.Metadata; -using System.Globalization; namespace Toolkit.UI.Avalonia; - -public class NamedTypeConverter : - MarkupExtension, - IValueConverter -{ - public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) - { - var d = value is not null ? value.GetType().Name : (object?)null; - return d; - } - - public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) - { - throw new NotImplementedException(); - } - - public override object ProvideValue(IServiceProvider serviceProvider) => this; -} public class ConditionalExpression : AvaloniaObject, ICondition diff --git a/Toolkit.UI.Avalonia/NamedTypeConverter.cs b/Toolkit.UI.Avalonia/NamedTypeConverter.cs new file mode 100644 index 0000000..4b6213a --- /dev/null +++ b/Toolkit.UI.Avalonia/NamedTypeConverter.cs @@ -0,0 +1,23 @@ +using Avalonia.Data.Converters; +using Avalonia.Markup.Xaml; +using System.Globalization; + +namespace Toolkit.UI.Avalonia; + +public class NamedTypeConverter : + MarkupExtension, + IValueConverter +{ + public object? Convert(object? value, Type targetType, object? parameter, CultureInfo culture) + { + var d = value is not null ? value.GetType().Name : (object?)null; + return d; + } + + public object? ConvertBack(object? value, Type targetType, object? parameter, CultureInfo culture) + { + throw new NotImplementedException(); + } + + public override object ProvideValue(IServiceProvider serviceProvider) => this; +} diff --git a/Toolkit.UI.Controls.Avalonia/OverflowListBox/OverflowListBox.axaml b/Toolkit.UI.Controls.Avalonia/Overflow/Overflow.axaml similarity index 69% rename from Toolkit.UI.Controls.Avalonia/OverflowListBox/OverflowListBox.axaml rename to Toolkit.UI.Controls.Avalonia/Overflow/Overflow.axaml index 8087360..0ce51ca 100644 --- a/Toolkit.UI.Controls.Avalonia/OverflowListBox/OverflowListBox.axaml +++ b/Toolkit.UI.Controls.Avalonia/Overflow/Overflow.axaml @@ -2,7 +2,7 @@ xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:controls="using:Toolkit.UI.Controls.Avalonia"> - + @@ -11,14 +11,14 @@ Grid.Column="0" ItemTemplate="{TemplateBinding ItemTemplate}" ItemsPanel="{TemplateBinding ItemsPanel}" - SelectedItem="{TemplateBinding SelectedItem}" /> - diff --git a/Toolkit.UI.Controls.Avalonia/OverflowListBox/OverflowListBox.cs b/Toolkit.UI.Controls.Avalonia/Overflow/Overflow.cs similarity index 67% rename from Toolkit.UI.Controls.Avalonia/OverflowListBox/OverflowListBox.cs rename to Toolkit.UI.Controls.Avalonia/Overflow/Overflow.cs index 7415192..bde03d4 100644 --- a/Toolkit.UI.Controls.Avalonia/OverflowListBox/OverflowListBox.cs +++ b/Toolkit.UI.Controls.Avalonia/Overflow/Overflow.cs @@ -2,36 +2,66 @@ using Avalonia.Controls; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; -using Avalonia.Data; using Avalonia.Metadata; using Avalonia.Threading; +using FluentAvalonia.Core; using System.Collections; using System.Collections.ObjectModel; using System.Collections.Specialized; namespace Toolkit.UI.Controls.Avalonia; -public class OverflowListBox : +public class Overflow : TemplatedControl { public static readonly StyledProperty> ItemsPanelProperty = - AvaloniaProperty.Register>(nameof(ItemsPanel), new FuncTemplate(() => new StackPanel())); + AvaloniaProperty.Register>(nameof(ItemsPanel), new FuncTemplate(() => new StackPanel())); public static readonly StyledProperty ItemsSourceProperty = - AvaloniaProperty.Register(nameof(ItemsSource)); + AvaloniaProperty.Register(nameof(ItemsSource)); public static readonly StyledProperty ItemTemplateProperty = - AvaloniaProperty.Register(nameof(ItemTemplate)); + AvaloniaProperty.Register(nameof(ItemTemplate)); public static readonly StyledProperty SelectedItemProperty = - AvaloniaProperty.Register(nameof(SelectedItem), BindingMode.TwoWay); + AvaloniaProperty.Register(nameof(SelectedItem)); - private readonly ObservableCollection primaryCollection = new(); - private readonly ObservableCollection secondaryCollection = new(); + private static readonly StyledProperty TemplateSettingsProperty = + AvaloniaProperty.Register(nameof(TemplateSettings)); + + private readonly ObservableCollection primaryCollection = []; + + private readonly ObservableCollection secondaryCollection = []; private ListBox? primaryListBox; + private ListBox? secondaryListBox; + public Overflow() + { + SetValue(TemplateSettingsProperty, new OverflowTemplateSettings()); + + TemplateSettings.GetPropertyChangedObservable(OverflowTemplateSettings.PrimarySelectionProperty) + .AddClassHandler(OnPrimarySelectionPropertyChanged); + + TemplateSettings.GetPropertyChangedObservable(OverflowTemplateSettings.SecondarySelectionProperty) + .AddClassHandler(OnSecondarySelectionPropertyChanged); + } + + private void OnPrimarySelectionPropertyChanged(OverflowTemplateSettings sender, + AvaloniaPropertyChangedEventArgs args) + { + object? selection = args.GetNewValue(); + SetValue(SelectedItemProperty, selection); + } + + private void OnSecondarySelectionPropertyChanged(OverflowTemplateSettings sender, + AvaloniaPropertyChangedEventArgs args) + { + object? selection = args.GetNewValue(); + SetValue(SelectedItemProperty, selection); + } + public ITemplate ItemsPanel { get => GetValue(ItemsPanelProperty); @@ -57,15 +87,26 @@ public class OverflowListBox : set => SetValue(SelectedItemProperty, value); } + public OverflowTemplateSettings TemplateSettings + { + get => GetValue(TemplateSettingsProperty); + set => SetValue(TemplateSettingsProperty, value); + } protected override void OnApplyTemplate(TemplateAppliedEventArgs args) { base.OnApplyTemplate(args); primaryListBox = args.NameScope.Get("PrimaryListBox"); - primaryListBox?.SetValue(ItemsControl.ItemsSourceProperty, primaryCollection); + if (primaryListBox is not null) + { + primaryListBox.SetValue(ItemsControl.ItemsSourceProperty, primaryCollection); + } secondaryListBox = args.NameScope.Get("SecondaryListBox"); - secondaryListBox?.SetValue(ItemsControl.ItemsSourceProperty, secondaryCollection); + if (secondaryListBox is not null) + { + secondaryListBox.SetValue(ItemsControl.ItemsSourceProperty, secondaryCollection); + } InitializeCollections(); UpdateOverflow(); @@ -74,6 +115,12 @@ public class OverflowListBox : protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs args) { base.OnPropertyChanged(args); + + if (args.Property == SelectedItemProperty) + { + UpdateSelectedItem(); + } + if (args.Property == ItemsSourceProperty) { if (args.OldValue is IEnumerable oldCollection && oldCollection is INotifyCollectionChanged oldNotifyCollectionChanged) @@ -105,7 +152,8 @@ public class OverflowListBox : } } - private void OnSourceCollectionChanged(object? sender, NotifyCollectionChangedEventArgs args) + private void OnSourceCollectionChanged(object? sender, + NotifyCollectionChangedEventArgs args) { switch (args.Action) { @@ -205,7 +253,7 @@ public class OverflowListBox : double accumulatedWidth = 0; double itemSpacing = 6; - List<(object item, int originalIndex)> itemsToMoveToSecondary = new(); + List itemsToMoveToSecondary = []; for (int i = 0; i < primaryCollection.Count; i++) { @@ -217,7 +265,7 @@ public class OverflowListBox : if (accumulatedWidth + itemWidth + (itemsToMoveToSecondary.Count * itemSpacing) > controlWidth) { - itemsToMoveToSecondary.Add((item, i)); + itemsToMoveToSecondary.Add(item); } else { @@ -226,14 +274,41 @@ public class OverflowListBox : } } - foreach (var (item, originalIndex) in itemsToMoveToSecondary.OrderByDescending(x => x.originalIndex)) + foreach (object item in itemsToMoveToSecondary) { primaryCollection.Remove(item); - int insertIndexInSecondary = originalIndex - primaryCollection.Count; + + int insertIndexInSecondary = secondaryCollection.Count; + if (ItemsSource.Contains(item)) + { + int indexInItemsSource = ItemsSource.IndexOf(item); + insertIndexInSecondary = Math.Min(indexInItemsSource, secondaryCollection.Count); + } + secondaryCollection.Insert(insertIndexInSecondary, item); } }); } + private void UpdateSelectedItem() + { + if (SelectedItem is not null) + { + if (primaryCollection.Contains(SelectedItem)) + { + TemplateSettings.SetValue(OverflowTemplateSettings.PrimarySelectionProperty, SelectedItem); + } + + if (secondaryCollection.Contains(SelectedItem)) + { + TemplateSettings.SetValue(OverflowTemplateSettings.SecondarySelectionProperty, SelectedItem); + } + } + else + { + TemplateSettings.SetValue(OverflowTemplateSettings.PrimarySelectionProperty, null); + TemplateSettings.SetValue(OverflowTemplateSettings.SecondarySelectionProperty, null); + } + } } diff --git a/Toolkit.UI.Controls.Avalonia/Overflow/OverflowTemplateSettings.cs b/Toolkit.UI.Controls.Avalonia/Overflow/OverflowTemplateSettings.cs new file mode 100644 index 0000000..26a0640 --- /dev/null +++ b/Toolkit.UI.Controls.Avalonia/Overflow/OverflowTemplateSettings.cs @@ -0,0 +1,49 @@ +using Avalonia; + +namespace Toolkit.UI.Controls.Avalonia; + +public class OverflowTemplateSettings : + AvaloniaObject +{ + public static readonly StyledProperty PrimarySelectionProperty = + AvaloniaProperty.Register(nameof(PrimarySelection)); + + public static readonly StyledProperty SecondarySelectionProperty = + AvaloniaProperty.Register(nameof(SecondarySelection)); + + private bool isSelectionChanging; + + protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs args) + { + base.OnPropertyChanged(args); + + if (!isSelectionChanging) + { + isSelectionChanging = true; + + if (args.Property == PrimarySelectionProperty) + { + SecondarySelection = null; + } + else if (args.Property == SecondarySelectionProperty) + { + PrimarySelection = null; + } + + isSelectionChanging = false; + } + } + + public object? PrimarySelection + { + get => GetValue(PrimarySelectionProperty); + set => SetValue(PrimarySelectionProperty, value); + } + + public object? SecondarySelection + { + get => GetValue(SecondarySelectionProperty); + set => SetValue(SecondarySelectionProperty, value); + } +} + diff --git a/Toolkit.UI.Controls.Avalonia/Themes/ControlResources.axaml b/Toolkit.UI.Controls.Avalonia/Themes/ControlResources.axaml index 76bc8a4..196bb1a 100644 --- a/Toolkit.UI.Controls.Avalonia/Themes/ControlResources.axaml +++ b/Toolkit.UI.Controls.Avalonia/Themes/ControlResources.axaml @@ -6,7 +6,7 @@ - +