Auto-scroll process queue

This commit is contained in:
MBucari
2025-12-29 19:36:15 -07:00
parent 31087c0855
commit 29a5c943cb
5 changed files with 94 additions and 2 deletions

View File

@@ -34,7 +34,7 @@
HorizontalScrollBarVisibility="Disabled"
VerticalScrollBarVisibility="Auto"
AllowAutoHide="False">
<ItemsControl ItemsSource="{Binding Queue}">
<ItemsControl Name="QueueListControl" ItemsSource="{Binding Queue}">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<VirtualizingStackPanel />

View File

@@ -1,6 +1,7 @@
using ApplicationServices;
using Avalonia.Controls;
using Avalonia.Data.Converters;
using Avalonia.Threading;
using DataLayer;
using LibationFileManager;
using LibationUiBase;
@@ -94,6 +95,42 @@ namespace LibationAvalonia.Views
#endregion
}
#region Auto-Scroll Current Item Into View
protected override void OnDataContextBeginUpdate()
{
if (DataContext is ProcessQueueViewModel vm)
{
vm.ProcessStart -= Book_ProcessStart;
}
base.OnDataContextBeginUpdate();
}
protected override void OnDataContextEndUpdate()
{
if (DataContext is ProcessQueueViewModel vm)
{
vm.ProcessStart += Book_ProcessStart;
}
base.OnDataContextEndUpdate();
}
private void Book_ProcessStart(object? sender, ProcessBookViewModel e)
{
Dispatcher.UIThread.Invoke(() =>
{
if (Queue?.IndexOf(e) is int newtBookIndex && newtBookIndex > 0 && QueueListControl.Presenter?.Panel is VirtualizingStackPanel panel && itemIsVisible(newtBookIndex - 1, panel))
{
// Only scroll the new item into view if the previous item is visible.
// This allows users to scroll through the queue without being interrupted.
QueueListControl.ScrollIntoView(newtBookIndex);
}
});
static bool itemIsVisible(int newtBookIndex, VirtualizingStackPanel panel)
=> panel.FirstRealizedIndex <= newtBookIndex && panel.LastRealizedIndex >= newtBookIndex;
}
#endregion
public void NumericUpDown_KeyDown(object sender, Avalonia.Input.KeyEventArgs e)
{
if (e.Key == Avalonia.Input.Key.Enter && sender is Avalonia.Input.IInputElement input) input.Focus();

View File

@@ -264,7 +264,8 @@ public class ProcessQueueViewModel : ReactiveObject
}
#endregion
public event EventHandler<ProcessBookViewModel>? ProcessStart;
public event EventHandler<ProcessBookViewModel>? ProcessEnd;
private async Task QueueLoop()
{
try
@@ -288,6 +289,7 @@ public class ProcessQueueViewModel : ReactiveObject
Serilog.Log.Logger.Information("Begin processing queued item: '{item_LibraryBook}'", nextBook.LibraryBook);
SpeedLimit = nextBook.Configuration.DownloadSpeedLimit / 1024m / 1024;
ProcessStart?.Invoke(this, nextBook);
var result = await nextBook.ProcessOneAsync();
Serilog.Log.Logger.Information("Completed processing queued item: '{item_LibraryBook}' with result: {result}", nextBook.LibraryBook, result);
@@ -310,6 +312,7 @@ public class ProcessQueueViewModel : ReactiveObject
MessageBoxIcon.Asterisk);
shownServiceOutageMessage = true;
}
ProcessEnd?.Invoke(this, nextBook);
}
Serilog.Log.Logger.Information("Completed processing queue");

View File

@@ -38,9 +38,26 @@ internal partial class ProcessQueueControl : UserControl
logDGV.EnableHeadersVisualStyles = !Application.IsDarkModeEnabled;
ViewModel.PropertyChanged += ProcessQueue_PropertyChanged;
ViewModel.LogEntries.CollectionChanged += LogEntries_CollectionChanged;
ViewModel.ProcessStart += Book_ProcessStart;
ProcessQueue_PropertyChanged(this, new PropertyChangedEventArgs(null));
}
private void Book_ProcessStart(object? sender, ProcessBookViewModel e)
{
Invoke(() =>
{
if (ViewModel.Queue?.IndexOf(e) is int newtBookIndex && newtBookIndex > 0 && itemIsVisible(newtBookIndex - 1))
{
// Only scroll the new item into view if the previous item is visible.
// This allows users to scroll through the queue without being interrupted.
virtualFlowControl2.ScrollIntoView(newtBookIndex);
}
});
bool itemIsVisible(int newtBookIndex)
=> virtualFlowControl2.FirstRealizedIndex <= newtBookIndex && virtualFlowControl2.LastRealizedIndex >= newtBookIndex;
}
private void LogEntries_CollectionChanged(object? sender, System.Collections.Specialized.NotifyCollectionChangedEventArgs e)
{
if (!IsDisposed && e.Action == System.Collections.Specialized.NotifyCollectionChangedAction.Add)

View File

@@ -14,6 +14,17 @@ namespace LibationWinForms.ProcessQueue
/// Triggered when one of the <see cref="ProcessBookControl"/>'s buttons has been clicked
/// </summary>
public event EventHandler<string>? ButtonClicked;
/// <summary>
/// Gets the index of the first realized element, or -1 if no elements are realized.
/// </summary>
public int FirstRealizedIndex { get; private set; } = -1;
/// <summary>
/// Gets the index of the last realized element, or -1 if no elements are realized.
/// </summary>
public int LastRealizedIndex { get; private set; } = -1;
public IList? Items { get; private set; }
private object? m_OldContext;
@@ -199,6 +210,27 @@ namespace LibationWinForms.ProcessQueue
}
}
/// <summary>
/// Scrolls the specified item into view.
/// </summary>
/// <param name="index">The index of the item.</param>
public void ScrollIntoView(int index)
{
if (index < 0 || index >= VirtualControlCount)
throw new ArgumentOutOfRangeException(nameof(index));
int firstVisible = FirstVisibleVirtualIndex;
int lastVisible = firstVisible + (DisplayHeight / VirtualControlHeight) - 1;
if (index < firstVisible)
{
SetScrollPosition(index * VirtualControlHeight);
}
else if (index > lastVisible)
{
int newScrollPos = (index - (lastVisible - firstVisible)) * VirtualControlHeight;
SetScrollPosition(newScrollPos);
}
}
/// <summary>
/// Calculated the virtual controls that are in view at the current scroll position and windows size,
/// positions <see cref="panel1"/> to simulate scroll activity, then fires updates the controls with
@@ -229,6 +261,9 @@ namespace LibationWinForms.ProcessQueue
{
BookControls[i].Visible = i < numVisible;
}
FirstRealizedIndex = firstVisible;
LastRealizedIndex = firstVisible + numVisible - 1;
}
/// <summary>