Skip to content

Commit

Permalink
Merge pull request #317 from AvaloniaUI/fixes/selection-collectioncha…
Browse files Browse the repository at this point in the history
…nged

Handle Move in TreeSelectionMode.
  • Loading branch information
grokys authored Nov 26, 2024
2 parents 8196b05 + 0336b9b commit ca2f993
Show file tree
Hide file tree
Showing 11 changed files with 326 additions and 34 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build-and-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ jobs:
with:
dotnet-version: |
6.0.x
7.0.x
8.0.x
- name: Install dependencies
run: dotnet restore
- name: Build
Expand Down
2 changes: 1 addition & 1 deletion Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
<TreatWarningsAsErrors>true</TreatWarningsAsErrors>
<WarningsAsErrors>nullable</WarningsAsErrors>
<TargetFramework>net6.0</TargetFramework>
<LangVersion>9</LangVersion>
<LangVersion>12</LangVersion>
<!-- https://github.com/dotnet/msbuild/issues/2661 -->
<AddSyntheticProjectReferencesForSolutionDependencies>false</AddSyntheticProjectReferencesForSolutionDependencies>
<MSBuildEnableWorkloadResolver>false</MSBuildEnableWorkloadResolver>
Expand Down
2 changes: 1 addition & 1 deletion global.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"sdk": {
"version": "7.0.101",
"version": "8.0.101",
"rollForward": "latestFeature"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
<PropertyGroup>
<TargetFramework>net5.0</TargetFramework>
<IsPackable>True</IsPackable>
<LangVersion>10</LangVersion>
<RootNamespace>Avalonia.Controls</RootNamespace>
</PropertyGroup>
<PropertyGroup>
Expand Down
4 changes: 2 additions & 2 deletions src/Avalonia.Controls.TreeDataGrid/Selection/IndexRange.cs
Original file line number Diff line number Diff line change
Expand Up @@ -275,13 +275,13 @@ public static int Remove(
public static int Remove(
IList<IndexRange> destination,
IReadOnlyList<IndexRange> source,
IList<IndexRange>? added = null)
IList<IndexRange>? removed = null)
{
var result = 0;

foreach (var range in source)
{
result += Remove(destination, range, added);
result += Remove(destination, range, removed);
}

return result;
Expand Down
54 changes: 51 additions & 3 deletions src/Avalonia.Controls.TreeDataGrid/Selection/SelectionNodeBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -243,7 +243,7 @@ protected int CommitDeselect(int begin, int end)
/// assigning new indexes. Override this method to carry out additional computation when
/// items are added.
/// </remarks>
protected virtual CollectionChangeState OnItemsAdded(int index, IList items)
private protected CollectionChangeState OnItemsAdded(int index, IList items)
{
var count = items.Count;
var shifted = false;
Expand Down Expand Up @@ -305,7 +305,7 @@ protected virtual CollectionChangeState OnItemsAdded(int index, IList items)
/// assigning new indexes. Override this method to carry out additional computation when
/// items are removed.
/// </remarks>
private protected virtual CollectionChangeState OnItemsRemoved(int index, IList items)
private protected CollectionChangeState OnItemsRemoved(int index, IList items)
{
var count = items.Count;
var removedRange = new IndexRange(index, index + count - 1);
Expand Down Expand Up @@ -349,10 +349,58 @@ private protected virtual CollectionChangeState OnItemsRemoved(int index, IList
};
}

private protected IReadOnlyList<CollectionChangeState> OnItemsMoved(
int oldIndex,
int newIndex,
int count)
{
var selectedItemsMoved = false;
var unselectedItemsMoved = false;

if (_ranges is not null)
{
var removedRange = new IndexRange(oldIndex, oldIndex + count - 1);
var movedRanges = new List<IndexRange>();

if (IndexRange.Remove(_ranges, removedRange, movedRanges) > 0)
{
foreach (var range in movedRanges)
{
var insertRange = new IndexRange(
range.Begin + (newIndex - oldIndex),
range.End + (newIndex - oldIndex));
IndexRange.Add(_ranges, insertRange);
selectedItemsMoved = true;
}
}

for (var i = 0; i < Ranges!.Count; ++i)
{
var existing = Ranges[i];

if (existing.Begin >= oldIndex && existing.End < newIndex)
{
_ranges[i] = new IndexRange(existing.Begin - count, existing.End - count);
unselectedItemsMoved = true;
}
}
}

if (selectedItemsMoved || unselectedItemsMoved)
{
var changes = new List<CollectionChangeState>();
return changes;
}
else
{
return [];
}
}

/// <summary>
/// Details the results of a collection change on the current selection;
/// </summary>
protected class CollectionChangeState
private protected class CollectionChangeState
{
/// <summary>
/// Gets or sets the first index that was shifted as a result of the collection
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -271,7 +271,8 @@ internal void OnNodeCollectionChangeStarted()

internal void OnNodeCollectionChanged(
IndexPath parentIndex,
int shiftIndex,
int shiftStartIndex,
int shiftEndIndex,
int shiftDelta,
bool raiseIndexesChanged,
IReadOnlyList<T?>? removed)
Expand All @@ -285,13 +286,13 @@ internal void OnNodeCollectionChanged(
{
IndexesChanged?.Invoke(
this,
new TreeSelectionModelIndexesChangedEventArgs(parentIndex, shiftIndex, shiftDelta));
new TreeSelectionModelIndexesChangedEventArgs(parentIndex, shiftStartIndex, shiftEndIndex, shiftDelta));
}

// Shift or clear the selected and anchor indexes according to the shift index/delta.
var hadSelection = _selectedIndex != default;
var selectedIndexChanged = ShiftIndex(parentIndex, shiftIndex, shiftDelta, ref _selectedIndex);
var anchorIndexChanged = ShiftIndex(parentIndex, shiftIndex, shiftDelta, ref _anchorIndex);
var selectedIndexChanged = ShiftIndex(parentIndex, shiftStartIndex, shiftDelta, ref _selectedIndex);
var anchorIndexChanged = ShiftIndex(parentIndex, shiftStartIndex, shiftDelta, ref _anchorIndex);
var selectedItemChanged = false;

// Check that the selected index is still selected in the node. It can get
Expand Down Expand Up @@ -340,6 +341,13 @@ protected internal virtual void OnNodeCollectionReset(IndexPath parentIndex, int
selectedIndexChanged = selectedItemChanged = true;
}

// If the anchor index is invalid, clear it.
if (_anchorIndex != default && !TryGetItemAt(_anchorIndex, out _))
{
_anchorIndex = default;
anchorIndexChanged = true;
}

Count -= removeCount;
SourceReset?.Invoke(this, new TreeSelectionModelSourceResetEventArgs(parentIndex));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,41 @@

namespace Avalonia.Controls.Selection
{
/// <summary>
/// Holds data for the <see cref="ITreeSelectionModel.IndexesChanged"/> event.
/// </summary>
public class TreeSelectionModelIndexesChangedEventArgs : EventArgs
{
public TreeSelectionModelIndexesChangedEventArgs(IndexPath parentIndex, int startIndex, int delta)
public TreeSelectionModelIndexesChangedEventArgs(
IndexPath parentIndex,
int startIndex,
int endIndex,
int delta)
{
ParentIndex = parentIndex;
StartIndex = startIndex;
EndIndex = endIndex;
Delta = delta;
}

/// <summary>
/// Gets the index of the parent item.
/// </summary>
public IndexPath ParentIndex { get; }

/// <summary>
/// Gets the inclusive start index of the range of indexes that changed.
/// </summary>
public int StartIndex { get; }

/// <summary>
/// Gets the exclusive end index of the range of indexes that changed.
/// </summary>
public int EndIndex { get; }

/// <summary>
/// Gets the delta of the change; i.e. the number of indexes added or removed.
/// </summary>
public int Delta { get; }
}
}
40 changes: 27 additions & 13 deletions src/Avalonia.Controls.TreeDataGrid/Selection/TreeSelectionNode.cs
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,8 @@ protected override void OnSourceCollectionChangeStarted()

protected override void OnSourceCollectionChanged(NotifyCollectionChangedEventArgs e)
{
var shiftIndex = 0;
var shiftStartIndex = 0;
var shiftEndIndex = -1;
var shiftDelta = 0;
var indexesChanged = false;
List<T?>? removed = null;
Expand All @@ -132,25 +133,34 @@ protected override void OnSourceCollectionChanged(NotifyCollectionChangedEventAr
switch (e.Action)
{
case NotifyCollectionChangedAction.Add:
shiftIndex = e.NewStartingIndex;
shiftStartIndex = e.NewStartingIndex;
shiftDelta = e.NewItems!.Count;
indexesChanged = OnItemsAdded(shiftIndex, e.NewItems).ShiftDelta > 0;
indexesChanged = OnItemsAdded(shiftStartIndex, e.NewItems).ShiftDelta > 0;
break;
case NotifyCollectionChangedAction.Remove:
shiftIndex = e.OldStartingIndex;
shiftStartIndex = e.OldStartingIndex;
shiftDelta = -e.OldItems!.Count;
var change = OnItemsRemoved(shiftIndex, e.OldItems);
var change = OnItemsRemoved(shiftStartIndex, e.OldItems);
indexesChanged = change.ShiftDelta != 0;
removed = change.RemovedItems;
break;
case NotifyCollectionChangedAction.Replace:
var removeChange = OnItemsRemoved(e.OldStartingIndex, e.OldItems!);
var addChange = OnItemsAdded(e.NewStartingIndex, e.NewItems!);
shiftIndex = removeChange.ShiftIndex;
shiftStartIndex = removeChange.ShiftIndex;
shiftDelta = removeChange.ShiftDelta + addChange.ShiftDelta;
indexesChanged = shiftDelta != 0;
removed = removeChange.RemovedItems;
break;
case NotifyCollectionChangedAction.Move:
shiftStartIndex = Math.Min(e.OldStartingIndex, e.NewStartingIndex);
shiftEndIndex = Math.Max(e.OldStartingIndex, e.NewStartingIndex);
shiftDelta = e.OldStartingIndex < e.NewStartingIndex ? -e.OldItems!.Count : e.OldItems!.Count;
var moveRemoveChange = OnItemsRemoved(e.OldStartingIndex, e.OldItems!);
var moveAddChange = OnItemsAdded(e.NewStartingIndex, e.NewItems!);
indexesChanged = shiftDelta != 0;
removed = moveRemoveChange.RemovedItems;
break;
case NotifyCollectionChangedAction.Reset:
OnSourceReset();
break;
Expand All @@ -161,29 +171,33 @@ protected override void OnSourceCollectionChanged(NotifyCollectionChangedEventAr
// Adjust the paths of any child nodes.
if (_children?.Count > 0 && shiftDelta != 0)
{
for (var i = shiftIndex; i < _children.Count; ++i)
for (var i = shiftStartIndex; i < _children.Count; ++i)
{
var child = _children[i];

if (shiftDelta < 1 && i >= shiftIndex && i < shiftIndex - shiftDelta)
if (shiftDelta < 1 && i >= shiftStartIndex && i < shiftStartIndex - shiftDelta)
{
child?.AncestorRemoved(ref removed);
}
else
{
child?.AncestorIndexChanged(Path, shiftIndex, shiftDelta);
child?.AncestorIndexChanged(Path, shiftStartIndex, shiftDelta);
indexesChanged = true;
}
}

if (shiftDelta > 0)
_children.InsertMany(shiftIndex, null, shiftDelta);
_children.InsertMany(shiftStartIndex, null, shiftDelta);
else
_children.RemoveRange(shiftIndex, -shiftDelta);
_children.RemoveRange(shiftStartIndex, -shiftDelta);
}

if (shiftDelta != 0 || removed?.Count> 0)
_owner.OnNodeCollectionChanged(Path, shiftIndex, shiftDelta, indexesChanged, removed);
if (shiftDelta != 0 || removed?.Count > 0)
{
if (shiftEndIndex == -1)
shiftEndIndex = ItemsView?.Count ?? 0;
_owner.OnNodeCollectionChanged(Path, shiftStartIndex, shiftEndIndex, shiftDelta, indexesChanged, removed);
}
}

protected override void OnSourceCollectionChangeFinished()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1517,7 +1517,7 @@ public void Clearing_Children_Updates_State()
var target = CreateTarget(data);
var selectionChangedRaised = 0;
var sourceResetRaised = 0;
var indexesChangedraised = 0;
var indexesChangedRaised = 0;

target.Select(new IndexPath(0, 1));
target.Select(new IndexPath(0, 1, 0));
Expand All @@ -1528,7 +1528,7 @@ public void Clearing_Children_Updates_State()

target.SelectionChanged += (s, e) => ++selectionChangedRaised;
target.SourceReset += (s, e) => ++sourceResetRaised;
target.IndexesChanged += (s, e) => ++indexesChangedraised;
target.IndexesChanged += (s, e) => ++indexesChangedRaised;

data[0].Children!.Clear();

Expand All @@ -1538,7 +1538,7 @@ public void Clearing_Children_Updates_State()
Assert.Equal("Node 1-3", target.SelectedItem!.Caption);
Assert.Equal(new[] { "Node 1-3" }, target.SelectedItems.Select(x => x!.Caption));
Assert.Equal(new IndexPath(1, 3), target.AnchorIndex);
Assert.Equal(0, indexesChangedraised);
Assert.Equal(0, indexesChangedRaised);
Assert.Equal(0, selectionChangedRaised);
Assert.Equal(1, sourceResetRaised);
}
Expand Down
Loading

0 comments on commit ca2f993

Please sign in to comment.