feat: add inheritance.
This commit is contained in:
@@ -11,15 +11,27 @@ using Ursa.Common;
|
||||
namespace Ursa.Controls;
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
public class Anchor: ItemsControl
|
||||
public class Anchor : ItemsControl
|
||||
{
|
||||
public static readonly StyledProperty<ScrollViewer?> TargetContainerProperty = AvaloniaProperty.Register<Anchor, ScrollViewer?>(
|
||||
nameof(TargetContainer));
|
||||
public static readonly StyledProperty<ScrollViewer?> TargetContainerProperty =
|
||||
AvaloniaProperty.Register<Anchor, ScrollViewer?>(
|
||||
nameof(TargetContainer));
|
||||
|
||||
public static readonly AttachedProperty<string?> AnchorIdProperty =
|
||||
AvaloniaProperty.RegisterAttached<Anchor, Visual, string?>("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<string?> AnchorIdProperty =
|
||||
AvaloniaProperty.RegisterAttached<Anchor, Visual, string?>("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);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 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.
|
||||
/// </summary>
|
||||
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<AnchorItem>()
|
||||
.FirstOrDefault(a => a.AnchorId == topAnchorId);
|
||||
.FirstOrDefault(a => a.AnchorId == topAnchorId);
|
||||
if (item is null) return;
|
||||
MarkSelectedContainer(item);
|
||||
}
|
||||
|
||||
@@ -31,7 +31,7 @@ public class AnchorItem : HeaderedItemsControl, ISelectable
|
||||
{
|
||||
SelectableMixin.Attach<AnchorItem>(IsSelectedProperty);
|
||||
PressedMixin.Attach<AnchorItem>();
|
||||
ItemsPanelProperty.OverrideDefaultValue<TreeViewItem>(DefaultPanel);
|
||||
ItemsPanelProperty.OverrideDefaultValue<AnchorItem>(DefaultPanel);
|
||||
}
|
||||
|
||||
public int Level
|
||||
|
||||
Reference in New Issue
Block a user