feat: add inheritance.
This commit is contained in:
@@ -6,11 +6,14 @@
|
|||||||
xmlns:u="https://irihi.tech/ursa">
|
xmlns:u="https://irihi.tech/ursa">
|
||||||
<converters:TreeLevelToMarginConverter x:Key="LevelToMarginConverter" />
|
<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="Background" Value="Transparent" />
|
||||||
|
<Setter Property="VerticalAlignment" Value="Top" />
|
||||||
<Setter Property="Template">
|
<Setter Property="Template">
|
||||||
<ControlTemplate>
|
<ControlTemplate>
|
||||||
<Panel>
|
<Panel>
|
||||||
<Rectangle
|
<Rectangle
|
||||||
Width="1"
|
Width="1"
|
||||||
|
Name="PART_Pipe"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
VerticalAlignment="Stretch"
|
VerticalAlignment="Stretch"
|
||||||
Fill="{DynamicResource SemiColorBorder}" />
|
Fill="{DynamicResource SemiColorBorder}" />
|
||||||
@@ -21,23 +24,30 @@
|
|||||||
</Panel>
|
</Panel>
|
||||||
</ControlTemplate>
|
</ControlTemplate>
|
||||||
</Setter>
|
</Setter>
|
||||||
|
<Style Selector="^.Mute /template/ Rectangle#PART_Pipe">
|
||||||
|
<Setter Property="Fill" Value="Transparent" />
|
||||||
|
</Style>
|
||||||
</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="Cursor" Value="Hand" />
|
||||||
|
<Setter Property="iri:ClassHelper.ClassSource" Value="{Binding $parent[u:Anchor]}" />
|
||||||
<Setter Property="Template">
|
<Setter Property="Template">
|
||||||
<ControlTemplate TargetType="u:AnchorItem">
|
<ControlTemplate TargetType="u:AnchorItem">
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
<Panel>
|
<Panel Background="{TemplateBinding Background}" >
|
||||||
<Rectangle
|
<Border
|
||||||
Name="PART_Pipe"
|
Name="PART_Pipe"
|
||||||
Width="2"
|
Width="2"
|
||||||
|
CornerRadius="1"
|
||||||
HorizontalAlignment="Left"
|
HorizontalAlignment="Left"
|
||||||
VerticalAlignment="Stretch" />
|
VerticalAlignment="Stretch" />
|
||||||
<Panel Margin="8,0,0,0">
|
<Panel Margin="8,0,0,0">
|
||||||
<ContentPresenter
|
<ContentPresenter
|
||||||
Name="{x:Static iri:PartNames.PART_HeaderPresenter}"
|
Name="{x:Static iri:PartNames.PART_HeaderPresenter}"
|
||||||
|
VerticalAlignment="Center"
|
||||||
|
Foreground="{DynamicResource SemiColorText2}"
|
||||||
Content="{TemplateBinding Header}"
|
Content="{TemplateBinding Header}"
|
||||||
ContentTemplate="{TemplateBinding HeaderTemplate}">
|
ContentTemplate="{TemplateBinding HeaderTemplate}">
|
||||||
<ContentPresenter.Margin>
|
<ContentPresenter.Margin>
|
||||||
@@ -53,8 +63,24 @@
|
|||||||
</StackPanel>
|
</StackPanel>
|
||||||
</ControlTemplate>
|
</ControlTemplate>
|
||||||
</Setter>
|
</Setter>
|
||||||
<Style Selector="^:selected /template/ Rectangle#PART_Pipe">
|
<Style Selector="^ /template/ ContentPresenter#PART_HeaderPresenter">
|
||||||
<Setter Property="Fill" Value="{DynamicResource SemiBlue5}" />
|
<Setter Property="MinHeight" Value="{DynamicResource AnchorDefaultHeight}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="^.Small /template/ ContentPresenter#PART_HeaderPresenter">
|
||||||
|
<Setter Property="MinHeight" Value="{DynamicResource AnchorSmallHeight}" />
|
||||||
|
<Setter Property="FontSize" Value="{DynamicResource SemiFontSizeSmall}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="^:selected /template/ Border#PART_Pipe">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource SemiColorTertiary}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="^.Primary:selected /template/ Border#PART_Pipe">
|
||||||
|
<Setter Property="Background" Value="{DynamicResource SemiColorPrimary}" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="^.Mute:selected /template/ Border#PART_Pipe">
|
||||||
|
<Setter Property="Background" Value="Transparent" />
|
||||||
|
</Style>
|
||||||
|
<Style Selector="^:selected /template/ ContentPresenter#PART_HeaderPresenter">
|
||||||
|
<Setter Property="Foreground" Value="{DynamicResource SemiColorText0}" />
|
||||||
</Style>
|
</Style>
|
||||||
</ControlTheme>
|
</ControlTheme>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
<ResourceDictionary xmlns="https://github.com/avaloniaui"
|
||||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
|
||||||
<x:Double x:Key="AnchorIndent">12</x:Double>
|
<x:Double x:Key="AnchorIndent">12</x:Double>
|
||||||
|
<x:Double x:Key="AnchorDefaultHeight">20</x:Double>
|
||||||
|
<x:Double x:Key="AnchorSmallHeight">16</x:Double>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
|
|||||||
@@ -11,15 +11,27 @@ using Ursa.Common;
|
|||||||
namespace Ursa.Controls;
|
namespace Ursa.Controls;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Some basic assumptions: This should not be a regular SelectingItemsControl, because it does not support multiple selections.
|
/// Some basic assumptions: This should not be a regular SelectingItemsControl, because it does not support multiple
|
||||||
/// Selection should not be exposed to the user, it is only used to determine which item is currently selected.
|
/// selections.
|
||||||
/// The manipulation of container selection should be simplified.
|
/// Selection should not be exposed to the user, it is only used to determine which item is currently selected.
|
||||||
/// Scroll event of TargetContainer also triggers selection change.
|
/// The manipulation of container selection should be simplified.
|
||||||
|
/// Scroll event of TargetContainer also triggers selection change.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public class Anchor: ItemsControl
|
public class Anchor : ItemsControl
|
||||||
{
|
{
|
||||||
public static readonly StyledProperty<ScrollViewer?> TargetContainerProperty = AvaloniaProperty.Register<Anchor, ScrollViewer?>(
|
public static readonly StyledProperty<ScrollViewer?> TargetContainerProperty =
|
||||||
nameof(TargetContainer));
|
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
|
public ScrollViewer? TargetContainer
|
||||||
{
|
{
|
||||||
@@ -27,11 +39,15 @@ public class Anchor: ItemsControl
|
|||||||
set => SetValue(TargetContainerProperty, value);
|
set => SetValue(TargetContainerProperty, value);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static readonly AttachedProperty<string?> AnchorIdProperty =
|
public static void SetAnchorId(Visual obj, string? value)
|
||||||
AvaloniaProperty.RegisterAttached<Anchor, Visual, string?>("AnchorId");
|
{
|
||||||
|
obj.SetValue(AnchorIdProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
public static void SetAnchorId(Visual obj, string? value) => obj.SetValue(AnchorIdProperty, value);
|
public static string? GetAnchorId(Visual obj)
|
||||||
public static string? GetAnchorId(Visual obj) => obj.GetValue(AnchorIdProperty);
|
{
|
||||||
|
return obj.GetValue(AnchorIdProperty);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey)
|
protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey)
|
||||||
@@ -45,9 +61,6 @@ public class Anchor: ItemsControl
|
|||||||
return i;
|
return i;
|
||||||
}
|
}
|
||||||
|
|
||||||
private CancellationTokenSource _cts = new();
|
|
||||||
private bool _scrollingFromSelection;
|
|
||||||
|
|
||||||
private void ScrollToAnchor(Visual target)
|
private void ScrollToAnchor(Visual target)
|
||||||
{
|
{
|
||||||
if (TargetContainer is null)
|
if (TargetContainer is null)
|
||||||
@@ -58,38 +71,30 @@ public class Anchor: ItemsControl
|
|||||||
{
|
{
|
||||||
var from = TargetContainer.Offset.Y;
|
var from = TargetContainer.Offset.Y;
|
||||||
var to = TargetContainer.Offset.Y + targetPosition.Value.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;
|
to = TargetContainer.Extent.Height - TargetContainer.Bounds.Height;
|
||||||
}
|
|
||||||
if (from == to) return;
|
if (from == to) return;
|
||||||
Animation animation = new Animation()
|
var animation = new Animation
|
||||||
{
|
{
|
||||||
Duration = TimeSpan.FromSeconds(0.3),
|
Duration = TimeSpan.FromSeconds(0.3),
|
||||||
Easing = new QuadraticEaseOut(),
|
Easing = new QuadraticEaseOut(),
|
||||||
Children =
|
Children =
|
||||||
{
|
{
|
||||||
new KeyFrame(){
|
new KeyFrame
|
||||||
Setters =
|
{
|
||||||
{
|
Setters = { new Setter(ScrollViewer.OffsetProperty, new Vector(0, from)) },
|
||||||
new Setter(ScrollViewer.OffsetProperty, new Vector(0, from)),
|
|
||||||
},
|
|
||||||
Cue = new Cue(0.0)
|
Cue = new Cue(0.0)
|
||||||
},
|
},
|
||||||
new KeyFrame()
|
new KeyFrame
|
||||||
{
|
{
|
||||||
Setters =
|
Setters = { new Setter(ScrollViewer.OffsetProperty, new Vector(0, to)) },
|
||||||
{
|
|
||||||
new Setter(ScrollViewer.OffsetProperty, new Vector(0, to))
|
|
||||||
},
|
|
||||||
Cue = new Cue(1.0)
|
Cue = new Cue(1.0)
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
_cts.Cancel();
|
_cts.Cancel();
|
||||||
_cts = new CancellationTokenSource();
|
_cts = new CancellationTokenSource();
|
||||||
var token = _cts.Token;
|
var token = _cts.Token;
|
||||||
token.Register(_ => _scrollingFromSelection = false, null);
|
token.Register(_ => _scrollingFromSelection = false, null);
|
||||||
_scrollingFromSelection = true;
|
_scrollingFromSelection = true;
|
||||||
animation.RunAsync(TargetContainer, token).ContinueWith(_ => _scrollingFromSelection = false, token);
|
animation.RunAsync(TargetContainer, token).ContinueWith(_ => _scrollingFromSelection = false, token);
|
||||||
@@ -102,23 +107,19 @@ public class Anchor: ItemsControl
|
|||||||
MarkSelectedContainerByPosition();
|
MarkSelectedContainerByPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<(string, double)> _positions = [];
|
|
||||||
|
|
||||||
internal void InvalidateAnchorPositions()
|
internal void InvalidateAnchorPositions()
|
||||||
{
|
{
|
||||||
if (TargetContainer is null) return;
|
if (TargetContainer is null) return;
|
||||||
var items = TargetContainer.GetVisualDescendants().Where(a => GetAnchorId(a) is not null);
|
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)
|
foreach (var item in items)
|
||||||
{
|
{
|
||||||
var anchorId = GetAnchorId(item);
|
var anchorId = GetAnchorId(item);
|
||||||
if (anchorId is null) continue;
|
if (anchorId is null) continue;
|
||||||
var position = item.TransformToVisual(TargetContainer)?.M32 + TargetContainer.Offset.Y;
|
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));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
positions.Sort((a, b) => a.Item2.CompareTo(b.Item2));
|
positions.Sort((a, b) => a.Item2.CompareTo(b.Item2));
|
||||||
_positions = positions;
|
_positions = positions;
|
||||||
}
|
}
|
||||||
@@ -126,14 +127,11 @@ public class Anchor: ItemsControl
|
|||||||
protected override void OnLoaded(RoutedEventArgs e)
|
protected override void OnLoaded(RoutedEventArgs e)
|
||||||
{
|
{
|
||||||
base.OnLoaded(e);
|
base.OnLoaded(e);
|
||||||
var target = this.TargetContainer;
|
var target = TargetContainer;
|
||||||
if (target is null) return;
|
if (target is null) return;
|
||||||
TargetContainer?.AddHandler(ScrollViewer.ScrollChangedEvent, OnScrollChanged);
|
TargetContainer?.AddHandler(ScrollViewer.ScrollChangedEvent, OnScrollChanged);
|
||||||
TargetContainer?.AddHandler(LoadedEvent, OnTargetContainerLoaded);
|
TargetContainer?.AddHandler(LoadedEvent, OnTargetContainerLoaded);
|
||||||
if (TargetContainer?.IsLoaded == true)
|
if (TargetContainer?.IsLoaded == true) InvalidateAnchorPositions();
|
||||||
{
|
|
||||||
InvalidateAnchorPositions();
|
|
||||||
}
|
|
||||||
MarkSelectedContainerByPosition();
|
MarkSelectedContainerByPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -150,13 +148,13 @@ 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 => GetAnchorId(a) == source.AnchorId);
|
||||||
if (target is null) return;
|
if (target is null) return;
|
||||||
ScrollToAnchor(target);
|
ScrollToAnchor(target);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <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>
|
/// </summary>
|
||||||
internal Control CreateContainerForItemOverride_INTERNAL(object? item, int index, object? recycleKey)
|
internal Control CreateContainerForItemOverride_INTERNAL(object? item, int index, object? recycleKey)
|
||||||
{
|
{
|
||||||
@@ -178,8 +176,6 @@ public class Anchor: ItemsControl
|
|||||||
ContainerForItemPreparedOverride(container, item, index);
|
ContainerForItemPreparedOverride(container, item, index);
|
||||||
}
|
}
|
||||||
|
|
||||||
private AnchorItem? _selectedContainer;
|
|
||||||
|
|
||||||
internal void MarkSelectedContainer(AnchorItem? item)
|
internal void MarkSelectedContainer(AnchorItem? item)
|
||||||
{
|
{
|
||||||
var oldValue = _selectedContainer;
|
var oldValue = _selectedContainer;
|
||||||
@@ -197,7 +193,7 @@ public class Anchor: ItemsControl
|
|||||||
var topAnchorId = _positions.LastOrDefault(a => a.Item2 <= top).Item1;
|
var topAnchorId = _positions.LastOrDefault(a => a.Item2 <= top).Item1;
|
||||||
if (topAnchorId is null) return;
|
if (topAnchorId is null) return;
|
||||||
var item = this.GetVisualDescendants().OfType<AnchorItem>()
|
var item = this.GetVisualDescendants().OfType<AnchorItem>()
|
||||||
.FirstOrDefault(a => a.AnchorId == topAnchorId);
|
.FirstOrDefault(a => a.AnchorId == topAnchorId);
|
||||||
if (item is null) return;
|
if (item is null) return;
|
||||||
MarkSelectedContainer(item);
|
MarkSelectedContainer(item);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public class AnchorItem : HeaderedItemsControl, ISelectable
|
|||||||
{
|
{
|
||||||
SelectableMixin.Attach<AnchorItem>(IsSelectedProperty);
|
SelectableMixin.Attach<AnchorItem>(IsSelectedProperty);
|
||||||
PressedMixin.Attach<AnchorItem>();
|
PressedMixin.Attach<AnchorItem>();
|
||||||
ItemsPanelProperty.OverrideDefaultValue<TreeViewItem>(DefaultPanel);
|
ItemsPanelProperty.OverrideDefaultValue<AnchorItem>(DefaultPanel);
|
||||||
}
|
}
|
||||||
|
|
||||||
public int Level
|
public int Level
|
||||||
|
|||||||
Reference in New Issue
Block a user