Files
Toolkit2/Toolkit.UI.Controls.Avalonia/Overflow/Overflow.cs
T
2024-07-18 18:00:01 +01:00

319 lines
11 KiB
C#

using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates;
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 Overflow :
TemplatedControl
{
public static readonly StyledProperty<IItemContainerTemplateSelector?> ItemContainerTemplateSelectorProperty =
AvaloniaProperty.Register<Overflow, IItemContainerTemplateSelector?>(nameof(ItemContainerTemplateSelector));
public static readonly StyledProperty<ITemplate<Panel?>> ItemsPanelProperty =
AvaloniaProperty.Register<Overflow, ITemplate<Panel?>>(nameof(ItemsPanel), new FuncTemplate<Panel?>(() => new StackPanel()));
public static readonly StyledProperty<IEnumerable?> ItemsSourceProperty =
AvaloniaProperty.Register<Overflow, IEnumerable?>(nameof(ItemsSource));
public static readonly StyledProperty<IDataTemplate?> ItemTemplateProperty =
AvaloniaProperty.Register<Overflow, IDataTemplate?>(nameof(ItemTemplate));
public static readonly StyledProperty<object?> SelectedItemProperty =
AvaloniaProperty.Register<Overflow, object?>(nameof(SelectedItem));
private static readonly StyledProperty<OverflowTemplateSettings> TemplateSettingsProperty =
AvaloniaProperty.Register<Overflow, OverflowTemplateSettings>(nameof(TemplateSettings));
private readonly ObservableCollection<object> primaryCollection = [];
private readonly ObservableCollection<object> secondaryCollection = [];
private OverflowList? primaryListBox;
private OverflowList? secondaryListBox;
public Overflow()
{
SetValue(TemplateSettingsProperty, new OverflowTemplateSettings());
TemplateSettings.GetPropertyChangedObservable(OverflowTemplateSettings.PrimarySelectionProperty)
.AddClassHandler<OverflowTemplateSettings>(OnPrimarySelectionPropertyChanged);
TemplateSettings.GetPropertyChangedObservable(OverflowTemplateSettings.SecondarySelectionProperty)
.AddClassHandler<OverflowTemplateSettings>(OnSecondarySelectionPropertyChanged);
}
public IItemContainerTemplateSelector? ItemContainerTemplateSelector
{
get => GetValue(ItemContainerTemplateSelectorProperty);
set => SetValue(ItemContainerTemplateSelectorProperty, value);
}
public ITemplate<Panel?> ItemsPanel
{
get => GetValue(ItemsPanelProperty);
set => SetValue(ItemsPanelProperty, value);
}
public IEnumerable? ItemsSource
{
get => GetValue(ItemsSourceProperty);
set => SetValue(ItemsSourceProperty, value);
}
[InheritDataTypeFromItems(nameof(ItemsSource))]
public IDataTemplate? ItemTemplate
{
get => GetValue(ItemTemplateProperty);
set => SetValue(ItemTemplateProperty, value);
}
public object? SelectedItem
{
get => GetValue(SelectedItemProperty);
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<OverflowList>("PrimaryListBox");
primaryListBox?.SetValue(ItemsControl.ItemsSourceProperty, primaryCollection);
secondaryListBox = args.NameScope.Get<OverflowList>("SecondaryListBox");
secondaryListBox?.SetValue(ItemsControl.ItemsSourceProperty, secondaryCollection);
InitializeCollections();
UpdateOverflow();
}
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)
{
oldNotifyCollectionChanged.CollectionChanged -= OnSourceCollectionChanged;
}
if (args.NewValue is IEnumerable newCollection && newCollection is INotifyCollectionChanged notifyCollectionChanged)
{
notifyCollectionChanged.CollectionChanged += OnSourceCollectionChanged;
}
InitializeCollections();
UpdateOverflow();
}
}
private void InitializeCollections()
{
primaryCollection.Clear();
secondaryCollection.Clear();
if (ItemsSource is not null)
{
foreach (object? item in ItemsSource)
{
primaryCollection.Add(item);
}
}
}
private void OnPrimarySelectionPropertyChanged(OverflowTemplateSettings sender,
AvaloniaPropertyChangedEventArgs args)
{
object? selection = args.GetNewValue<object>();
SetValue(SelectedItemProperty, selection);
}
private void OnSecondarySelectionPropertyChanged(OverflowTemplateSettings sender,
AvaloniaPropertyChangedEventArgs args)
{
object? selection = args.GetNewValue<object>();
SetValue(SelectedItemProperty, selection);
}
private void OnSourceCollectionChanged(object? sender,
NotifyCollectionChangedEventArgs args)
{
switch (args.Action)
{
case NotifyCollectionChangedAction.Add:
if (args.NewItems is not null)
{
int insertIndex = args.NewStartingIndex > primaryCollection.Count ? primaryCollection.Count : args.NewStartingIndex;
foreach (object? newItem in args.NewItems)
{
primaryCollection.Insert(insertIndex++, newItem);
}
}
break;
case NotifyCollectionChangedAction.Remove:
if (args.OldItems is not null)
{
foreach (object? oldItem in args.OldItems)
{
primaryCollection.Remove(oldItem);
secondaryCollection.Remove(oldItem);
}
}
break;
case NotifyCollectionChangedAction.Replace:
if (args.OldItems is not null && args.NewItems is not null && args.OldItems.Count == args.NewItems.Count)
{
for (int i = 0; i < args.OldItems.Count; i++)
{
if (args.OldItems[i] is object oldItem &&
args.NewItems[i] is object newItem)
{
int index = primaryCollection.IndexOf(oldItem);
if (index != -1)
{
primaryCollection[index] = newItem;
}
index = secondaryCollection.IndexOf(oldItem);
if (index != -1)
{
secondaryCollection[index] = newItem;
}
}
}
}
break;
case NotifyCollectionChangedAction.Move:
if (args.OldItems != null && args.NewItems != null && args.OldItems.Count == args.NewItems.Count)
{
for (int i = 0; i < args.OldItems.Count; i++)
{
if (args.OldItems[i] is object item)
{
int oldIndex = primaryCollection.IndexOf(item);
if (oldIndex != -1)
{
primaryCollection.RemoveAt(oldIndex);
int newIndex = args.NewStartingIndex + i;
primaryCollection.Insert(newIndex, item);
}
oldIndex = secondaryCollection.IndexOf(item);
if (oldIndex != -1)
{
secondaryCollection.RemoveAt(oldIndex);
int newIndex = args.NewStartingIndex + i;
secondaryCollection.Insert(newIndex, item);
}
}
}
}
break;
case NotifyCollectionChangedAction.Reset:
InitializeCollections();
break;
}
UpdateOverflow();
}
private void UpdateOverflow()
{
Dispatcher.UIThread.InvokeAsync(() =>
{
if (ItemsSource is null)
{
return;
}
double controlWidth = 240;
double accumulatedWidth = 0;
double itemSpacing = 6;
List<object> itemsToMoveToSecondary = [];
for (int i = 0; i < primaryCollection.Count; i++)
{
object? item = primaryCollection[i];
if (item is not null && primaryListBox?.ContainerFromItem(item) is ListBoxItem itemContainer)
{
itemContainer.Measure(new Size(double.PositiveInfinity, double.PositiveInfinity));
double itemWidth = itemContainer.DesiredSize.Width;
if (accumulatedWidth + itemWidth + (itemsToMoveToSecondary.Count * itemSpacing) > controlWidth)
{
itemsToMoveToSecondary.Add(item);
}
else
{
accumulatedWidth += itemWidth + itemSpacing;
}
}
}
foreach (object item in itemsToMoveToSecondary)
{
primaryCollection.Remove(item);
int insertIndexInSecondary = secondaryCollection.Count;
if (ItemsSource.Contains(item))
{
int indexInItemsSource = ItemsSource.IndexOf(item);
insertIndexInSecondary = Math.Min(indexInItemsSource, secondaryCollection.Count);
}
secondaryCollection.Insert(insertIndexInSecondary, item);
}
PseudoClasses.Set(":overflow", secondaryCollection is { Count: > 0 });
});
}
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);
}
}
}