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> ItemsProperty = AvaloniaProperty.Register>(nameof(Items)); public static readonly StyledProperty SelectedItemProperty = AvaloniaProperty.Register(nameof(SelectedItem)); public static readonly DirectProperty SelectedIndexProperty = AvaloniaProperty.RegisterDirect(nameof(SelectedIndex), x => x.SelectedIndex, (x, v) => x.SelectedIndex = v); [Content] public ObservableCollection 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(Items_Changed); } protected override void OnApplyTemplate(TemplateAppliedEventArgs e) { base.OnApplyTemplate(e); Grid = e.NameScope.Get("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? 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? 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 TextProperty = AvaloniaProperty.Register(nameof(Text)); public static readonly StyledProperty ContentProperty = AvaloniaProperty.Register(nameof(Content)); public static readonly StyledProperty SelectedProperty = AvaloniaProperty.Register(nameof(Selected)); public static readonly StyledProperty IndexProperty = AvaloniaProperty.Register(nameof(Index)); public static readonly StyledProperty CommandProperty = AvaloniaProperty.Register(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(Selected_Changed); } private static void Selected_Changed(AccordionItem item, AvaloniaPropertyChangedEventArgs args) { item.PseudoClasses.Set(":selected", item.Selected); } }