diff --git a/src/Ursa.Themes.Semi/Controls/Anchor.axaml b/src/Ursa.Themes.Semi/Controls/Anchor.axaml index 098f292..108f37b 100644 --- a/src/Ursa.Themes.Semi/Controls/Anchor.axaml +++ b/src/Ursa.Themes.Semi/Controls/Anchor.axaml @@ -6,11 +6,14 @@ xmlns:u="https://irihi.tech/ursa"> + + @@ -21,23 +24,30 @@ + - + + - - + @@ -53,8 +63,24 @@ - + + + + + diff --git a/src/Ursa.Themes.Semi/Themes/Shared/Anchor.axaml b/src/Ursa.Themes.Semi/Themes/Shared/Anchor.axaml index 3f22c22..cffb17e 100644 --- a/src/Ursa.Themes.Semi/Themes/Shared/Anchor.axaml +++ b/src/Ursa.Themes.Semi/Themes/Shared/Anchor.axaml @@ -1,4 +1,6 @@ 12 + 20 + 16 diff --git a/src/Ursa/Controls/Anchor/Anchor.cs b/src/Ursa/Controls/Anchor/Anchor.cs index 7a75b93..2ecf018 100644 --- a/src/Ursa/Controls/Anchor/Anchor.cs +++ b/src/Ursa/Controls/Anchor/Anchor.cs @@ -11,15 +11,27 @@ using Ursa.Common; namespace Ursa.Controls; /// -/// 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. +/// 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 class Anchor : ItemsControl { - public static readonly StyledProperty TargetContainerProperty = AvaloniaProperty.Register( - nameof(TargetContainer)); + public static readonly StyledProperty TargetContainerProperty = + AvaloniaProperty.Register( + nameof(TargetContainer)); + + public static readonly AttachedProperty AnchorIdProperty = + AvaloniaProperty.RegisterAttached("AnchorId"); + + private CancellationTokenSource _cts = new(); + + private List<(string, double)> _positions = []; + private bool _scrollingFromSelection; + + private AnchorItem? _selectedContainer; public ScrollViewer? TargetContainer { @@ -27,12 +39,16 @@ public class Anchor: ItemsControl set => SetValue(TargetContainerProperty, value); } - public static readonly AttachedProperty AnchorIdProperty = - AvaloniaProperty.RegisterAttached("AnchorId"); + public static void SetAnchorId(Visual obj, string? value) + { + obj.SetValue(AnchorIdProperty, value); + } + + public static string? GetAnchorId(Visual obj) + { + return obj.GetValue(AnchorIdProperty); + } - public static void SetAnchorId(Visual obj, string? value) => obj.SetValue(AnchorIdProperty, value); - public static string? GetAnchorId(Visual obj) => obj.GetValue(AnchorIdProperty); - protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey) { @@ -45,80 +61,65 @@ public class Anchor: ItemsControl return i; } - private CancellationTokenSource _cts = new(); - private bool _scrollingFromSelection; - private void ScrollToAnchor(Visual target) { if (TargetContainer is null) return; - + var targetPosition = target.TranslatePoint(new Point(0, 0), TargetContainer); if (targetPosition.HasValue) { var from = TargetContainer.Offset.Y; var to = TargetContainer.Offset.Y + targetPosition.Value.Y; - if(to > TargetContainer.Extent.Height - TargetContainer.Bounds.Height) - { + if (to > TargetContainer.Extent.Height - TargetContainer.Bounds.Height) to = TargetContainer.Extent.Height - TargetContainer.Bounds.Height; - } if (from == to) return; - Animation animation = new Animation() + var animation = new Animation { Duration = TimeSpan.FromSeconds(0.3), Easing = new QuadraticEaseOut(), Children = { - new KeyFrame(){ - Setters = - { - new Setter(ScrollViewer.OffsetProperty, new Vector(0, from)), - }, + new KeyFrame + { + Setters = { new Setter(ScrollViewer.OffsetProperty, new Vector(0, from)) }, Cue = new Cue(0.0) }, - new KeyFrame() + new KeyFrame { - Setters = - { - new Setter(ScrollViewer.OffsetProperty, new Vector(0, to)) - }, + Setters = { new Setter(ScrollViewer.OffsetProperty, new Vector(0, to)) }, Cue = new Cue(1.0) } - } }; _cts.Cancel(); _cts = new CancellationTokenSource(); - var token = _cts.Token; + var token = _cts.Token; token.Register(_ => _scrollingFromSelection = false, null); _scrollingFromSelection = true; animation.RunAsync(TargetContainer, token).ContinueWith(_ => _scrollingFromSelection = false, token); } } - + public void InvalidatePositions() { InvalidateAnchorPositions(); MarkSelectedContainerByPosition(); } - private List<(string, double)> _positions = []; - internal void InvalidateAnchorPositions() { if (TargetContainer is null) return; var items = TargetContainer.GetVisualDescendants().Where(a => GetAnchorId(a) is not null); - List<(string, double)> positions = new List<(string, double)>(); + var positions = new List<(string, double)>(); foreach (var item in items) { var anchorId = GetAnchorId(item); if (anchorId is null) continue; var position = item.TransformToVisual(TargetContainer)?.M32 + TargetContainer.Offset.Y; - if (position.HasValue) - { - positions.Add((anchorId, position.Value)); - } + if (position.HasValue) positions.Add((anchorId, position.Value)); } + positions.Sort((a, b) => a.Item2.CompareTo(b.Item2)); _positions = positions; } @@ -126,14 +127,11 @@ public class Anchor: ItemsControl protected override void OnLoaded(RoutedEventArgs e) { base.OnLoaded(e); - var target = this.TargetContainer; + var target = TargetContainer; if (target is null) return; TargetContainer?.AddHandler(ScrollViewer.ScrollChangedEvent, OnScrollChanged); TargetContainer?.AddHandler(LoadedEvent, OnTargetContainerLoaded); - if (TargetContainer?.IsLoaded == true) - { - InvalidateAnchorPositions(); - } + if (TargetContainer?.IsLoaded == true) InvalidateAnchorPositions(); MarkSelectedContainerByPosition(); } @@ -150,24 +148,24 @@ public class Anchor: ItemsControl if (source is null) return; MarkSelectedContainer(source); var target = TargetContainer?.GetVisualDescendants() - .FirstOrDefault(a => Anchor.GetAnchorId(a) == source.AnchorId); + .FirstOrDefault(a => 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. + /// 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); } - + internal bool NeedsContainerOverride_INTERNAL(object? item, int index, out object? recycleKey) { return NeedsContainerOverride(item, index, out recycleKey); } - + internal void PrepareContainerForItemOverride_INTERNAL(Control container, object? item, int index) { PrepareContainerForItemOverride(container, item, index); @@ -178,8 +176,6 @@ public class Anchor: ItemsControl ContainerForItemPreparedOverride(container, item, index); } - private AnchorItem? _selectedContainer; - internal void MarkSelectedContainer(AnchorItem? item) { var oldValue = _selectedContainer; @@ -197,7 +193,7 @@ public class Anchor: ItemsControl var topAnchorId = _positions.LastOrDefault(a => a.Item2 <= top).Item1; if (topAnchorId is null) return; var item = this.GetVisualDescendants().OfType() - .FirstOrDefault(a => a.AnchorId == topAnchorId); + .FirstOrDefault(a => a.AnchorId == topAnchorId); if (item is null) return; MarkSelectedContainer(item); } diff --git a/src/Ursa/Controls/Anchor/AnchorItem.cs b/src/Ursa/Controls/Anchor/AnchorItem.cs index 6986d6f..17f6917 100644 --- a/src/Ursa/Controls/Anchor/AnchorItem.cs +++ b/src/Ursa/Controls/Anchor/AnchorItem.cs @@ -31,7 +31,7 @@ public class AnchorItem : HeaderedItemsControl, ISelectable { SelectableMixin.Attach(IsSelectedProperty); PressedMixin.Attach(); - ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); + ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); } public int Level