feat: WIP.
This commit is contained in:
3
Ursa.sln
3
Ursa.sln
@@ -42,8 +42,8 @@ EndProject
|
|||||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHub Action", "GitHub Action", "{66123AC1-7C8C-4AA0-BBDB-5CC3E647A741}"
|
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "GitHub Action", "GitHub Action", "{66123AC1-7C8C-4AA0-BBDB-5CC3E647A741}"
|
||||||
ProjectSection(SolutionItems) = preProject
|
ProjectSection(SolutionItems) = preProject
|
||||||
.github\workflows\deploy.yml = .github\workflows\deploy.yml
|
.github\workflows\deploy.yml = .github\workflows\deploy.yml
|
||||||
.github\workflows\pack.yml = .github\workflows\pack.yml
|
|
||||||
.github\workflows\pack-nightly.yml = .github\workflows\pack-nightly.yml
|
.github\workflows\pack-nightly.yml = .github\workflows\pack-nightly.yml
|
||||||
|
.github\workflows\pack.yml = .github\workflows\pack.yml
|
||||||
.github\workflows\publish.yml = .github\workflows\publish.yml
|
.github\workflows\publish.yml = .github\workflows\publish.yml
|
||||||
.github\workflows\release-tag.yml = .github\workflows\release-tag.yml
|
.github\workflows\release-tag.yml = .github\workflows\release-tag.yml
|
||||||
.github\workflows\test.yml = .github\workflows\test.yml
|
.github\workflows\test.yml = .github\workflows\test.yml
|
||||||
@@ -69,6 +69,7 @@ Global
|
|||||||
{53B5F277-3AEB-4661-ACAE-15CFFF2ED800}.Release|Any CPU.Build.0 = Release|Any CPU
|
{53B5F277-3AEB-4661-ACAE-15CFFF2ED800}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{3FC76CD9-CE5D-4804-A8D6-4E292EB296AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{3FC76CD9-CE5D-4804-A8D6-4E292EB296AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
{3FC76CD9-CE5D-4804-A8D6-4E292EB296AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
{3FC76CD9-CE5D-4804-A8D6-4E292EB296AA}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||||
|
{3FC76CD9-CE5D-4804-A8D6-4E292EB296AA}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
|
||||||
{3FC76CD9-CE5D-4804-A8D6-4E292EB296AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
{3FC76CD9-CE5D-4804-A8D6-4E292EB296AA}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||||
{3FC76CD9-CE5D-4804-A8D6-4E292EB296AA}.Release|Any CPU.Build.0 = Release|Any CPU
|
{3FC76CD9-CE5D-4804-A8D6-4E292EB296AA}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||||
{B6BAB821-A9FE-44F3-B9CD-06E27FDB63F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
{B6BAB821-A9FE-44F3-B9CD-06E27FDB63F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||||
|
|||||||
@@ -25,49 +25,48 @@
|
|||||||
</Style>
|
</Style>
|
||||||
</StackPanel.Styles>
|
</StackPanel.Styles>
|
||||||
<Border
|
<Border
|
||||||
Name="a1"
|
u:Anchor.AnchorId="a1"
|
||||||
Height="300"
|
Height="300"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Background="{DynamicResource SemiRed2}">
|
Background="{DynamicResource SemiRed2}">
|
||||||
<TextBlock Text="Border 1" />
|
<TextBlock Text="Border 1" />
|
||||||
</Border>
|
</Border>
|
||||||
<Border
|
<Border
|
||||||
Name="a2"
|
u:Anchor.AnchorId="a2"
|
||||||
Height="300"
|
Height="300"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Background="{DynamicResource SemiPink1}">
|
Background="{DynamicResource SemiPink1}">
|
||||||
<TextBlock Text="Border 2" />
|
<TextBlock Text="Border 2" />
|
||||||
</Border>
|
</Border>
|
||||||
<Border
|
<Border
|
||||||
Name="a3"
|
u:Anchor.AnchorId="a3"
|
||||||
Height="300"
|
Height="300"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Background="{DynamicResource SemiPurple1}">
|
Background="{DynamicResource SemiPurple1}">
|
||||||
<TextBlock Text="Border 3" />
|
<TextBlock Text="Border 3" />
|
||||||
</Border>
|
</Border>
|
||||||
<Border
|
<Border
|
||||||
Name="a4"
|
|
||||||
Height="300"
|
Height="300"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Background="{DynamicResource SemiViolet1}">
|
Background="{DynamicResource SemiViolet1}">
|
||||||
<TextBlock Text="Border 4" />
|
<TextBlock u:Anchor.AnchorId="a4" Text="Border 4" />
|
||||||
</Border>
|
</Border>
|
||||||
<Border
|
<Border
|
||||||
Name="a5"
|
u:Anchor.AnchorId="a5"
|
||||||
Height="300"
|
Height="300"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Background="{DynamicResource SemiIndigo1}">
|
Background="{DynamicResource SemiIndigo1}">
|
||||||
<TextBlock Text="Border 5" />
|
<TextBlock Text="Border 5" />
|
||||||
</Border>
|
</Border>
|
||||||
<Border
|
<Border
|
||||||
Name="a6"
|
u:Anchor.AnchorId="a6"
|
||||||
Height="300"
|
Height="300"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Background="{DynamicResource SemiBlue1}">
|
Background="{DynamicResource SemiBlue1}">
|
||||||
<TextBlock Text="Border 6" />
|
<TextBlock Text="Border 6" />
|
||||||
</Border>
|
</Border>
|
||||||
<Border
|
<Border
|
||||||
Name="a7"
|
u:Anchor.AnchorId="a7"
|
||||||
Height="300"
|
Height="300"
|
||||||
HorizontalAlignment="Stretch"
|
HorizontalAlignment="Stretch"
|
||||||
Background="{DynamicResource SemiLightBlue1}">
|
Background="{DynamicResource SemiLightBlue1}">
|
||||||
@@ -80,13 +79,16 @@
|
|||||||
Width="200"
|
Width="200"
|
||||||
Margin="24"
|
Margin="24"
|
||||||
TargetContainer="{Binding ElementName=container1}">
|
TargetContainer="{Binding ElementName=container1}">
|
||||||
<u:AnchorItem Content="Rectangle 1" Target="{Binding #a1}" />
|
<u:AnchorItem Header="Rectangle 1" AnchorId="a1" >
|
||||||
<u:AnchorItem Content="Rectangle 2" Target="{Binding #a2}" />
|
<u:AnchorItem Header="Rectangle 2" AnchorId="a2" />
|
||||||
<u:AnchorItem Content="Rectangle 3" Target="{Binding #a3}" />
|
<u:AnchorItem Header="Rectangle 3" AnchorId="a3" />
|
||||||
<u:AnchorItem Content="Rectangle 4" Target="{Binding #a4}" />
|
</u:AnchorItem>
|
||||||
<u:AnchorItem Content="Rectangle 5" Target="{Binding #a5}" />
|
<u:AnchorItem Header="Rectangle 4" AnchorId="a4" />
|
||||||
<u:AnchorItem Content="Rectangle 6" Target="{Binding #a6}" />
|
<u:AnchorItem Header="Rectangle 5" AnchorId="a5" >
|
||||||
<u:AnchorItem Content="Rectangle 7" Target="{Binding #a7}" />
|
<u:AnchorItem Header="Rectangle 6" AnchorId="a6" />
|
||||||
|
<u:AnchorItem Header="Rectangle 7" AnchorId="a7" />
|
||||||
|
</u:AnchorItem>
|
||||||
|
|
||||||
|
|
||||||
</u:Anchor>
|
</u:Anchor>
|
||||||
</Grid>
|
</Grid>
|
||||||
@@ -157,13 +159,14 @@
|
|||||||
</ScrollViewer>
|
</ScrollViewer>
|
||||||
<u:Anchor
|
<u:Anchor
|
||||||
Grid.Column="1"
|
Grid.Column="1"
|
||||||
MinWidth="300"
|
Width="200"
|
||||||
|
Margin="24"
|
||||||
ItemsSource="{Binding AnchorItems}"
|
ItemsSource="{Binding AnchorItems}"
|
||||||
TargetContainer="{Binding #container2}">
|
TargetContainer="{Binding #container2}">
|
||||||
<u:Anchor.Styles>
|
<u:Anchor.Styles>
|
||||||
<Style x:DataType="viewModels:AnchorItemViewModel" Selector="u|AnchorItem">
|
<Style x:DataType="viewModels:AnchorItemViewModel" Selector="u|AnchorItem">
|
||||||
<Setter Property="AnchorId" Value="{Binding AnchorId}" />
|
<Setter Property="AnchorId" Value="{Binding AnchorId}" />
|
||||||
<Setter Property="Content" Value="{Binding Header}" />
|
<Setter Property="Header" Value="{Binding Header}" />
|
||||||
</Style>
|
</Style>
|
||||||
</u:Anchor.Styles>
|
</u:Anchor.Styles>
|
||||||
</u:Anchor>
|
</u:Anchor>
|
||||||
|
|||||||
@@ -12,4 +12,16 @@
|
|||||||
</ControlTemplate>
|
</ControlTemplate>
|
||||||
</Setter>
|
</Setter>
|
||||||
</ControlTheme>
|
</ControlTheme>
|
||||||
|
|
||||||
|
<ControlTheme x:Key="{x:Type u:AnchorItem}" TargetType="u:AnchorItem" >
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
<Setter Property="Template">
|
||||||
|
<ControlTemplate TargetType="u:AnchorItem">
|
||||||
|
<StackPanel>
|
||||||
|
<ContentPresenter Content="{TemplateBinding Header}" />
|
||||||
|
<ItemsPresenter Margin="8 0 0 0" ItemsPanel="{TemplateBinding ItemsPanel}" />
|
||||||
|
</StackPanel>
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter>
|
||||||
|
</ControlTheme>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -47,11 +47,12 @@ public class Anchor: SelectingItemsControl
|
|||||||
ScrollToAnchor(target);
|
ScrollToAnchor(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void ScrollToAnchor(Visual target)
|
private CancellationTokenSource _cts = new();
|
||||||
|
|
||||||
|
private void ScrollToAnchor(Visual target)
|
||||||
{
|
{
|
||||||
if (TargetContainer is null)
|
if (TargetContainer is null)
|
||||||
return;
|
return;
|
||||||
TargetContainer.Loaded += OnTargetLoaded;
|
|
||||||
var targetPosition = target.TranslatePoint(new Point(0, 0), TargetContainer);
|
var targetPosition = target.TranslatePoint(new Point(0, 0), TargetContainer);
|
||||||
if (targetPosition.HasValue)
|
if (targetPosition.HasValue)
|
||||||
{
|
{
|
||||||
@@ -61,6 +62,7 @@ public class Anchor: SelectingItemsControl
|
|||||||
{
|
{
|
||||||
to = TargetContainer.Extent.Height - TargetContainer.Bounds.Height;
|
to = TargetContainer.Extent.Height - TargetContainer.Bounds.Height;
|
||||||
}
|
}
|
||||||
|
if (from == to) return;
|
||||||
Animation animation = new Animation()
|
Animation animation = new Animation()
|
||||||
{
|
{
|
||||||
Duration = TimeSpan.FromSeconds(0.3),
|
Duration = TimeSpan.FromSeconds(0.3),
|
||||||
@@ -85,24 +87,9 @@ public class Anchor: SelectingItemsControl
|
|||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
animation.RunAsync(TargetContainer);
|
_cts.Cancel();
|
||||||
// TargetContainer.Offset = TargetContainer.Offset.WithY(TargetContainer.Offset.Y + targetPosition.Value.Y);
|
_cts = new CancellationTokenSource();
|
||||||
}
|
animation.RunAsync(TargetContainer, _cts.Token);
|
||||||
}
|
|
||||||
|
|
||||||
private void OnTargetLoaded(object sender, RoutedEventArgs e)
|
|
||||||
{
|
|
||||||
if (sender is ScrollViewer scrollViewer)
|
|
||||||
{
|
|
||||||
scrollViewer.Loaded -= OnTargetLoaded;
|
|
||||||
if (scrollViewer.Content is Visual target)
|
|
||||||
{
|
|
||||||
var anchorId = GetAnchorId(target);
|
|
||||||
if (!string.IsNullOrEmpty(anchorId))
|
|
||||||
{
|
|
||||||
ScrollToAnchor(anchorId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -110,4 +97,41 @@ public class Anchor: SelectingItemsControl
|
|||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override void OnLoaded(RoutedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnLoaded(e);
|
||||||
|
var items = this.GetVisualDescendants().OfType<AnchorItem>().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);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void OnScrollChanged(object? sender, ScrollChangedEventArgs e)
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ContainerForItemPreparedOverride_INTERNAL(Control container, object? item, int index)
|
||||||
|
{
|
||||||
|
ContainerForItemPreparedOverride(container, item, index);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -1,51 +1,87 @@
|
|||||||
using System.Windows.Input;
|
|
||||||
using Avalonia;
|
using Avalonia;
|
||||||
using Avalonia.Controls;
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Mixins;
|
||||||
|
using Avalonia.Controls.Primitives;
|
||||||
|
using Avalonia.Controls.Templates;
|
||||||
using Avalonia.Input;
|
using Avalonia.Input;
|
||||||
using Avalonia.VisualTree;
|
using Avalonia.LogicalTree;
|
||||||
|
|
||||||
namespace Ursa.Controls;
|
namespace Ursa.Controls;
|
||||||
|
|
||||||
public class AnchorItem: ContentControl
|
public class AnchorItem : HeaderedItemsControl, ISelectable
|
||||||
{
|
{
|
||||||
public static readonly StyledProperty<Control?> TargetProperty = AvaloniaProperty.Register<AnchorItem, Control?>(
|
|
||||||
nameof(Target));
|
|
||||||
|
|
||||||
public Control? Target
|
|
||||||
{
|
|
||||||
get => GetValue(TargetProperty);
|
|
||||||
set => SetValue(TargetProperty, value);
|
|
||||||
}
|
|
||||||
|
|
||||||
public static readonly StyledProperty<string?> AnchorIdProperty = AvaloniaProperty.Register<AnchorItem, string?>(
|
public static readonly StyledProperty<string?> AnchorIdProperty = AvaloniaProperty.Register<AnchorItem, string?>(
|
||||||
nameof(AnchorId));
|
nameof(AnchorId));
|
||||||
|
|
||||||
|
public static readonly StyledProperty<bool> IsSelectedProperty =
|
||||||
|
SelectingItemsControl.IsSelectedProperty.AddOwner<AnchorItem>();
|
||||||
|
|
||||||
|
private static readonly FuncTemplate<Panel?> DefaultPanel =
|
||||||
|
new(() => new StackPanel());
|
||||||
|
|
||||||
|
private Anchor? _root;
|
||||||
|
|
||||||
|
static AnchorItem()
|
||||||
|
{
|
||||||
|
SelectableMixin.Attach<AnchorItem>(IsSelectedProperty);
|
||||||
|
PressedMixin.Attach<AnchorItem>();
|
||||||
|
ItemsPanelProperty.OverrideDefaultValue<TreeViewItem>(DefaultPanel);
|
||||||
|
}
|
||||||
|
|
||||||
public string? AnchorId
|
public string? AnchorId
|
||||||
{
|
{
|
||||||
get => GetValue(AnchorIdProperty);
|
get => GetValue(AnchorIdProperty);
|
||||||
set => SetValue(AnchorIdProperty, value);
|
set => SetValue(AnchorIdProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Anchor? _root;
|
public bool IsSelected
|
||||||
|
{
|
||||||
|
get => GetValue(IsSelectedProperty);
|
||||||
|
set => SetValue(IsSelectedProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnAttachedToVisualTree(e);
|
base.OnAttachedToVisualTree(e);
|
||||||
_root = this.FindAncestorOfType<Anchor>() ??
|
_root = this.GetLogicalAncestors().OfType<Anchor>().FirstOrDefault();
|
||||||
throw new InvalidOperationException("AnchorItem must be inside an Anchor control.");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void OnPointerPressed(PointerPressedEventArgs e)
|
protected override void OnPointerPressed(PointerPressedEventArgs e)
|
||||||
{
|
{
|
||||||
|
var item = new TreeViewItem();
|
||||||
base.OnPointerPressed(e);
|
base.OnPointerPressed(e);
|
||||||
|
if (e.Handled) return;
|
||||||
if (_root is null)
|
if (_root is null)
|
||||||
return;
|
return;
|
||||||
if (Target is not null)
|
if (!string.IsNullOrEmpty(AnchorId))
|
||||||
{
|
|
||||||
_root.ScrollToAnchor(Target);
|
|
||||||
}
|
|
||||||
else if (!string.IsNullOrEmpty(AnchorId))
|
|
||||||
{
|
{
|
||||||
_root.ScrollToAnchor(AnchorId);
|
_root.ScrollToAnchor(AnchorId);
|
||||||
|
e.Handled = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey)
|
||||||
|
{
|
||||||
|
return EnsureRoot().CreateContainerForItemOverride_INTERNAL(item, index, recycleKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey)
|
||||||
|
{
|
||||||
|
return EnsureRoot().NeedsContainerOverride_INTERNAL(item, index, out recycleKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PrepareContainerForItemOverride(Control container, object? item, int index)
|
||||||
|
{
|
||||||
|
EnsureRoot().PrepareContainerForItemOverride_INTERNAL(container, item, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ContainerForItemPreparedOverride(Control container, object? item, int index)
|
||||||
|
{
|
||||||
|
EnsureRoot().ContainerForItemPreparedOverride_INTERNAL(container, item, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Anchor EnsureRoot()
|
||||||
|
{
|
||||||
|
return _root ?? throw new InvalidOperationException("AnchorItem must be inside an Anchor control.");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user