wip
This commit is contained in:
@@ -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
|
||||||
|
|||||||
@@ -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;
|
||||||
|
}
|
||||||
+4
-4
@@ -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>
|
||||||
+90
-15
@@ -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>
|
||||||
|
|||||||
Reference in New Issue
Block a user