feat: add selection list.
This commit is contained in:
@@ -22,6 +22,7 @@ public static class MenuKeys
|
||||
public const string MenuKeyNumericUpDown = "NumericUpDown";
|
||||
public const string MenuKeyPagination = "Pagination";
|
||||
public const string MenuKeyRangeSlider = "RangeSlider";
|
||||
public const string MenuKeySelectionList = "SelectionList";
|
||||
public const string MenuKeyTagInput = "TagInput";
|
||||
public const string MenuKeyTimeline = "Timeline";
|
||||
public const string MenuKeyTwoTonePathIcon = "TwoTonePathIcon";
|
||||
|
||||
15
demo/Ursa.Demo/Pages/SelectionBoxDemo.axaml
Normal file
15
demo/Ursa.Demo/Pages/SelectionBoxDemo.axaml
Normal file
@@ -0,0 +1,15 @@
|
||||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:u="https://irihi.tech/ursa"
|
||||
xmlns:vm="using:Ursa.Demo.ViewModels"
|
||||
x:DataType="vm:SelectionListDemoViewModel"
|
||||
x:CompileBindings="True"
|
||||
mc:Ignorable="d" d:DesignWidth="800"
|
||||
d:DesignHeight="450"
|
||||
x:Class="Ursa.Demo.Pages.SelectionListDemo">
|
||||
<StackPanel HorizontalAlignment="Left">
|
||||
<u:SelectionList ItemsSource="{Binding Items}"></u:SelectionList>
|
||||
</StackPanel>
|
||||
</UserControl>
|
||||
13
demo/Ursa.Demo/Pages/SelectionBoxDemo.axaml.cs
Normal file
13
demo/Ursa.Demo/Pages/SelectionBoxDemo.axaml.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace Ursa.Demo.Pages;
|
||||
|
||||
public partial class SelectionListDemo : UserControl
|
||||
{
|
||||
public SelectionListDemo()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
}
|
||||
@@ -44,6 +44,7 @@ public class MainViewViewModel : ViewModelBase
|
||||
MenuKeys.MenuKeyNumericUpDown => new NumericUpDownDemoViewModel(),
|
||||
MenuKeys.MenuKeyPagination => new PaginationDemoViewModel(),
|
||||
MenuKeys.MenuKeyRangeSlider => new RangeSliderDemoViewModel(),
|
||||
MenuKeys.MenuKeySelectionList => new SelectionListDemoViewModel(),
|
||||
MenuKeys.MenuKeyTagInput => new TagInputDemoViewModel(),
|
||||
MenuKeys.MenuKeyTimeline => new TimelineDemoViewModel(),
|
||||
MenuKeys.MenuKeyTwoTonePathIcon => new TwoTonePathIconDemoViewModel(),
|
||||
|
||||
@@ -31,6 +31,7 @@ public class MenuViewModel: ViewModelBase
|
||||
new() { MenuHeader = "NumericUpDown", Key = MenuKeys.MenuKeyNumericUpDown, Status = "New" },
|
||||
new() { MenuHeader = "Pagination", Key = MenuKeys.MenuKeyPagination },
|
||||
new() { MenuHeader = "RangeSlider", Key = MenuKeys.MenuKeyRangeSlider, Status = "New"},
|
||||
new() { MenuHeader = "Selection LIst", Key = MenuKeys.MenuKeySelectionList, Status = "New" },
|
||||
new() { MenuHeader = "TagInput", Key = MenuKeys.MenuKeyTagInput },
|
||||
new() { MenuHeader = "Theme Toggler", Key = MenuKeys.MenuKeyThemeToggler },
|
||||
new() { MenuHeader = "Timeline", Key = MenuKeys.MenuKeyTimeline, Status = "Updated" },
|
||||
|
||||
17
demo/Ursa.Demo/ViewModels/SelectionListDemoViewModel.cs
Normal file
17
demo/Ursa.Demo/ViewModels/SelectionListDemoViewModel.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
using System.Collections.ObjectModel;
|
||||
using CommunityToolkit.Mvvm.ComponentModel;
|
||||
|
||||
namespace Ursa.Demo.ViewModels;
|
||||
|
||||
public class SelectionListDemoViewModel: ObservableObject
|
||||
{
|
||||
public ObservableCollection<string> Items { get; set; }
|
||||
|
||||
public SelectionListDemoViewModel()
|
||||
{
|
||||
Items = new ObservableCollection<string>()
|
||||
{
|
||||
"Ding", "Otter", "Husky", "Mr. 17", "Cass"
|
||||
};
|
||||
}
|
||||
}
|
||||
52
src/Ursa.Themes.Semi/Controls/SelectionList.axaml
Normal file
52
src/Ursa.Themes.Semi/Controls/SelectionList.axaml
Normal file
@@ -0,0 +1,52 @@
|
||||
<ResourceDictionary
|
||||
xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:u="https://irihi.tech/ursa">
|
||||
<ControlTheme x:Key="{x:Type u:SelectionList}" TargetType="u:SelectionList">
|
||||
<Setter Property="Background" Value="LightBlue"></Setter>
|
||||
<Setter Property="ListBox.Template">
|
||||
<ControlTemplate TargetType="ListBox">
|
||||
<Border
|
||||
Name="border"
|
||||
Background="{TemplateBinding Background}"
|
||||
BorderBrush="{TemplateBinding BorderBrush}"
|
||||
BorderThickness="{TemplateBinding BorderThickness}"
|
||||
ClipToBounds="{TemplateBinding ClipToBounds}"
|
||||
CornerRadius="{TemplateBinding CornerRadius}">
|
||||
<Panel>
|
||||
<Border
|
||||
Classes="Shadow"
|
||||
Margin="2"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch"
|
||||
Name="{x:Static u:SelectionList.PART_Indicator}"
|
||||
Theme="{DynamicResource CardBorder}" />
|
||||
<ItemsPresenter
|
||||
Name="PART_ItemsPresenter"
|
||||
Margin="{TemplateBinding Padding}"
|
||||
ItemsPanel="{TemplateBinding ItemsPanel}" />
|
||||
</Panel>
|
||||
</Border>
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
</ControlTheme>
|
||||
|
||||
<ControlTheme x:Key="{x:Type u:SelectionListItem}" TargetType="u:SelectionListItem">
|
||||
<Setter Property="Background" Value="Transparent" />
|
||||
<Setter Property="Padding" Value="8" />
|
||||
<Setter Property="Template">
|
||||
<ControlTemplate TargetType="u:SelectionListItem">
|
||||
<ContentPresenter
|
||||
Name="PART_ContentPresenter"
|
||||
Padding="{TemplateBinding Padding}"
|
||||
Foreground="{TemplateBinding Foreground}"
|
||||
Background="{TemplateBinding Background}"
|
||||
Content="{TemplateBinding Content}"
|
||||
ContentTemplate="{TemplateBinding ContentTemplate}" />
|
||||
</ControlTemplate>
|
||||
</Setter>
|
||||
<Style Selector="^:selected">
|
||||
<Setter Property="Foreground" Value="Blue"></Setter>
|
||||
</Style>
|
||||
</ControlTheme>
|
||||
</ResourceDictionary>
|
||||
@@ -21,6 +21,7 @@
|
||||
<ResourceInclude Source="NumericUpDown.axaml" />
|
||||
<ResourceInclude Source="Pagination.axaml" />
|
||||
<ResourceInclude Source="RangeSlider.axaml" />
|
||||
<ResourceInclude Source="SelectionList.axaml" />
|
||||
<ResourceInclude Source="TagInput.axaml" />
|
||||
<ResourceInclude Source="ThemeSelector.axaml" />
|
||||
<ResourceInclude Source="Timeline.axaml" />
|
||||
|
||||
126
src/Ursa/Controls/SelectionList/SelectionList.cs
Normal file
126
src/Ursa/Controls/SelectionList/SelectionList.cs
Normal file
@@ -0,0 +1,126 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Metadata;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Controls.Selection;
|
||||
using Avalonia.Controls.Templates;
|
||||
using Avalonia.Input;
|
||||
using Avalonia.Rendering.Composition;
|
||||
using Avalonia.Rendering.Composition.Animations;
|
||||
using Irihi.Avalonia.Shared.Helpers;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
[TemplatePart(PART_Indicator, typeof(Control))]
|
||||
public class SelectionList: SelectingItemsControl
|
||||
{
|
||||
public const string PART_Indicator = "PART_Indicator";
|
||||
private static readonly FuncTemplate<Panel?> DefaultPanel = new(() => new StackPanel());
|
||||
|
||||
private Control? _indicator;
|
||||
private ImplicitAnimationCollection? _implicitAnimations;
|
||||
|
||||
static SelectionList()
|
||||
{
|
||||
SelectionModeProperty.OverrideMetadata<SelectionList>(
|
||||
new StyledPropertyMetadata<SelectionMode>(
|
||||
defaultValue: SelectionMode.Single,
|
||||
coerce: (o, mode) => SelectionMode.Single)
|
||||
);
|
||||
SelectedItemProperty.Changed.AddClassHandler<SelectionList, object?>((list, args) =>
|
||||
list.OnSelectedItemChanged(args));
|
||||
}
|
||||
|
||||
private void OnSelectedItemChanged(AvaloniaPropertyChangedEventArgs<object?> args)
|
||||
{
|
||||
var newValue = args.NewValue.Value;
|
||||
if (newValue is null)
|
||||
{
|
||||
OpacityProperty.SetValue(0d, _indicator);
|
||||
return;
|
||||
}
|
||||
var container = ContainerFromItem(newValue);
|
||||
if (container is null)
|
||||
{
|
||||
OpacityProperty.SetValue(0d, _indicator);
|
||||
return;
|
||||
}
|
||||
OpacityProperty.SetValue(1d, _indicator);
|
||||
InvalidateMeasure();
|
||||
InvalidateArrange();
|
||||
}
|
||||
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
var size = base.ArrangeOverride(finalSize);
|
||||
if(_indicator is not null && SelectedItem is not null)
|
||||
{
|
||||
var container = ContainerFromItem(SelectedItem);
|
||||
if (container is null) return size;
|
||||
_indicator.Arrange(container.Bounds);
|
||||
}
|
||||
return size;
|
||||
}
|
||||
|
||||
protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey)
|
||||
{
|
||||
return NeedsContainer<SelectionListItem>(item, out recycleKey);
|
||||
}
|
||||
|
||||
protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey)
|
||||
{
|
||||
return new SelectionListItem();
|
||||
}
|
||||
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
||||
{
|
||||
base.OnApplyTemplate(e);
|
||||
_indicator = e.NameScope.Find<Control>(PART_Indicator);
|
||||
_indicator?.Arrange(new Rect());
|
||||
if (_indicator is not null)
|
||||
{
|
||||
_indicator.Opacity = 0;
|
||||
SetUpAnimation();
|
||||
if (ElementComposition.GetElementVisual(_indicator) is { } v)
|
||||
{
|
||||
v.ImplicitAnimations = _implicitAnimations;
|
||||
}
|
||||
_indicator.SizeChanged += OnIndicatorSizeChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void OnIndicatorSizeChanged(object sender, SizeChangedEventArgs e)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
internal void SelectByIndex(int index)
|
||||
{
|
||||
using var operation = Selection.BatchUpdate();
|
||||
Selection.Clear();
|
||||
Selection.Select(index);
|
||||
}
|
||||
|
||||
private void SetUpAnimation()
|
||||
{
|
||||
var compositor = ElementComposition.GetElementVisual(this)!.Compositor;
|
||||
var offsetAnimation = compositor.CreateVector3KeyFrameAnimation();
|
||||
offsetAnimation.Target = "Offset";
|
||||
offsetAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue");
|
||||
offsetAnimation.Duration = TimeSpan.FromSeconds(0.3);
|
||||
var sizeAnimation = compositor.CreateVector2KeyFrameAnimation();
|
||||
sizeAnimation.Target = "Size";
|
||||
sizeAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue");
|
||||
sizeAnimation.Duration = TimeSpan.FromSeconds(0.3);
|
||||
var opacityAnimation = compositor.CreateScalarKeyFrameAnimation();
|
||||
opacityAnimation.Target = nameof(CompositionVisual.Opacity);
|
||||
opacityAnimation.InsertExpressionKeyFrame(1.0f, "this.FinalValue");
|
||||
opacityAnimation.Duration = TimeSpan.FromSeconds(0.3);
|
||||
|
||||
_implicitAnimations = compositor.CreateImplicitAnimationCollection();
|
||||
_implicitAnimations["Offset"] = offsetAnimation;
|
||||
_implicitAnimations["Size"] = sizeAnimation;
|
||||
_implicitAnimations["Opacity"] = opacityAnimation;
|
||||
}
|
||||
|
||||
}
|
||||
38
src/Ursa/Controls/SelectionList/SelectionListItem.cs
Normal file
38
src/Ursa/Controls/SelectionList/SelectionListItem.cs
Normal file
@@ -0,0 +1,38 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Mixins;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Input;
|
||||
|
||||
namespace Ursa.Controls;
|
||||
|
||||
public class SelectionListItem: ContentControl, ISelectable
|
||||
{
|
||||
static SelectionListItem()
|
||||
{
|
||||
SelectableMixin.Attach<SelectionListItem>(IsSelectedProperty);
|
||||
PressedMixin.Attach<SelectionListItem>();
|
||||
FocusableProperty.OverrideDefaultValue<SelectionListItem>(true);
|
||||
}
|
||||
|
||||
private static readonly Point s_invalidPoint = new Point(double.NaN, double.NaN);
|
||||
private Point _pointerDownPoint = s_invalidPoint;
|
||||
|
||||
public static readonly StyledProperty<bool> IsSelectedProperty = SelectingItemsControl.IsSelectedProperty.AddOwner<ListBoxItem>();
|
||||
|
||||
public bool IsSelected
|
||||
{
|
||||
get => GetValue(IsSelectedProperty);
|
||||
set => SetValue(IsSelectedProperty, value);
|
||||
}
|
||||
|
||||
protected override void OnPointerPressed(PointerPressedEventArgs e)
|
||||
{
|
||||
base.OnPointerPressed(e);
|
||||
if (ItemsControl.ItemsControlFromItemContaner(this) is SelectionList list)
|
||||
{
|
||||
int index = list.IndexFromContainer(this);
|
||||
list.SelectByIndex(index);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user