From 9073234f725cdbef28e713353fd52e430030bb92 Mon Sep 17 00:00:00 2001 From: Steven Kirk Date: Sat, 1 Feb 2020 00:48:57 +0100 Subject: [PATCH] Use SelectedItems for change event args. --- src/Avalonia.Controls/IndexRange.cs | 12 ++ src/Avalonia.Controls/SelectedItems.cs | 37 ++--- src/Avalonia.Controls/SelectionModel.cs | 14 +- .../SelectionModelChangeSet.cs | 149 ++++++++++++++---- ...SelectionModelSelectionChangedEventArgs.cs | 53 +------ .../SelectionNodeOperation.cs | 32 +++- 6 files changed, 193 insertions(+), 104 deletions(-) diff --git a/src/Avalonia.Controls/IndexRange.cs b/src/Avalonia.Controls/IndexRange.cs index 124f1e0500..1dc161c699 100644 --- a/src/Avalonia.Controls/IndexRange.cs +++ b/src/Avalonia.Controls/IndexRange.cs @@ -202,6 +202,18 @@ namespace Avalonia.Controls } } + public static int GetCount(IEnumerable ranges) + { + var result = 0; + + foreach (var range in ranges) + { + result += (range.End - range.Begin) + 1; + } + + return result; + } + private static void MergeRanges(IList ranges) { for (var i = ranges.Count - 2; i >= 0; --i) diff --git a/src/Avalonia.Controls/SelectedItems.cs b/src/Avalonia.Controls/SelectedItems.cs index af43742670..a3acb48765 100644 --- a/src/Avalonia.Controls/SelectedItems.cs +++ b/src/Avalonia.Controls/SelectedItems.cs @@ -6,44 +6,37 @@ using System; using System.Collections; using System.Collections.Generic; -using SelectedItemInfo = Avalonia.Controls.SelectionModel.SelectedItemInfo; #nullable enable namespace Avalonia.Controls { - internal class SelectedItems : IReadOnlyList + public interface ISelectedItemInfo { - private readonly List _infos; - private readonly Func, int, T> _getAtImpl; + public IndexPath Path { get; } + } + + internal class SelectedItems : IReadOnlyList + where Tinfo : ISelectedItemInfo + { + private readonly List _infos; + private readonly Func, int, TValue> _getAtImpl; public SelectedItems( - List infos, - Func, int, T> getAtImpl) + List infos, + int count, + Func, int, TValue> getAtImpl) { _infos = infos; _getAtImpl = getAtImpl; - - foreach (var info in infos) - { - var node = info.Node; - - if (node != null) - { - Count += node.SelectedCount; - } - else - { - throw new InvalidOperationException("Selection changed after the SelectedIndices/Items property was read."); - } - } + Count = count; } - public T this[int index] => _getAtImpl(_infos, index); + public TValue this[int index] => _getAtImpl(_infos, index); public int Count { get; } - public IEnumerator GetEnumerator() + public IEnumerator GetEnumerator() { for (var i = 0; i < Count; ++i) { diff --git a/src/Avalonia.Controls/SelectionModel.cs b/src/Avalonia.Controls/SelectionModel.cs index 49f2c8d2f6..c8d2c5cc9e 100644 --- a/src/Avalonia.Controls/SelectionModel.cs +++ b/src/Avalonia.Controls/SelectionModel.cs @@ -165,6 +165,7 @@ namespace Avalonia.Controls if (_selectedItemsCached == null) { var selectedInfos = new List(); + var count = 0; if (_rootNode.Source != null) { @@ -176,6 +177,7 @@ namespace Avalonia.Controls if (currentInfo.Node.SelectedCount > 0) { selectedInfos.Add(new SelectedItemInfo(currentInfo.Node, currentInfo.Path)); + count += currentInfo.Node.SelectedCount; } }); } @@ -185,8 +187,9 @@ namespace Avalonia.Controls // the selected item at a particular index. This avoid having to create the storage and copying // needed in a dumb vector. This also allows us to expose a tree of selected nodes into an // easier to consume flat vector view of objects. - var selectedItems = new SelectedItems ( + var selectedItems = new SelectedItems ( selectedInfos, + count, (infos, index) => { var currentIndex = 0; @@ -233,6 +236,8 @@ namespace Avalonia.Controls if (_selectedIndicesCached == null) { var selectedInfos = new List(); + var count = 0; + SelectionTreeHelper.Traverse( _rootNode, false, @@ -241,6 +246,7 @@ namespace Avalonia.Controls if (currentInfo.Node.SelectedCount > 0) { selectedInfos.Add(new SelectedItemInfo(currentInfo.Node, currentInfo.Path)); + count += currentInfo.Node.SelectedCount; } }); @@ -249,8 +255,9 @@ namespace Avalonia.Controls // the IndexPath at a particular index. This avoid having to create the storage and copying // needed in a dumb vector. This also allows us to expose a tree of selected nodes into an // easier to consume flat vector view of IndexPaths. - var indices = new SelectedItems( + var indices = new SelectedItems( selectedInfos, + count, (infos, index) => // callback for GetAt(index) { var currentIndex = 0; @@ -720,7 +727,7 @@ namespace Avalonia.Controls OnSelectionChanged(e); } - internal class SelectedItemInfo + internal class SelectedItemInfo : ISelectedItemInfo { public SelectedItemInfo(SelectionNode node, IndexPath path) { @@ -730,6 +737,7 @@ namespace Avalonia.Controls public SelectionNode Node { get; } public IndexPath Path { get; } + public int Count => Node.SelectedCount; } private struct Operation : IDisposable diff --git a/src/Avalonia.Controls/SelectionModelChangeSet.cs b/src/Avalonia.Controls/SelectionModelChangeSet.cs index e927afb9b9..c0228a1cbc 100644 --- a/src/Avalonia.Controls/SelectionModelChangeSet.cs +++ b/src/Avalonia.Controls/SelectionModelChangeSet.cs @@ -14,61 +14,144 @@ namespace Avalonia.Controls public SelectionModelSelectionChangedEventArgs CreateEventArgs() { + var deselectedCount = 0; + var selectedCount = 0; + + foreach (var change in _changes) + { + deselectedCount += change.DeselectedCount; + selectedCount += change.SelectedCount; + } + + var deselectedIndices = new SelectedItems( + _changes, + deselectedCount, + GetDeselectedIndexAt); + var selectedIndices = new SelectedItems( + _changes, + selectedCount, + GetSelectedIndexAt); + var deselectedItems = new SelectedItems( + _changes, + deselectedCount, + GetDeselectedItemAt); + var selectedItems = new SelectedItems( + _changes, + selectedCount, + GetSelectedItemAt); + return new SelectionModelSelectionChangedEventArgs( - CreateIndices(x => x.DeselectedRanges), - CreateIndices(x => x.SelectedRanges), - CreateItems(x => x.DeselectedRanges), - CreateItems(x => x.SelectedRanges)); + deselectedIndices, + selectedIndices, + deselectedItems, + selectedItems); } - private IEnumerable CreateIndices(Func?> selector) + private IndexPath GetDeselectedIndexAt( + List infos, + int index) { - if (_changes == null) - { - yield break; - } + static int GetCount(SelectionNodeOperation info) => info.DeselectedCount; + static List GetRanges(SelectionNodeOperation info) => info.DeselectedRanges; + return GetIndexAt(infos, index, GetCount, GetRanges); + } - foreach (var i in _changes) + private IndexPath GetSelectedIndexAt( + List infos, + int index) + { + static int GetCount(SelectionNodeOperation info) => info.SelectedCount; + static List GetRanges(SelectionNodeOperation info) => info.SelectedRanges; + return GetIndexAt(infos, index, GetCount, GetRanges); + } + + private object GetDeselectedItemAt( + List infos, + int index) + { + static int GetCount(SelectionNodeOperation info) => info.DeselectedCount; + static List GetRanges(SelectionNodeOperation info) => info.DeselectedRanges; + return GetItemAt(infos, index, GetCount, GetRanges); + } + + private object GetSelectedItemAt( + List infos, + int index) + { + static int GetCount(SelectionNodeOperation info) => info.SelectedCount; + static List GetRanges(SelectionNodeOperation info) => info.SelectedRanges; + return GetItemAt(infos, index, GetCount, GetRanges); + } + + private IndexPath GetIndexAt( + List infos, + int index, + Func getCount, + Func> getRanges) + { + var currentIndex = 0; + IndexPath path = default; + + foreach (var info in infos) { - var ranges = selector(i); + var currentCount = getCount(info); - if (ranges != null) + if (index >= currentIndex && index < currentIndex + currentCount) { - foreach (var j in ranges) - { - for (var k = j.Begin; k <= j.End; ++k) - { - yield return i.Path.CloneWithChildIndex(k); - } - } + int targetIndex = GetIndexAt(getRanges(info), index - currentIndex); + path = info.Path.CloneWithChildIndex(targetIndex); + break; } + + currentIndex += currentCount; } + + return path; } - private IEnumerable CreateItems(Func?> selector) + private object GetItemAt( + List infos, + int index, + Func getCount, + Func> getRanges) { - if (_changes == null) + var currentIndex = 0; + object item = null; + + foreach (var info in infos) { - yield break; + var currentCount = getCount(info); + + if (index >= currentIndex && index < currentIndex + currentCount) + { + int targetIndex = GetIndexAt(getRanges(info), index - currentIndex); + item = info.Items.GetAt(targetIndex); + break; + } + + currentIndex += currentCount; } - var result = new List(); + return item; + } - foreach (var i in _changes) + private int GetIndexAt(List ranges, int index) + { + var currentIndex = 0; + + foreach (var range in ranges) { - var ranges = selector(i); + var currentCount = (range.End - range.Begin) + 1; - if (ranges != null && i.Items != null) + if (index >= currentIndex && index < currentIndex + currentCount) { - foreach (var j in ranges) - { - for (var k = j.Begin; k <= j.End; ++k) - { - yield return i.Items.GetAt(k); - } - } + return range.Begin + (index - currentIndex); } + + currentIndex += currentCount; } + + throw new IndexOutOfRangeException(); } } } diff --git a/src/Avalonia.Controls/SelectionModelSelectionChangedEventArgs.cs b/src/Avalonia.Controls/SelectionModelSelectionChangedEventArgs.cs index ae98f6a1ce..4e64ee6e6f 100644 --- a/src/Avalonia.Controls/SelectionModelSelectionChangedEventArgs.cs +++ b/src/Avalonia.Controls/SelectionModelSelectionChangedEventArgs.cs @@ -12,73 +12,36 @@ namespace Avalonia.Controls { public class SelectionModelSelectionChangedEventArgs : EventArgs { - private readonly IEnumerable? _deselectedIndicesSource; - private readonly IEnumerable? _selectedIndicesSource; - private readonly IEnumerable? _deselectedItemsSource; - private readonly IEnumerable? _selectedItemsSource; - private IReadOnlyList? _deselectedIndices; - private IReadOnlyList? _selectedIndices; - private IReadOnlyList? _deselectedItems; - private IReadOnlyList? _selectedItems; - public SelectionModelSelectionChangedEventArgs( IReadOnlyList? deselectedIndices, IReadOnlyList? selectedIndices, IReadOnlyList? deselectedItems, IReadOnlyList? selectedItems) { - _deselectedIndices = deselectedIndices ?? Array.Empty(); - _selectedIndices = selectedIndices ?? Array.Empty(); - _deselectedItems = deselectedItems ?? Array.Empty(); - _selectedItems= selectedItems ?? Array.Empty(); - } - - public SelectionModelSelectionChangedEventArgs( - IEnumerable? deselectedIndices, - IEnumerable? selectedIndices, - IEnumerable? deselectedItems, - IEnumerable? selectedItems) - { - static void Set(IEnumerable? source, ref IEnumerable? sourceField, ref IReadOnlyList? field) - { - if (source != null) - { - sourceField = source; - } - else - { - field = Array.Empty(); - } - } - - Set(deselectedIndices, ref _deselectedIndicesSource, ref _deselectedIndices); - Set(selectedIndices, ref _selectedIndicesSource, ref _selectedIndices); - Set(deselectedItems, ref _deselectedItemsSource, ref _deselectedItems); - Set(selectedItems, ref _selectedItemsSource, ref _selectedItems); + DeselectedIndices = deselectedIndices ?? Array.Empty(); + SelectedIndices = selectedIndices ?? Array.Empty(); + DeselectedItems = deselectedItems ?? Array.Empty(); + SelectedItems= selectedItems ?? Array.Empty(); } /// /// Gets the indices of the items that were removed from the selection. /// - public IReadOnlyList DeselectedIndices - => _deselectedIndices ??= new List(_deselectedIndicesSource); + public IReadOnlyList DeselectedIndices { get; } /// /// Gets the indices of the items that were added to the selection. /// - public IReadOnlyList SelectedIndices - => _selectedIndices ??= new List(_selectedIndicesSource); + public IReadOnlyList SelectedIndices { get; } /// /// Gets the items that were removed from the selection. /// - public IReadOnlyList DeselectedItems - => _deselectedItems ??= new List(_deselectedItemsSource); + public IReadOnlyList DeselectedItems { get; } /// /// Gets the items that were added to the selection. /// - public IReadOnlyList SelectedItems - => _selectedItems ??= new List(_selectedItemsSource); + public IReadOnlyList SelectedItems { get; } } } diff --git a/src/Avalonia.Controls/SelectionNodeOperation.cs b/src/Avalonia.Controls/SelectionNodeOperation.cs index 04b8554f7c..9622a52f00 100644 --- a/src/Avalonia.Controls/SelectionNodeOperation.cs +++ b/src/Avalonia.Controls/SelectionNodeOperation.cs @@ -6,11 +6,13 @@ using System.Linq; namespace Avalonia.Controls { - internal class SelectionNodeOperation + internal class SelectionNodeOperation : ISelectedItemInfo { private readonly SelectionNode _owner; private List? _selected; private List? _deselected; + private int _selectedCount = -1; + private int _deselectedCount = -1; public SelectionNodeOperation(SelectionNode owner) { @@ -23,9 +25,36 @@ namespace Avalonia.Controls public IndexPath Path => _owner.IndexPath; public ItemsSourceView? Items => _owner.ItemsSourceView; + public int SelectedCount + { + get + { + if (_selectedCount == -1) + { + _selectedCount = (_selected != null) ? IndexRange.GetCount(_selected) : 0; + } + + return _selectedCount; + } + } + + public int DeselectedCount + { + get + { + if (_deselectedCount == -1) + { + _deselectedCount = (_deselected != null) ? IndexRange.GetCount(_deselected) : 0; + } + + return _deselectedCount; + } + } + public void Selected(IndexRange range) { Add(range, ref _selected, _deselected); + _selectedCount = -1; } public void Selected(IEnumerable ranges) @@ -39,6 +68,7 @@ namespace Avalonia.Controls public void Deselected(IndexRange range) { Add(range, ref _deselected, _selected); + _deselectedCount = -1; } public void Deselected(IEnumerable ranges)