Add Git overlay, operation tools, and virtualized preview so large folders stay responsive.
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
155
src/Explorer.App/FolderViewport.cs
Normal file
155
src/Explorer.App/FolderViewport.cs
Normal file
@@ -0,0 +1,155 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user