|
|
@@ -0,0 +1,269 @@
|
|
|
+using Avalonia;
|
|
|
+using Avalonia.Controls;
|
|
|
+using Avalonia.Controls.Metadata;
|
|
|
+using Avalonia.Controls.Primitives;
|
|
|
+using Avalonia.LogicalTree;
|
|
|
+using Avalonia.Media;
|
|
|
+using Avalonia.Metadata;
|
|
|
+using CommunityToolkit.Mvvm.Input;
|
|
|
+using System;
|
|
|
+using System.Collections;
|
|
|
+using System.Collections.Generic;
|
|
|
+using System.Collections.ObjectModel;
|
|
|
+using System.Collections.Specialized;
|
|
|
+using System.Linq;
|
|
|
+using System.Text;
|
|
|
+using System.Threading.Tasks;
|
|
|
+using System.Windows.Input;
|
|
|
+
|
|
|
+namespace InABox.Avalonia.Components;
|
|
|
+
|
|
|
+[TemplatePart(Name = "PART_Grid")]
|
|
|
+public partial class Accordion : TemplatedControl
|
|
|
+{
|
|
|
+ public static readonly StyledProperty<ObservableCollection<AccordionItem>> ItemsProperty =
|
|
|
+ AvaloniaProperty.Register<Accordion, ObservableCollection<AccordionItem>>(nameof(Items));
|
|
|
+
|
|
|
+ public static readonly StyledProperty<object?> SelectedItemProperty =
|
|
|
+ AvaloniaProperty.Register<Accordion, object?>(nameof(SelectedItem));
|
|
|
+
|
|
|
+ public static readonly DirectProperty<Accordion, int> SelectedIndexProperty =
|
|
|
+ AvaloniaProperty.RegisterDirect<Accordion, int>(nameof(SelectedIndex), x => x.SelectedIndex, (x, v) => x.SelectedIndex = v);
|
|
|
+
|
|
|
+ [Content]
|
|
|
+ public ObservableCollection<AccordionItem> Items
|
|
|
+ {
|
|
|
+ get => GetValue(ItemsProperty);
|
|
|
+ set => SetValue(ItemsProperty, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ private AccordionItem? SelectedButton
|
|
|
+ {
|
|
|
+ get => Items.FirstOrDefault(x => x.Selected);
|
|
|
+ set
|
|
|
+ {
|
|
|
+ var i = 0;
|
|
|
+ var index = -1;
|
|
|
+ foreach(var item in Items)
|
|
|
+ {
|
|
|
+ item.Selected = item == value;
|
|
|
+ if(item == value)
|
|
|
+ {
|
|
|
+ index = i;
|
|
|
+ }
|
|
|
+ if(Grid is not null)
|
|
|
+ {
|
|
|
+ Grid.RowDefinitions[i].Height = item == value ? GridLength.Star : GridLength.Auto;
|
|
|
+ }
|
|
|
+ ++i;
|
|
|
+ }
|
|
|
+ SelectedItem = value?.Tag;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public object? SelectedItem
|
|
|
+ {
|
|
|
+ get => GetValue(SelectedItemProperty);
|
|
|
+ private set => SetValue(SelectedItemProperty, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ public int SelectedIndex
|
|
|
+ {
|
|
|
+ get => SelectedButton != null && Items.Contains(SelectedButton) ? Items.IndexOf(SelectedButton) : -1;
|
|
|
+ set => SelectedButton = value > -1 && value < Items.Count ? Items[value] : null;
|
|
|
+ }
|
|
|
+
|
|
|
+ public event EventHandler? SelectionChanged;
|
|
|
+
|
|
|
+ private Grid? Grid;
|
|
|
+
|
|
|
+ static Accordion()
|
|
|
+ {
|
|
|
+ ItemsProperty.Changed.AddClassHandler<Accordion>(Items_Changed);
|
|
|
+ }
|
|
|
+
|
|
|
+ protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
|
|
|
+ {
|
|
|
+ base.OnApplyTemplate(e);
|
|
|
+
|
|
|
+ Grid = e.NameScope.Get<Grid>("PART_Grid");
|
|
|
+ if(Grid is not null)
|
|
|
+ {
|
|
|
+ foreach(var item in Items)
|
|
|
+ {
|
|
|
+ Grid.SetRow(item, Grid.RowDefinitions.Count);
|
|
|
+ Grid.RowDefinitions.Add(new(item.Selected ? GridLength.Star : GridLength.Auto));
|
|
|
+ Grid.Children.Add(item);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void Items_Changed(Accordion strip, AvaloniaPropertyChangedEventArgs args)
|
|
|
+ {
|
|
|
+ strip.LogicalChildren.Clear();
|
|
|
+ strip.Grid?.Children.Clear();
|
|
|
+
|
|
|
+ if (strip.Items is not null)
|
|
|
+ {
|
|
|
+ strip.SelectedButton = strip.Items.FirstOrDefault();
|
|
|
+
|
|
|
+ strip.LogicalChildren.AddRange(strip.Items);
|
|
|
+ foreach(var item in strip.Items)
|
|
|
+ {
|
|
|
+ item.Command = strip.ItemSelectedCommand;
|
|
|
+ if(strip.Grid is not null)
|
|
|
+ {
|
|
|
+ Grid.SetRow(item, strip.Grid.RowDefinitions.Count);
|
|
|
+ strip.Grid.RowDefinitions.Add(new(item.Selected ? GridLength.Star : GridLength.Auto));
|
|
|
+ strip.Grid.Children.Add(item);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ public Accordion()
|
|
|
+ {
|
|
|
+ Items = new();
|
|
|
+ Items.CollectionChanged += Items_CollectionChanged;
|
|
|
+ }
|
|
|
+
|
|
|
+ private void Items_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
|
|
|
+ {
|
|
|
+ SelectedButton ??= Items.FirstOrDefault();
|
|
|
+ switch (e.Action)
|
|
|
+ {
|
|
|
+ case NotifyCollectionChangedAction.Add:
|
|
|
+ AddControlItemsToLogicalChildren(e.NewItems);
|
|
|
+ break;
|
|
|
+ case NotifyCollectionChangedAction.Remove:
|
|
|
+ RemoveControlItemsFromLogicalChildren(e.OldItems);
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ foreach(var item in Items)
|
|
|
+ {
|
|
|
+ item.Command = ItemSelectedCommand;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ private void AddControlItemsToLogicalChildren(IEnumerable? items)
|
|
|
+ {
|
|
|
+ if (items is null) return;
|
|
|
+
|
|
|
+ List<Control>? toAdd = null;
|
|
|
+ foreach(var i in items)
|
|
|
+ {
|
|
|
+ if(i is Control control && !LogicalChildren.Contains(control))
|
|
|
+ {
|
|
|
+ toAdd ??= new();
|
|
|
+ toAdd.Add(control);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if(toAdd is not null)
|
|
|
+ {
|
|
|
+ LogicalChildren.AddRange(toAdd);
|
|
|
+ if(Grid is not null)
|
|
|
+ {
|
|
|
+ foreach(var item in toAdd)
|
|
|
+ {
|
|
|
+ Grid.SetRow(item, Grid.RowDefinitions.Count);
|
|
|
+ Grid.RowDefinitions.Add(new(item is AccordionItem aItem && aItem.Selected ? GridLength.Star : GridLength.Auto));
|
|
|
+ Grid.Children.Add(item);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ private void RemoveControlItemsFromLogicalChildren(IEnumerable? items)
|
|
|
+ {
|
|
|
+ if (items is null) return;
|
|
|
+
|
|
|
+ List<Control>? toRemove = null;
|
|
|
+ foreach(var i in items)
|
|
|
+ {
|
|
|
+ if(i is Control control)
|
|
|
+ {
|
|
|
+ toRemove ??= new();
|
|
|
+ toRemove.Add(control);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if(toRemove is not null)
|
|
|
+ {
|
|
|
+ foreach(var item in toRemove)
|
|
|
+ {
|
|
|
+ var index = LogicalChildren.IndexOf(item);
|
|
|
+ if(index != -1)
|
|
|
+ {
|
|
|
+ Grid?.RowDefinitions.RemoveAt(index);
|
|
|
+ Grid?.Children.Remove(item);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ LogicalChildren.RemoveAll(toRemove);
|
|
|
+ if(Grid is not null)
|
|
|
+ {
|
|
|
+ var i = 0;
|
|
|
+ foreach(var item in LogicalChildren)
|
|
|
+ {
|
|
|
+ if(item is Control control)
|
|
|
+ {
|
|
|
+ Grid.SetRow(control, i);
|
|
|
+ ++i;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ [RelayCommand]
|
|
|
+ private void ItemSelected(AccordionItem item)
|
|
|
+ {
|
|
|
+ var children = this.GetLogicalChildren().ToArray();
|
|
|
+ SelectedButton = item;
|
|
|
+ SelectionChanged?.Invoke(this, new());
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+[PseudoClasses(":selected")]
|
|
|
+public class AccordionItem : TemplatedControl
|
|
|
+{
|
|
|
+ public static readonly StyledProperty<string> TextProperty = AvaloniaProperty.Register<AccordionItem, string>(nameof(Text));
|
|
|
+ public static readonly StyledProperty<object?> ContentProperty = AvaloniaProperty.Register<AccordionItem, object?>(nameof(Content));
|
|
|
+ public static readonly StyledProperty<bool> SelectedProperty = AvaloniaProperty.Register<AccordionItem, bool>(nameof(Selected));
|
|
|
+ public static readonly StyledProperty<int> IndexProperty = AvaloniaProperty.Register<AccordionItem, int>(nameof(Index));
|
|
|
+ public static readonly StyledProperty<ICommand> CommandProperty = AvaloniaProperty.Register<AccordionItem, ICommand>(nameof(Command));
|
|
|
+
|
|
|
+ public string Text
|
|
|
+ {
|
|
|
+ get => GetValue(TextProperty);
|
|
|
+ set => SetValue(TextProperty, value);
|
|
|
+ }
|
|
|
+ [Content]
|
|
|
+ public object? Content
|
|
|
+ {
|
|
|
+ get => GetValue(ContentProperty);
|
|
|
+ set => SetValue(ContentProperty, value);
|
|
|
+ }
|
|
|
+ public bool Selected
|
|
|
+ {
|
|
|
+ get => GetValue(SelectedProperty);
|
|
|
+ set => SetValue(SelectedProperty, value);
|
|
|
+ }
|
|
|
+ public int Index
|
|
|
+ {
|
|
|
+ get => GetValue(IndexProperty);
|
|
|
+ set => SetValue(IndexProperty, value);
|
|
|
+ }
|
|
|
+ public ICommand Command
|
|
|
+ {
|
|
|
+ get => GetValue(CommandProperty);
|
|
|
+ set => SetValue(CommandProperty, value);
|
|
|
+ }
|
|
|
+
|
|
|
+ static AccordionItem()
|
|
|
+ {
|
|
|
+ SelectedProperty.Changed.AddClassHandler<AccordionItem>(Selected_Changed);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static void Selected_Changed(AccordionItem item, AvaloniaPropertyChangedEventArgs args)
|
|
|
+ {
|
|
|
+ item.PseudoClasses.Set(":selected", item.Selected);
|
|
|
+ }
|
|
|
+}
|