This commit is contained in:
TheXamlGuy
2024-07-10 14:36:09 +01:00
parent c5b330d041
commit 38e2913cab
6 changed files with 167 additions and 41 deletions
@@ -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
+23
View File
@@ -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;
}
@@ -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">
<ControlTheme x:Key="{x:Type controls:OverflowListBox}" TargetType="controls:OverflowListBox">
<ControlTheme x:Key="{x:Type controls:Overflow}" TargetType="controls:Overflow">
<Setter Property="Template">
<ControlTemplate>
<Grid Margin="{TemplateBinding Margin}" ColumnDefinitions="*,Auto">
@@ -11,14 +11,14 @@
Grid.Column="0"
ItemTemplate="{TemplateBinding ItemTemplate}"
ItemsPanel="{TemplateBinding ItemsPanel}"
SelectedItem="{TemplateBinding SelectedItem}" />
<Button Grid.Column="1">
SelectedItem="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.PrimarySelection, Mode=TwoWay}" />
<Button Grid.Column="1" Focusable="False">
<Button.Flyout>
<Flyout>
<ListBox
x:Name="SecondaryListBox"
ItemTemplate="{TemplateBinding ItemTemplate}"
SelectedItem="{TemplateBinding SelectedItem}" />
SelectedItem="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.SecondarySelection, Mode=TwoWay}" />
</Flyout>
</Button.Flyout>
</Button>
@@ -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<ITemplate<Panel?>> ItemsPanelProperty =
AvaloniaProperty.Register<OverflowListBox, ITemplate<Panel?>>(nameof(ItemsPanel), new FuncTemplate<Panel?>(() => new StackPanel()));
AvaloniaProperty.Register<Overflow, ITemplate<Panel?>>(nameof(ItemsPanel), new FuncTemplate<Panel?>(() => new StackPanel()));
public static readonly StyledProperty<IEnumerable?> ItemsSourceProperty =
AvaloniaProperty.Register<OverflowListBox, IEnumerable?>(nameof(ItemsSource));
AvaloniaProperty.Register<Overflow, IEnumerable?>(nameof(ItemsSource));
public static readonly StyledProperty<IDataTemplate?> ItemTemplateProperty =
AvaloniaProperty.Register<OverflowListBox, IDataTemplate?>(nameof(ItemTemplate));
AvaloniaProperty.Register<Overflow, IDataTemplate?>(nameof(ItemTemplate));
public static readonly StyledProperty<object?> SelectedItemProperty =
AvaloniaProperty.Register<OverflowListBox, object?>(nameof(SelectedItem), BindingMode.TwoWay);
AvaloniaProperty.Register<Overflow, object?>(nameof(SelectedItem));
private readonly ObservableCollection<object> primaryCollection = new();
private readonly ObservableCollection<object> secondaryCollection = new();
private static readonly StyledProperty<OverflowTemplateSettings> TemplateSettingsProperty =
AvaloniaProperty.Register<Overflow, OverflowTemplateSettings>(nameof(TemplateSettings));
private readonly ObservableCollection<object> primaryCollection = [];
private readonly ObservableCollection<object> secondaryCollection = [];
private ListBox? primaryListBox;
private ListBox? secondaryListBox;
public Overflow()
{
SetValue(TemplateSettingsProperty, new OverflowTemplateSettings());
TemplateSettings.GetPropertyChangedObservable(OverflowTemplateSettings.PrimarySelectionProperty)
.AddClassHandler<OverflowTemplateSettings>(OnPrimarySelectionPropertyChanged);
TemplateSettings.GetPropertyChangedObservable(OverflowTemplateSettings.SecondarySelectionProperty)
.AddClassHandler<OverflowTemplateSettings>(OnSecondarySelectionPropertyChanged);
}
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);
}
public ITemplate<Panel?> 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<ListBox>("PrimaryListBox");
primaryListBox?.SetValue(ItemsControl.ItemsSourceProperty, primaryCollection);
if (primaryListBox is not null)
{
primaryListBox.SetValue(ItemsControl.ItemsSourceProperty, primaryCollection);
}
secondaryListBox = args.NameScope.Get<ListBox>("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<object> 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);
}
}
}
@@ -0,0 +1,49 @@
using Avalonia;
namespace Toolkit.UI.Controls.Avalonia;
public class OverflowTemplateSettings :
AvaloniaObject
{
public static readonly StyledProperty<object?> PrimarySelectionProperty =
AvaloniaProperty.Register<OverflowTemplateSettings, object?>(nameof(PrimarySelection));
public static readonly StyledProperty<object?> SecondarySelectionProperty =
AvaloniaProperty.Register<OverflowTemplateSettings, object?>(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);
}
}
@@ -6,7 +6,7 @@
<MergeResourceInclude Source="../CarouselView/CarouselView.axaml" />
<MergeResourceInclude Source="../PersonPicture/PersonPicture.axaml" />
<MergeResourceInclude Source="../SettingsExpander/SettingsExpander.axaml" />
<MergeResourceInclude Source="../OverflowListBox/OverflowListBox.axaml" />
<MergeResourceInclude Source="../Overflow/Overflow.axaml" />
</ResourceDictionary.MergedDictionaries>
</ResourceDictionary>
</Styles.Resources>