From 2f2fc996629058482a5ac6dadb5399be44537e61 Mon Sep 17 00:00:00 2001 From: Alek-ban Date: Thu, 26 Feb 2026 11:14:56 +0300 Subject: [PATCH] feat: SectionPanel ChildrenSource and ChildrenTemplate --- GSS2.UI.Core/SectionPanel.cs | 160 ++++++++++++++++++++++++++++++++++- 1 file changed, 158 insertions(+), 2 deletions(-) diff --git a/GSS2.UI.Core/SectionPanel.cs b/GSS2.UI.Core/SectionPanel.cs index 2707c9f..0aafa99 100644 --- a/GSS2.UI.Core/SectionPanel.cs +++ b/GSS2.UI.Core/SectionPanel.cs @@ -1,6 +1,10 @@ +using System.Collections; +using System.Collections.Specialized; + using Avalonia; using Avalonia.Animation; using Avalonia.Controls; +using Avalonia.Controls.Templates; using Avalonia.Layout; using Avalonia.Media; using Avalonia.Styling; @@ -64,6 +68,18 @@ public class SectionPanel : Panel get => GetValue(CanScrollBackwardProperty); set => SetValue(CanScrollBackwardProperty, value); } + public static readonly StyledProperty ChildrenSourceProperty = AvaloniaProperty.Register(nameof(ChildrenSource), null, defaultBindingMode: Avalonia.Data.BindingMode.OneWay); + public IEnumerable? ChildrenSource + { + get => GetValue(ChildrenSourceProperty); + set => SetValue(ChildrenSourceProperty, value); + } + public static readonly StyledProperty ChildrenTemplateProperty = AvaloniaProperty.Register(nameof(ChildrenTemplate), null); + public IDataTemplate? ChildrenTemplate + { + get => GetValue(ChildrenTemplateProperty); + set => SetValue(ChildrenTemplateProperty, value); + } private readonly Dictionary _childIndeces = new Dictionary(); private bool _initialized = false; @@ -78,13 +94,153 @@ public class SectionPanel : Panel CanScrollBackward = Cyclic || CurrentIndex != 0; panel.InvalidateArrange(); }); + ChildrenSourceProperty.Changed.AddClassHandler((panel, ea) => + { + if (panel != this) + return; + (ea.OldValue as INotifyCollectionChanged)?.CollectionChanged -= panel.OnChildrenSourceCollectionChanged; + (ea.NewValue as INotifyCollectionChanged)?.CollectionChanged += panel.OnChildrenSourceCollectionChanged; + panel.Children.Clear(); + if (ea.NewValue is IEnumerable enumerable) + { + IEnumerable controls; + if (ChildrenTemplate is null) + controls = enumerable.OfType(); + else + controls = enumerable.Cast() + .Where(ChildrenTemplate.Match) + .Select(i => + { + var control = ChildrenTemplate.Build(i); + control?.DataContext = i; + return control; + }) + .OfType(); + + foreach (var control in controls) + panel.Children.Add(control); + } + panel.InvalidateArrange(); + }); Children.CollectionChanged += (_, ea) => { - CanScrollForward = Cyclic || CurrentIndex != Children.Count() - 1; - CanScrollBackward = Cyclic || CurrentIndex != 0; + CanScrollForward = (Cyclic && Children.Count() > 1) || CurrentIndex != Children.Count() - 1; + CanScrollBackward = (Cyclic && Children.Count() > 1) || CurrentIndex != 0; }; } + protected void OnChildrenSourceCollectionChanged(object? sender, NotifyCollectionChangedEventArgs ea) + { + switch (ea.Action) + { + case NotifyCollectionChangedAction.Add: + { + if (ea.NewItems is null) + return; + + var index = ea.NewStartingIndex >= 0 + ? ea.NewStartingIndex + : Children.Count; + + IEnumerable controls; + if (ChildrenTemplate is null) + controls = ea.NewItems.OfType(); + else + controls = ea.NewItems.Cast() + .Where(ChildrenTemplate.Match) + .Select(i => + { + var control = ChildrenTemplate.Build(i); + control?.DataContext = i; + return control; + }) + .OfType(); + + foreach (Control control in controls) + Children.Insert(index++, control); + + + break; + } + + case NotifyCollectionChangedAction.Remove: + { + if (ea.OldItems is null) + return; + + foreach (Control control in ea.OldItems) + Children.Remove(control); + + break; + } + + case NotifyCollectionChangedAction.Replace: + { + if (ea.OldItems is not null) + foreach (Control control in ea.OldItems) + Children.Remove(control); + + if (ea.NewItems is not null) + { + var index = ea.NewStartingIndex >= 0 ? + ea.NewStartingIndex : + Children.Count; + + foreach (Control control in ea.NewItems) + Children.Insert(index++, control); + } + + break; + } + + case NotifyCollectionChangedAction.Move: + { + if (ea.OldItems is not IEnumerable oldControls || oldControls.Count() == 0) + return; + + var control = oldControls.First(); + + if (ea.OldStartingIndex >= 0) + Children.RemoveAt(ea.OldStartingIndex); + + if (ea.NewStartingIndex >= 0) + Children.Insert(ea.NewStartingIndex, control); + + break; + } + + case NotifyCollectionChangedAction.Reset: + { + Children.Clear(); + + if (ChildrenSource is null) + break; + + IEnumerable controls; + if (ChildrenTemplate is null) + controls = ChildrenSource.OfType(); + else + controls = ChildrenSource.Cast() + .Where(ChildrenTemplate.Match) + .Select(i => + { + var control = ChildrenTemplate.Build(i); + control?.DataContext = i; + return control; + }) + .OfType(); + + foreach (var control in controls) + Children.Add(control); + + break; + } + } + + InvalidateMeasure(); + InvalidateArrange(); + } + protected override Size MeasureOverride(Size availableSize) { foreach (var child in Children)