From 85605b5faf5d4dbef53dc49050ddbddb0da6e691 Mon Sep 17 00:00:00 2001 From: Dong Bin Date: Sat, 5 Jul 2025 01:55:17 +0800 Subject: [PATCH] feat: implement scroll detection. --- src/Ursa.Themes.Semi/Controls/Anchor.axaml | 8 +++- src/Ursa/Common/VisualHelpers.cs | 13 ++++++ src/Ursa/Controls/Anchor/Anchor.cs | 47 ++++++++++++++++++++-- src/Ursa/Controls/Anchor/AnchorItem.cs | 1 + 4 files changed, 63 insertions(+), 6 deletions(-) create mode 100644 src/Ursa/Common/VisualHelpers.cs diff --git a/src/Ursa.Themes.Semi/Controls/Anchor.axaml b/src/Ursa.Themes.Semi/Controls/Anchor.axaml index 510c98e..1145346 100644 --- a/src/Ursa.Themes.Semi/Controls/Anchor.axaml +++ b/src/Ursa.Themes.Semi/Controls/Anchor.axaml @@ -1,7 +1,8 @@ + xmlns:u="https://irihi.tech/ursa" + xmlns:iri="https://irihi.tech/shared"> @@ -18,10 +19,13 @@ - + + diff --git a/src/Ursa/Common/VisualHelpers.cs b/src/Ursa/Common/VisualHelpers.cs new file mode 100644 index 0000000..37195ac --- /dev/null +++ b/src/Ursa/Common/VisualHelpers.cs @@ -0,0 +1,13 @@ +using Avalonia; +using Avalonia.VisualTree; + +namespace Ursa.Common; + +public static class VisualHelpers +{ + public static T? GetContainerFromEventSource(this Visual? source) + { + var item = source.GetSelfAndVisualAncestors().OfType().FirstOrDefault(); + return item; + } +} \ No newline at end of file diff --git a/src/Ursa/Controls/Anchor/Anchor.cs b/src/Ursa/Controls/Anchor/Anchor.cs index 966a894..58f1d82 100644 --- a/src/Ursa/Controls/Anchor/Anchor.cs +++ b/src/Ursa/Controls/Anchor/Anchor.cs @@ -1,15 +1,23 @@ +using System.Diagnostics; using Avalonia; using Avalonia.Animation; using Avalonia.Animation.Easings; using Avalonia.Controls; -using Avalonia.Controls.Primitives; +using Avalonia.Input; using Avalonia.Interactivity; using Avalonia.Styling; using Avalonia.VisualTree; +using Ursa.Common; namespace Ursa.Controls; -public class Anchor: SelectingItemsControl +/// +/// Some basic assumptions: This should not be a regular SelectingItemsControl, because it does not support multiple selections. +/// Selection should not be exposed to the user, it is only used to determine which item is currently selected. +/// The manipulation of container selection should be simplified. +/// Scroll event of TargetContainer also triggers selection change. +/// +public class Anchor: ItemsControl { public static readonly StyledProperty TargetContainerProperty = AvaloniaProperty.Register( nameof(TargetContainer)); @@ -40,6 +48,7 @@ public class Anchor: SelectingItemsControl internal void ScrollToAnchor(string anchorId) { + return; if (TargetContainer is null) return; var target = TargetContainer.GetVisualDescendants().FirstOrDefault(a=>Anchor.GetAnchorId(a) == anchorId); @@ -48,11 +57,13 @@ public class Anchor: SelectingItemsControl } private CancellationTokenSource _cts = new(); + private bool _scrollingFromSelection = false; private void ScrollToAnchor(Visual target) { if (TargetContainer is null) return; + var targetPosition = target.TranslatePoint(new Point(0, 0), TargetContainer); if (targetPosition.HasValue) { @@ -89,7 +100,10 @@ public class Anchor: SelectingItemsControl }; _cts.Cancel(); _cts = new CancellationTokenSource(); - animation.RunAsync(TargetContainer, _cts.Token); + var token = _cts.Token; + token.Register(_ => _scrollingFromSelection = false, null); + _scrollingFromSelection = true; + animation.RunAsync(TargetContainer, token).ContinueWith(_ => _scrollingFromSelection = false, token); } } @@ -112,9 +126,30 @@ public class Anchor: SelectingItemsControl private void OnScrollChanged(object? sender, ScrollChangedEventArgs e) { - + if (_scrollingFromSelection) return; + Debug.WriteLine("Scroll changed"); } + protected override void OnPointerPressed(PointerPressedEventArgs e) + { + base.OnPointerPressed(e); + var source = (e.Source as Visual).GetContainerFromEventSource(); + if (source is null) return; + if (_selectedContainer is not null) + { + _selectedContainer.IsSelected = false; + } + source.IsSelected = true; + _selectedContainer = source; + var target = TargetContainer?.GetVisualDescendants() + .FirstOrDefault(a => Anchor.GetAnchorId(a) == source?.AnchorId); + if (target is null) return; + ScrollToAnchor(target); + } + + /// + /// This method is used to expose the protected CreateContainerForItemOverride method to the AnchorItem class. + /// internal Control CreateContainerForItemOverride_INTERNAL(object? item, int index, object? recycleKey) { return CreateContainerForItemOverride(item, index, recycleKey); @@ -134,4 +169,8 @@ public class Anchor: SelectingItemsControl { ContainerForItemPreparedOverride(container, item, index); } + + internal AnchorItem? _selectedContainer; + + } \ No newline at end of file diff --git a/src/Ursa/Controls/Anchor/AnchorItem.cs b/src/Ursa/Controls/Anchor/AnchorItem.cs index 446797c..dfbd522 100644 --- a/src/Ursa/Controls/Anchor/AnchorItem.cs +++ b/src/Ursa/Controls/Anchor/AnchorItem.cs @@ -59,6 +59,7 @@ public class AnchorItem : HeaderedItemsControl, ISelectable { // var item = new TreeViewItem(); base.OnPointerPressed(e); + return; if (e.Handled) return; if (_root is null) return;