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;
using Avalonia.Data.Converters;
using Avalonia.Markup.Xaml;
using Avalonia.Metadata; using Avalonia.Metadata;
using System.Globalization;
namespace Toolkit.UI.Avalonia; 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 : public class ConditionalExpression :
AvaloniaObject, AvaloniaObject,
ICondition 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="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="using:Toolkit.UI.Controls.Avalonia"> 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"> <Setter Property="Template">
<ControlTemplate> <ControlTemplate>
<Grid Margin="{TemplateBinding Margin}" ColumnDefinitions="*,Auto"> <Grid Margin="{TemplateBinding Margin}" ColumnDefinitions="*,Auto">
@@ -11,14 +11,14 @@
Grid.Column="0" Grid.Column="0"
ItemTemplate="{TemplateBinding ItemTemplate}" ItemTemplate="{TemplateBinding ItemTemplate}"
ItemsPanel="{TemplateBinding ItemsPanel}" ItemsPanel="{TemplateBinding ItemsPanel}"
SelectedItem="{TemplateBinding SelectedItem}" /> SelectedItem="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.PrimarySelection, Mode=TwoWay}" />
<Button Grid.Column="1"> <Button Grid.Column="1" Focusable="False">
<Button.Flyout> <Button.Flyout>
<Flyout> <Flyout>
<ListBox <ListBox
x:Name="SecondaryListBox" x:Name="SecondaryListBox"
ItemTemplate="{TemplateBinding ItemTemplate}" ItemTemplate="{TemplateBinding ItemTemplate}"
SelectedItem="{TemplateBinding SelectedItem}" /> SelectedItem="{Binding RelativeSource={RelativeSource TemplatedParent}, Path=TemplateSettings.SecondarySelection, Mode=TwoWay}" />
</Flyout> </Flyout>
</Button.Flyout> </Button.Flyout>
</Button> </Button>
@@ -2,36 +2,66 @@
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives; using Avalonia.Controls.Primitives;
using Avalonia.Controls.Templates; using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Metadata; using Avalonia.Metadata;
using Avalonia.Threading; using Avalonia.Threading;
using FluentAvalonia.Core;
using System.Collections; using System.Collections;
using System.Collections.ObjectModel; using System.Collections.ObjectModel;
using System.Collections.Specialized; using System.Collections.Specialized;
namespace Toolkit.UI.Controls.Avalonia; namespace Toolkit.UI.Controls.Avalonia;
public class OverflowListBox : public class Overflow :
TemplatedControl TemplatedControl
{ {
public static readonly StyledProperty<ITemplate<Panel?>> ItemsPanelProperty = 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 = public static readonly StyledProperty<IEnumerable?> ItemsSourceProperty =
AvaloniaProperty.Register<OverflowListBox, IEnumerable?>(nameof(ItemsSource)); AvaloniaProperty.Register<Overflow, IEnumerable?>(nameof(ItemsSource));
public static readonly StyledProperty<IDataTemplate?> ItemTemplateProperty = public static readonly StyledProperty<IDataTemplate?> ItemTemplateProperty =
AvaloniaProperty.Register<OverflowListBox, IDataTemplate?>(nameof(ItemTemplate)); AvaloniaProperty.Register<Overflow, IDataTemplate?>(nameof(ItemTemplate));
public static readonly StyledProperty<object?> SelectedItemProperty = 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 static readonly StyledProperty<OverflowTemplateSettings> TemplateSettingsProperty =
private readonly ObservableCollection<object> secondaryCollection = new(); AvaloniaProperty.Register<Overflow, OverflowTemplateSettings>(nameof(TemplateSettings));
private readonly ObservableCollection<object> primaryCollection = [];
private readonly ObservableCollection<object> secondaryCollection = [];
private ListBox? primaryListBox; private ListBox? primaryListBox;
private ListBox? secondaryListBox; 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 public ITemplate<Panel?> ItemsPanel
{ {
get => GetValue(ItemsPanelProperty); get => GetValue(ItemsPanelProperty);
@@ -57,15 +87,26 @@ public class OverflowListBox :
set => SetValue(SelectedItemProperty, value); set => SetValue(SelectedItemProperty, value);
} }
public OverflowTemplateSettings TemplateSettings
{
get => GetValue(TemplateSettingsProperty);
set => SetValue(TemplateSettingsProperty, value);
}
protected override void OnApplyTemplate(TemplateAppliedEventArgs args) protected override void OnApplyTemplate(TemplateAppliedEventArgs args)
{ {
base.OnApplyTemplate(args); base.OnApplyTemplate(args);
primaryListBox = args.NameScope.Get<ListBox>("PrimaryListBox"); 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 = args.NameScope.Get<ListBox>("SecondaryListBox");
secondaryListBox?.SetValue(ItemsControl.ItemsSourceProperty, secondaryCollection); if (secondaryListBox is not null)
{
secondaryListBox.SetValue(ItemsControl.ItemsSourceProperty, secondaryCollection);
}
InitializeCollections(); InitializeCollections();
UpdateOverflow(); UpdateOverflow();
@@ -74,6 +115,12 @@ public class OverflowListBox :
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs args) protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs args)
{ {
base.OnPropertyChanged(args); base.OnPropertyChanged(args);
if (args.Property == SelectedItemProperty)
{
UpdateSelectedItem();
}
if (args.Property == ItemsSourceProperty) if (args.Property == ItemsSourceProperty)
{ {
if (args.OldValue is IEnumerable oldCollection && oldCollection is INotifyCollectionChanged oldNotifyCollectionChanged) 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) switch (args.Action)
{ {
@@ -205,7 +253,7 @@ public class OverflowListBox :
double accumulatedWidth = 0; double accumulatedWidth = 0;
double itemSpacing = 6; double itemSpacing = 6;
List<(object item, int originalIndex)> itemsToMoveToSecondary = new(); List<object> itemsToMoveToSecondary = [];
for (int i = 0; i < primaryCollection.Count; i++) for (int i = 0; i < primaryCollection.Count; i++)
{ {
@@ -217,7 +265,7 @@ public class OverflowListBox :
if (accumulatedWidth + itemWidth + (itemsToMoveToSecondary.Count * itemSpacing) > controlWidth) if (accumulatedWidth + itemWidth + (itemsToMoveToSecondary.Count * itemSpacing) > controlWidth)
{ {
itemsToMoveToSecondary.Add((item, i)); itemsToMoveToSecondary.Add(item);
} }
else 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); 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); 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="../CarouselView/CarouselView.axaml" />
<MergeResourceInclude Source="../PersonPicture/PersonPicture.axaml" /> <MergeResourceInclude Source="../PersonPicture/PersonPicture.axaml" />
<MergeResourceInclude Source="../SettingsExpander/SettingsExpander.axaml" /> <MergeResourceInclude Source="../SettingsExpander/SettingsExpander.axaml" />
<MergeResourceInclude Source="../OverflowListBox/OverflowListBox.axaml" /> <MergeResourceInclude Source="../Overflow/Overflow.axaml" />
</ResourceDictionary.MergedDictionaries> </ResourceDictionary.MergedDictionaries>
</ResourceDictionary> </ResourceDictionary>
</Styles.Resources> </Styles.Resources>