156 lines
4.4 KiB
C#
156 lines
4.4 KiB
C#
using System.Windows;
|
|
using System.Windows.Controls;
|
|
using System.Windows.Media;
|
|
using Explorer.Presentation;
|
|
using Explorer.Presentation.ViewModels;
|
|
|
|
namespace Explorer.App;
|
|
|
|
public static class FolderViewport
|
|
{
|
|
public static readonly DependencyProperty IsTrackedProperty =
|
|
DependencyProperty.RegisterAttached(
|
|
"IsTracked",
|
|
typeof(bool),
|
|
typeof(FolderViewport),
|
|
new PropertyMetadata(false, OnTrackedChanged));
|
|
|
|
public static void SetIsTracked(ListView element, bool value) => element.SetValue(IsTrackedProperty, value);
|
|
|
|
public static bool GetIsTracked(ListView element) => (bool)element.GetValue(IsTrackedProperty);
|
|
|
|
private static void OnTrackedChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
|
|
{
|
|
if (d is not ListView list)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (e.NewValue is true)
|
|
{
|
|
list.AddHandler(ScrollViewer.ScrollChangedEvent, new ScrollChangedEventHandler(OnScrollChanged), true);
|
|
list.Loaded += OnLoaded;
|
|
}
|
|
else
|
|
{
|
|
list.RemoveHandler(ScrollViewer.ScrollChangedEvent, new ScrollChangedEventHandler(OnScrollChanged));
|
|
list.Loaded -= OnLoaded;
|
|
}
|
|
}
|
|
|
|
private static void OnLoaded(object sender, RoutedEventArgs e) => Report(sender as ListView);
|
|
|
|
private static void OnScrollChanged(object sender, ScrollChangedEventArgs e)
|
|
{
|
|
var list = sender as ListView ?? FindAncestor<ListView>(e.OriginalSource as DependencyObject);
|
|
Report(list);
|
|
}
|
|
|
|
private static void Report(ListView? list)
|
|
{
|
|
if (list?.DataContext is not ExplorerPaneViewModel pane || list.Visibility != Visibility.Visible)
|
|
{
|
|
return;
|
|
}
|
|
|
|
var count = list.Items.Count;
|
|
if (count == 0)
|
|
{
|
|
pane.NotifyViewport([]);
|
|
return;
|
|
}
|
|
|
|
int first;
|
|
int lastExclusive;
|
|
if (FindWrapPanel(list) is { } wrap)
|
|
{
|
|
first = wrap.FirstVisibleIndex;
|
|
lastExclusive = Math.Max(first, wrap.LastVisibleIndex);
|
|
}
|
|
else
|
|
{
|
|
var viewer = FindScrollViewer(list);
|
|
int visible;
|
|
if (viewer is null || viewer.ExtentHeight <= 0)
|
|
{
|
|
first = 0;
|
|
visible = Math.Min(80, count);
|
|
}
|
|
else
|
|
{
|
|
first = (int)Math.Clamp(Math.Floor(viewer.VerticalOffset / viewer.ExtentHeight * count), 0, count - 1);
|
|
visible = Math.Max(8, (int)Math.Ceiling(viewer.ViewportHeight / viewer.ExtentHeight * count) + 8);
|
|
}
|
|
|
|
lastExclusive = Math.Min(count, first + visible);
|
|
}
|
|
|
|
lastExclusive = Math.Clamp(lastExclusive, 0, count);
|
|
first = Math.Clamp(first, 0, count);
|
|
var paths = new string[Math.Max(0, lastExclusive - first)];
|
|
for (var i = first; i < lastExclusive; i++)
|
|
{
|
|
if (list.Items[i] is FolderItemViewModel item)
|
|
{
|
|
paths[i - first] = item.FullPath;
|
|
}
|
|
}
|
|
|
|
pane.NotifyViewport(paths);
|
|
}
|
|
|
|
private static ScrollViewer? FindScrollViewer(DependencyObject root)
|
|
{
|
|
if (root is ScrollViewer viewer)
|
|
{
|
|
return viewer;
|
|
}
|
|
|
|
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(root); i++)
|
|
{
|
|
var found = FindScrollViewer(VisualTreeHelper.GetChild(root, i));
|
|
if (found is not null)
|
|
{
|
|
return found;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static VirtualizingWrapPanel? FindWrapPanel(DependencyObject root)
|
|
{
|
|
if (root is VirtualizingWrapPanel wrap)
|
|
{
|
|
return wrap;
|
|
}
|
|
|
|
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(root); i++)
|
|
{
|
|
var found = FindWrapPanel(VisualTreeHelper.GetChild(root, i));
|
|
if (found is not null)
|
|
{
|
|
return found;
|
|
}
|
|
}
|
|
|
|
return null;
|
|
}
|
|
|
|
private static T? FindAncestor<T>(DependencyObject? current)
|
|
where T : DependencyObject
|
|
{
|
|
while (current is not null)
|
|
{
|
|
if (current is T match)
|
|
{
|
|
return match;
|
|
}
|
|
|
|
current = VisualTreeHelper.GetParent(current);
|
|
}
|
|
|
|
return null;
|
|
}
|
|
}
|