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(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(DependencyObject? current) where T : DependencyObject { while (current is not null) { if (current is T match) { return match; } current = VisualTreeHelper.GetParent(current); } return null; } }