refactor(ui): flatten TasksIsland's task list into one virtualized ListBox
Replace the three ItemsControl sections (Overdue/Open/Completed) with a single flat Rows collection (HeaderRow | TaskRowViewModel) bound to a ListBox + VirtualizingStackPanel, so the realized container count stays bounded instead of growing with the list (measured: ~7-8 realized containers at any scroll position with 1000 source rows). Group headers become regular row entries, each section is simply omitted from Rows when empty, and the Completed section's Clear-completed action moves onto the header row itself. SectionFor/ReorderAsync/FindNextInSameSection now read a row's section off the nearest preceding HeaderRow in Rows instead of three separate bound collections, and ScrollSelectedIntoView goes through ListBox.ScrollIntoView so it still works for rows outside the realized window. Drag/reorder behavior, grouping/counts, and the Clear-completed button are unchanged. Auto-scroll-while-dragging and the cross-section reorder bug are explicitly out of scope (follow-up tasks).
This commit is contained in:
@@ -1,5 +1,4 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
@@ -62,19 +61,14 @@ public partial class TasksIslandView : UserControl
|
||||
};
|
||||
}
|
||||
|
||||
// Bring the selected row into view — the task list is a plain ItemsControl with no
|
||||
// built-in selection scrolling, so a programmatic select (e.g. Mission Control's
|
||||
// "Open in app") would otherwise highlight a row that stays off-screen.
|
||||
// Bring the selected row into view — a programmatic select (e.g. Mission Control's "Open in
|
||||
// app") can target a row that isn't currently realized. RowsListBox.ScrollIntoView is
|
||||
// virtualization-aware (it realizes the container as needed); a visual-tree search for an
|
||||
// existing Button, as this used to do, only finds rows already on screen.
|
||||
private void ScrollSelectedIntoView()
|
||||
{
|
||||
if (DataContext is not TasksIslandViewModel vm || vm.SelectedTask is not { } target) return;
|
||||
Dispatcher.UIThread.Post(() =>
|
||||
{
|
||||
var match = this.GetVisualDescendants()
|
||||
.OfType<Button>()
|
||||
.FirstOrDefault(b => ReferenceEquals(b.DataContext, target));
|
||||
match?.BringIntoView();
|
||||
}, DispatcherPriority.Background);
|
||||
Dispatcher.UIThread.Post(() => RowsListBox.ScrollIntoView(target), DispatcherPriority.Background);
|
||||
}
|
||||
|
||||
private async System.Threading.Tasks.Task<bool> ShowConfirmAsync(string message)
|
||||
@@ -363,13 +357,12 @@ public partial class TasksIslandView : UserControl
|
||||
return null;
|
||||
}
|
||||
|
||||
// The next row in Rows is only "in the same section" while it's a TaskRowViewModel —
|
||||
// hitting a HeaderRow (or the end of the list) means row was the last one in its section.
|
||||
private static TaskRowViewModel? FindNextInSameSection(TasksIslandViewModel vm, TaskRowViewModel row)
|
||||
{
|
||||
foreach (var section in new[] { vm.OverdueItems, vm.OpenItems, vm.CompletedItems })
|
||||
{
|
||||
var idx = section.IndexOf(row);
|
||||
if (idx >= 0) return idx + 1 < section.Count ? section[idx + 1] : null;
|
||||
}
|
||||
return null;
|
||||
var idx = vm.Rows.IndexOf(row);
|
||||
if (idx < 0 || idx + 1 >= vm.Rows.Count) return null;
|
||||
return vm.Rows[idx + 1] as TaskRowViewModel;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user