feat: update selection handling, fix initial offset calculation.
This commit is contained in:
@@ -1,31 +1,60 @@
|
|||||||
<ResourceDictionary
|
<ResourceDictionary
|
||||||
xmlns="https://github.com/avaloniaui"
|
xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
xmlns:u="https://irihi.tech/ursa"
|
xmlns:converters="clr-namespace:Ursa.Themes.Semi.Converters"
|
||||||
xmlns:iri="https://irihi.tech/shared">
|
xmlns:iri="https://irihi.tech/shared"
|
||||||
|
xmlns:u="https://irihi.tech/ursa">
|
||||||
|
<converters:TreeLevelToMarginConverter x:Key="LevelToMarginConverter" />
|
||||||
<ControlTheme x:Key="{x:Type u:Anchor}" TargetType="{x:Type u:Anchor}">
|
<ControlTheme x:Key="{x:Type u:Anchor}" TargetType="{x:Type u:Anchor}">
|
||||||
<Setter Property="Template">
|
<Setter Property="Template">
|
||||||
<ControlTemplate>
|
<ControlTemplate>
|
||||||
<ItemsPresenter
|
<Panel>
|
||||||
Name="PART_ItemsPresenter"
|
<Rectangle
|
||||||
Margin="{TemplateBinding Padding}"
|
Width="1"
|
||||||
ItemsPanel="{TemplateBinding ItemsPanel}" />
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Stretch"
|
||||||
|
Fill="{DynamicResource SemiColorBorder}" />
|
||||||
|
<ItemsPresenter
|
||||||
|
Name="PART_ItemsPresenter"
|
||||||
|
Margin="{TemplateBinding Padding}"
|
||||||
|
ItemsPanel="{TemplateBinding ItemsPanel}" />
|
||||||
|
</Panel>
|
||||||
</ControlTemplate>
|
</ControlTemplate>
|
||||||
</Setter>
|
</Setter>
|
||||||
</ControlTheme>
|
</ControlTheme>
|
||||||
|
|
||||||
<ControlTheme x:Key="{x:Type u:AnchorItem}" TargetType="u:AnchorItem" >
|
<ControlTheme x:Key="{x:Type u:AnchorItem}" TargetType="u:AnchorItem">
|
||||||
<Setter Property="Background" Value="Transparent" />
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="MinHeight" Value="20" />
|
||||||
<Setter Property="Template">
|
<Setter Property="Template">
|
||||||
<ControlTemplate TargetType="u:AnchorItem">
|
<ControlTemplate TargetType="u:AnchorItem">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<ContentPresenter Name="{x:Static iri:PartNames.PART_HeaderPresenter}" Content="{TemplateBinding Header}" ContentTemplate="{TemplateBinding HeaderTemplate}" />
|
<Panel>
|
||||||
<ItemsPresenter Margin="8 0 0 0" ItemsPanel="{TemplateBinding ItemsPanel}" />
|
<Rectangle
|
||||||
|
Name="PART_Pipe"
|
||||||
|
Width="2"
|
||||||
|
HorizontalAlignment="Left"
|
||||||
|
VerticalAlignment="Stretch" />
|
||||||
|
<Panel Margin="8,0,0,0">
|
||||||
|
<ContentPresenter
|
||||||
|
Name="{x:Static iri:PartNames.PART_HeaderPresenter}"
|
||||||
|
Content="{TemplateBinding Header}"
|
||||||
|
ContentTemplate="{TemplateBinding HeaderTemplate}">
|
||||||
|
<ContentPresenter.Margin>
|
||||||
|
<MultiBinding Converter="{StaticResource LevelToMarginConverter}">
|
||||||
|
<Binding Path="Level" RelativeSource="{RelativeSource AncestorType={x:Type u:AnchorItem}}" />
|
||||||
|
<DynamicResource ResourceKey="AnchorIndent" />
|
||||||
|
</MultiBinding>
|
||||||
|
</ContentPresenter.Margin>
|
||||||
|
</ContentPresenter>
|
||||||
|
</Panel>
|
||||||
|
</Panel>
|
||||||
|
<ItemsPresenter ItemsPanel="{TemplateBinding ItemsPanel}" />
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ControlTemplate>
|
</ControlTemplate>
|
||||||
</Setter>
|
</Setter>
|
||||||
<Style Selector="^:selected /template/ ContentPresenter#PART_HeaderPresenter">
|
<Style Selector="^:selected /template/ Rectangle#PART_Pipe">
|
||||||
<Setter Property="Foreground" Value="{DynamicResource SemiBlue5}" />
|
<Setter Property="Fill" Value="{DynamicResource SemiBlue5}" />
|
||||||
</Style>
|
</Style>
|
||||||
</ControlTheme>
|
</ControlTheme>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -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<object?> 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();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -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<object?> 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();
|
||||||
|
}
|
||||||
|
}
|
||||||
4
src/Ursa.Themes.Semi/Themes/Shared/Anchor.axaml
Normal file
4
src/Ursa.Themes.Semi/Themes/Shared/Anchor.axaml
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
|
<x:Double x:Key="AnchorIndent">12</x:Double>
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
<ResourceDictionary xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
<ResourceDictionary xmlns="https://github.com/avaloniaui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
<ResourceDictionary.MergedDictionaries>
|
<ResourceDictionary.MergedDictionaries>
|
||||||
<ResourceInclude Source="Avatar.axaml" />
|
<ResourceInclude Source="Avatar.axaml" />
|
||||||
|
<ResourceInclude Source="Anchor.axaml" />
|
||||||
<ResourceInclude Source="Badge.axaml" />
|
<ResourceInclude Source="Badge.axaml" />
|
||||||
<ResourceInclude Source="Banner.axaml" />
|
<ResourceInclude Source="Banner.axaml" />
|
||||||
<ResourceInclude Source="ButtonGroup.axaml" />
|
<ResourceInclude Source="ButtonGroup.axaml" />
|
||||||
|
|||||||
21
src/Ursa/Common/LogicalHelpers.cs
Normal file
21
src/Ursa/Common/LogicalHelpers.cs
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
using Avalonia.LogicalTree;
|
||||||
|
using Ursa.Controls;
|
||||||
|
|
||||||
|
namespace Ursa.Common;
|
||||||
|
|
||||||
|
public static class LogicalHelpers
|
||||||
|
{
|
||||||
|
public static int CalculateDistanceFromLogicalParent<T, TItem>(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;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,4 +1,3 @@
|
|||||||
using System.Diagnostics;
|
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Animation;
|
using Avalonia.Animation;
|
||||||
using Avalonia.Animation.Easings;
|
using Avalonia.Animation.Easings;
|
||||||
@@ -47,7 +46,7 @@ public class Anchor: ItemsControl
|
|||||||
}
|
}
|
||||||
|
|
||||||
private CancellationTokenSource _cts = new();
|
private CancellationTokenSource _cts = new();
|
||||||
private bool _scrollingFromSelection = false;
|
private bool _scrollingFromSelection;
|
||||||
|
|
||||||
private void ScrollToAnchor(Visual target)
|
private void ScrollToAnchor(Visual target)
|
||||||
{
|
{
|
||||||
@@ -114,7 +113,7 @@ public class Anchor: ItemsControl
|
|||||||
{
|
{
|
||||||
var anchorId = GetAnchorId(item);
|
var anchorId = GetAnchorId(item);
|
||||||
if (anchorId is null) continue;
|
if (anchorId is null) continue;
|
||||||
var position = item.TransformToVisual(TargetContainer)?.M32;
|
var position = item.TransformToVisual(TargetContainer)?.M32 + TargetContainer.Offset.Y;
|
||||||
if (position.HasValue)
|
if (position.HasValue)
|
||||||
{
|
{
|
||||||
positions.Add((anchorId, position.Value));
|
positions.Add((anchorId, position.Value));
|
||||||
@@ -127,22 +126,14 @@ public class Anchor: ItemsControl
|
|||||||
protected override void OnLoaded(RoutedEventArgs e)
|
protected override void OnLoaded(RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnLoaded(e);
|
base.OnLoaded(e);
|
||||||
var items = this.GetVisualDescendants().OfType<AnchorItem>().ToList();
|
|
||||||
var target = this.TargetContainer;
|
var target = this.TargetContainer;
|
||||||
if (target is null) return;
|
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);
|
TargetContainer?.AddHandler(ScrollViewer.ScrollChangedEvent, OnScrollChanged);
|
||||||
if (isloaded is true)
|
TargetContainer?.AddHandler(LoadedEvent, OnTargetContainerLoaded);
|
||||||
|
if (TargetContainer?.IsLoaded == true)
|
||||||
{
|
{
|
||||||
InvalidateAnchorPositions();
|
InvalidateAnchorPositions();
|
||||||
}
|
}
|
||||||
else
|
|
||||||
{
|
|
||||||
TargetContainer.Loaded += (s, args) => InvalidateAnchorPositions();
|
|
||||||
}
|
|
||||||
|
|
||||||
MarkSelectedContainerByPosition();
|
MarkSelectedContainerByPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -159,7 +150,7 @@ public class Anchor: ItemsControl
|
|||||||
if (source is null) return;
|
if (source is null) return;
|
||||||
MarkSelectedContainer(source);
|
MarkSelectedContainer(source);
|
||||||
var target = TargetContainer?.GetVisualDescendants()
|
var target = TargetContainer?.GetVisualDescendants()
|
||||||
.FirstOrDefault(a => Anchor.GetAnchorId(a) == source?.AnchorId);
|
.FirstOrDefault(a => Anchor.GetAnchorId(a) == source.AnchorId);
|
||||||
if (target is null) return;
|
if (target is null) return;
|
||||||
ScrollToAnchor(target);
|
ScrollToAnchor(target);
|
||||||
}
|
}
|
||||||
@@ -187,7 +178,7 @@ public class Anchor: ItemsControl
|
|||||||
ContainerForItemPreparedOverride(container, item, index);
|
ContainerForItemPreparedOverride(container, item, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal AnchorItem? _selectedContainer;
|
private AnchorItem? _selectedContainer;
|
||||||
|
|
||||||
internal void MarkSelectedContainer(AnchorItem? item)
|
internal void MarkSelectedContainer(AnchorItem? item)
|
||||||
{
|
{
|
||||||
@@ -211,4 +202,14 @@ public class Anchor: ItemsControl
|
|||||||
MarkSelectedContainer(item);
|
MarkSelectedContainer(item);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnUnloaded(RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnUnloaded(e);
|
||||||
|
TargetContainer?.RemoveHandler(UnloadedEvent, OnTargetContainerLoaded);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnTargetContainerLoaded(object? sender, RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
InvalidateAnchorPositions();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -3,8 +3,8 @@ using Avalonia.Controls;
|
|||||||
using Avalonia.Controls.Mixins;
|
using Avalonia.Controls.Mixins;
|
||||||
using Avalonia.Controls.Primitives;
|
using Avalonia.Controls.Primitives;
|
||||||
using Avalonia.Controls.Templates;
|
using Avalonia.Controls.Templates;
|
||||||
using Avalonia.Input;
|
|
||||||
using Avalonia.LogicalTree;
|
using Avalonia.LogicalTree;
|
||||||
|
using Ursa.Common;
|
||||||
|
|
||||||
namespace Ursa.Controls;
|
namespace Ursa.Controls;
|
||||||
|
|
||||||
@@ -19,6 +19,12 @@ public class AnchorItem : HeaderedItemsControl, ISelectable
|
|||||||
private static readonly FuncTemplate<Panel?> DefaultPanel =
|
private static readonly FuncTemplate<Panel?> DefaultPanel =
|
||||||
new(() => new StackPanel());
|
new(() => new StackPanel());
|
||||||
|
|
||||||
|
internal static readonly DirectProperty<AnchorItem, int> LevelProperty =
|
||||||
|
AvaloniaProperty.RegisterDirect<AnchorItem, int>(
|
||||||
|
nameof(Level), o => o.Level, (o, v) => o.Level = v);
|
||||||
|
|
||||||
|
private int _level;
|
||||||
|
|
||||||
private Anchor? _root;
|
private Anchor? _root;
|
||||||
|
|
||||||
static AnchorItem()
|
static AnchorItem()
|
||||||
@@ -28,6 +34,12 @@ public class AnchorItem : HeaderedItemsControl, ISelectable
|
|||||||
ItemsPanelProperty.OverrideDefaultValue<TreeViewItem>(DefaultPanel);
|
ItemsPanelProperty.OverrideDefaultValue<TreeViewItem>(DefaultPanel);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int Level
|
||||||
|
{
|
||||||
|
get => _level;
|
||||||
|
set => SetAndRaise(LevelProperty, ref _level, value);
|
||||||
|
}
|
||||||
|
|
||||||
public string? AnchorId
|
public string? AnchorId
|
||||||
{
|
{
|
||||||
get => GetValue(AnchorIdProperty);
|
get => GetValue(AnchorIdProperty);
|
||||||
@@ -44,15 +56,12 @@ public class AnchorItem : HeaderedItemsControl, ISelectable
|
|||||||
{
|
{
|
||||||
base.OnAttachedToVisualTree(e);
|
base.OnAttachedToVisualTree(e);
|
||||||
_root = this.GetLogicalAncestors().OfType<Anchor>().FirstOrDefault();
|
_root = this.GetLogicalAncestors().OfType<Anchor>().FirstOrDefault();
|
||||||
|
Level = LogicalHelpers.CalculateDistanceFromLogicalParent<Anchor, AnchorItem>(this);
|
||||||
if (ItemTemplate is null && _root?.ItemTemplate is not null)
|
if (ItemTemplate is null && _root?.ItemTemplate is not null)
|
||||||
{
|
|
||||||
SetCurrentValue(ItemTemplateProperty, _root.ItemTemplate);
|
SetCurrentValue(ItemTemplateProperty, _root.ItemTemplate);
|
||||||
}
|
|
||||||
|
|
||||||
if (ItemContainerTheme is null && _root?.ItemContainerTheme is not null)
|
if (ItemContainerTheme is null && _root?.ItemContainerTheme is not null)
|
||||||
{
|
|
||||||
SetCurrentValue(ItemContainerThemeProperty, _root.ItemContainerTheme);
|
SetCurrentValue(ItemContainerThemeProperty, _root.ItemContainerTheme);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey)
|
protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey)
|
||||||
|
|||||||
Reference in New Issue
Block a user