diff --git a/src/Ursa.Themes.Semi/Controls/Anchor.axaml b/src/Ursa.Themes.Semi/Controls/Anchor.axaml index 1145346..098f292 100644 --- a/src/Ursa.Themes.Semi/Controls/Anchor.axaml +++ b/src/Ursa.Themes.Semi/Controls/Anchor.axaml @@ -1,31 +1,60 @@ + xmlns:converters="clr-namespace:Ursa.Themes.Semi.Converters" + xmlns:iri="https://irihi.tech/shared" + xmlns:u="https://irihi.tech/ursa"> + - + + + + - - + + + - - + + + + + + + + + + + + + + - diff --git a/src/Ursa.Themes.Semi/Converters/NavigationMenuItemLevelToMarginConverter.cs b/src/Ursa.Themes.Semi/Converters/NavigationMenuItemLevelToMarginConverter.cs deleted file mode 100644 index bcacb57..0000000 --- a/src/Ursa.Themes.Semi/Converters/NavigationMenuItemLevelToMarginConverter.cs +++ /dev/null @@ -1,23 +0,0 @@ -using System.Globalization; -using Avalonia; -using Avalonia.Data.Converters; - -namespace Ursa.Themes.Semi.Converters; - -public class NavigationMenuItemLevelToMarginConverter: IMultiValueConverter -{ - public int Indent { get; set; } - - public object? Convert(IList values, Type targetType, object? parameter, CultureInfo culture) - { - if (values[0] is int i && values[1] is bool b) - { - if (b) - { - return new Thickness(); - } - return new Thickness((i-1) * Indent, 0, 0, 0); - } - return new Thickness(); - } -} \ No newline at end of file diff --git a/src/Ursa.Themes.Semi/Converters/TreeLevelToMarginConverter.cs b/src/Ursa.Themes.Semi/Converters/TreeLevelToMarginConverter.cs new file mode 100644 index 0000000..15ca142 --- /dev/null +++ b/src/Ursa.Themes.Semi/Converters/TreeLevelToMarginConverter.cs @@ -0,0 +1,17 @@ +using System.Globalization; +using Avalonia; +using Avalonia.Data.Converters; + +namespace Ursa.Themes.Semi.Converters; + +public class TreeLevelToMarginConverter: IMultiValueConverter +{ + public object? Convert(IList values, Type targetType, object? parameter, CultureInfo culture) + { + if (values[0] is int i && values[1] is double indent) + { + return new Thickness((i-1) * indent, 0, 0, 0); + } + return new Thickness(); + } +} \ No newline at end of file diff --git a/src/Ursa.Themes.Semi/Themes/Shared/Anchor.axaml b/src/Ursa.Themes.Semi/Themes/Shared/Anchor.axaml new file mode 100644 index 0000000..3f22c22 --- /dev/null +++ b/src/Ursa.Themes.Semi/Themes/Shared/Anchor.axaml @@ -0,0 +1,4 @@ + + 12 + diff --git a/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml b/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml index 01c7e1a..7a665f6 100644 --- a/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml +++ b/src/Ursa.Themes.Semi/Themes/Shared/_index.axaml @@ -1,6 +1,7 @@ + diff --git a/src/Ursa/Common/LogicalHelpers.cs b/src/Ursa/Common/LogicalHelpers.cs new file mode 100644 index 0000000..4a17487 --- /dev/null +++ b/src/Ursa/Common/LogicalHelpers.cs @@ -0,0 +1,21 @@ +using Avalonia.LogicalTree; +using Ursa.Controls; + +namespace Ursa.Common; + +public static class LogicalHelpers +{ + public static int CalculateDistanceFromLogicalParent(TItem? item, int @default = -1) + where T : class + where TItem : ILogical + { + var result = 0; + ILogical? logical = item; + while (logical is not null and not T) + { + if (logical is TItem) result++; + logical = logical.LogicalParent; + } + return item is not null ? result : @default; + } +} \ No newline at end of file diff --git a/src/Ursa/Controls/Anchor/Anchor.cs b/src/Ursa/Controls/Anchor/Anchor.cs index 5f3d5c8..7a75b93 100644 --- a/src/Ursa/Controls/Anchor/Anchor.cs +++ b/src/Ursa/Controls/Anchor/Anchor.cs @@ -1,4 +1,3 @@ -using System.Diagnostics; using Avalonia; using Avalonia.Animation; using Avalonia.Animation.Easings; @@ -47,7 +46,7 @@ public class Anchor: ItemsControl } private CancellationTokenSource _cts = new(); - private bool _scrollingFromSelection = false; + private bool _scrollingFromSelection; private void ScrollToAnchor(Visual target) { @@ -114,7 +113,7 @@ public class Anchor: ItemsControl { var anchorId = GetAnchorId(item); if (anchorId is null) continue; - var position = item.TransformToVisual(TargetContainer)?.M32; + var position = item.TransformToVisual(TargetContainer)?.M32 + TargetContainer.Offset.Y; if (position.HasValue) { positions.Add((anchorId, position.Value)); @@ -127,22 +126,14 @@ public class Anchor: ItemsControl protected override void OnLoaded(RoutedEventArgs e) { base.OnLoaded(e); - var items = this.GetVisualDescendants().OfType().ToList(); var target = this.TargetContainer; if (target is null) return; - var targetItems = target.GetVisualDescendants().Where(a => Anchor.GetAnchorId(a) is not null).ToList(); - var tops = targetItems.Select(a => (a.TransformToVisual(target)?.M32, GetAnchorId(a))); - var isloaded = TargetContainer?.IsLoaded; TargetContainer?.AddHandler(ScrollViewer.ScrollChangedEvent, OnScrollChanged); - if (isloaded is true) + TargetContainer?.AddHandler(LoadedEvent, OnTargetContainerLoaded); + if (TargetContainer?.IsLoaded == true) { InvalidateAnchorPositions(); } - else - { - TargetContainer.Loaded += (s, args) => InvalidateAnchorPositions(); - } - MarkSelectedContainerByPosition(); } @@ -159,7 +150,7 @@ public class Anchor: ItemsControl if (source is null) return; MarkSelectedContainer(source); var target = TargetContainer?.GetVisualDescendants() - .FirstOrDefault(a => Anchor.GetAnchorId(a) == source?.AnchorId); + .FirstOrDefault(a => Anchor.GetAnchorId(a) == source.AnchorId); if (target is null) return; ScrollToAnchor(target); } @@ -187,7 +178,7 @@ public class Anchor: ItemsControl ContainerForItemPreparedOverride(container, item, index); } - internal AnchorItem? _selectedContainer; + private AnchorItem? _selectedContainer; internal void MarkSelectedContainer(AnchorItem? item) { @@ -210,5 +201,15 @@ public class Anchor: ItemsControl if (item is null) return; MarkSelectedContainer(item); } - + + protected override void OnUnloaded(RoutedEventArgs e) + { + base.OnUnloaded(e); + TargetContainer?.RemoveHandler(UnloadedEvent, OnTargetContainerLoaded); + } + + private void OnTargetContainerLoaded(object? sender, RoutedEventArgs e) + { + InvalidateAnchorPositions(); + } } \ No newline at end of file diff --git a/src/Ursa/Controls/Anchor/AnchorItem.cs b/src/Ursa/Controls/Anchor/AnchorItem.cs index 381e284..6986d6f 100644 --- a/src/Ursa/Controls/Anchor/AnchorItem.cs +++ b/src/Ursa/Controls/Anchor/AnchorItem.cs @@ -3,8 +3,8 @@ using Avalonia.Controls; using Avalonia.Controls.Mixins; using Avalonia.Controls.Primitives; using Avalonia.Controls.Templates; -using Avalonia.Input; using Avalonia.LogicalTree; +using Ursa.Common; namespace Ursa.Controls; @@ -19,6 +19,12 @@ public class AnchorItem : HeaderedItemsControl, ISelectable private static readonly FuncTemplate DefaultPanel = new(() => new StackPanel()); + internal static readonly DirectProperty LevelProperty = + AvaloniaProperty.RegisterDirect( + nameof(Level), o => o.Level, (o, v) => o.Level = v); + + private int _level; + private Anchor? _root; static AnchorItem() @@ -28,6 +34,12 @@ public class AnchorItem : HeaderedItemsControl, ISelectable ItemsPanelProperty.OverrideDefaultValue(DefaultPanel); } + public int Level + { + get => _level; + set => SetAndRaise(LevelProperty, ref _level, value); + } + public string? AnchorId { get => GetValue(AnchorIdProperty); @@ -44,17 +56,14 @@ public class AnchorItem : HeaderedItemsControl, ISelectable { base.OnAttachedToVisualTree(e); _root = this.GetLogicalAncestors().OfType().FirstOrDefault(); + Level = LogicalHelpers.CalculateDistanceFromLogicalParent(this); if (ItemTemplate is null && _root?.ItemTemplate is not null) - { SetCurrentValue(ItemTemplateProperty, _root.ItemTemplate); - } if (ItemContainerTheme is null && _root?.ItemContainerTheme is not null) - { SetCurrentValue(ItemContainerThemeProperty, _root.ItemContainerTheme); - } } - + protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey) { return EnsureRoot().CreateContainerForItemOverride_INTERNAL(item, index, recycleKey);