feat: implementations.
This commit is contained in:
69
demo/Ursa.Demo/Pages/AnchorDemo.axaml
Normal file
69
demo/Ursa.Demo/Pages/AnchorDemo.axaml
Normal file
@@ -0,0 +1,69 @@
|
|||||||
|
<UserControl
|
||||||
|
x:Class="Ursa.Demo.Pages.AnchorDemo"
|
||||||
|
xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||||
|
xmlns:u="https://irihi.tech/ursa"
|
||||||
|
d:DesignHeight="450"
|
||||||
|
d:DesignWidth="800"
|
||||||
|
mc:Ignorable="d">
|
||||||
|
<Grid ColumnDefinitions="*, Auto">
|
||||||
|
<ScrollViewer
|
||||||
|
Name="container"
|
||||||
|
Grid.Column="0"
|
||||||
|
VerticalAlignment="Stretch">
|
||||||
|
<StackPanel>
|
||||||
|
<Rectangle
|
||||||
|
Name="rectangle1"
|
||||||
|
Height="300"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Fill="{DynamicResource SemiRed2}" />
|
||||||
|
<Rectangle
|
||||||
|
Name="rectangle2"
|
||||||
|
Height="300"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Fill="{DynamicResource SemiPink1}" />
|
||||||
|
<Rectangle
|
||||||
|
Name="rectangle3"
|
||||||
|
Height="300"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Fill="{DynamicResource SemiPurple1}" />
|
||||||
|
<Rectangle
|
||||||
|
Name="rectangle4"
|
||||||
|
Height="300"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Fill="{DynamicResource SemiViolet1}" />
|
||||||
|
<Rectangle
|
||||||
|
Name="rectangle5"
|
||||||
|
Height="300"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Fill="{DynamicResource SemiIndigo1}" />
|
||||||
|
<Rectangle
|
||||||
|
Name="rectangle6"
|
||||||
|
Height="300"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Fill="{DynamicResource SemiBlue1}" />
|
||||||
|
<Rectangle
|
||||||
|
Name="rectangle7"
|
||||||
|
Height="300"
|
||||||
|
HorizontalAlignment="Stretch"
|
||||||
|
Fill="{DynamicResource SemiLightBlue1}" />
|
||||||
|
</StackPanel>
|
||||||
|
</ScrollViewer>
|
||||||
|
<u:Anchor
|
||||||
|
Grid.Column="1"
|
||||||
|
Width="200"
|
||||||
|
Margin="24"
|
||||||
|
TargetContainer="{Binding ElementName=container}">
|
||||||
|
<u:AnchorItem Content="Rectangle 1" Target="{Binding #rectangle1}" />
|
||||||
|
<u:AnchorItem Content="Rectangle 2" Target="{Binding #rectangle2}" />
|
||||||
|
<u:AnchorItem Content="Rectangle 3" Target="{Binding #rectangle3}" />
|
||||||
|
<u:AnchorItem Content="Rectangle 4" Target="{Binding #rectangle4}" />
|
||||||
|
<u:AnchorItem Content="Rectangle 5" Target="{Binding #rectangle5}" />
|
||||||
|
<u:AnchorItem Content="Rectangle 6" Target="{Binding #rectangle6}" />
|
||||||
|
<u:AnchorItem Content="Rectangle 7" Target="{Binding #rectangle7}" />
|
||||||
|
|
||||||
|
</u:Anchor>
|
||||||
|
</Grid>
|
||||||
|
</UserControl>
|
||||||
13
demo/Ursa.Demo/Pages/AnchorDemo.axaml.cs
Normal file
13
demo/Ursa.Demo/Pages/AnchorDemo.axaml.cs
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Markup.Xaml;
|
||||||
|
|
||||||
|
namespace Ursa.Demo.Pages;
|
||||||
|
|
||||||
|
public partial class AnchorDemo : UserControl
|
||||||
|
{
|
||||||
|
public AnchorDemo()
|
||||||
|
{
|
||||||
|
InitializeComponent();
|
||||||
|
}
|
||||||
|
}
|
||||||
8
demo/Ursa.Demo/ViewModels/AnchorDemoViewModel.cs
Normal file
8
demo/Ursa.Demo/ViewModels/AnchorDemoViewModel.cs
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
using CommunityToolkit.Mvvm.ComponentModel;
|
||||||
|
|
||||||
|
namespace Ursa.Demo.ViewModels;
|
||||||
|
|
||||||
|
public partial class AnchorDemoViewModel: ObservableObject
|
||||||
|
{
|
||||||
|
|
||||||
|
}
|
||||||
@@ -86,8 +86,9 @@ public partial class MainViewViewModel : ViewModelBase
|
|||||||
MenuKeys.MenuKeyToolBar => new ToolBarDemoViewModel(),
|
MenuKeys.MenuKeyToolBar => new ToolBarDemoViewModel(),
|
||||||
MenuKeys.MenuKeyTreeComboBox => new TreeComboBoxDemoViewModel(),
|
MenuKeys.MenuKeyTreeComboBox => new TreeComboBoxDemoViewModel(),
|
||||||
MenuKeys.MenuKeyTwoTonePathIcon => new TwoTonePathIconDemoViewModel(),
|
MenuKeys.MenuKeyTwoTonePathIcon => new TwoTonePathIconDemoViewModel(),
|
||||||
MenuKeys.AspectRatioLayout => new AspectRatioLayoutDemoViewModel(),
|
MenuKeys.MenuKeyAspectRatioLayout => new AspectRatioLayoutDemoViewModel(),
|
||||||
MenuKeys.PathPicker => new PathPickerDemoViewModel(),
|
MenuKeys.MenuKeyPathPicker => new PathPickerDemoViewModel(),
|
||||||
|
MenuKeys.MenuKeyAnchor => new AnchorDemoViewModel(),
|
||||||
_ => throw new ArgumentOutOfRangeException(nameof(s), s, null)
|
_ => throw new ArgumentOutOfRangeException(nameof(s), s, null)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public class MenuViewModel : ViewModelBase
|
|||||||
new() { MenuHeader = "MultiComboBox", Key = MenuKeys.MenuKeyMultiComboBox },
|
new() { MenuHeader = "MultiComboBox", Key = MenuKeys.MenuKeyMultiComboBox },
|
||||||
new() { MenuHeader = "Numeric UpDown", Key = MenuKeys.MenuKeyNumericUpDown },
|
new() { MenuHeader = "Numeric UpDown", Key = MenuKeys.MenuKeyNumericUpDown },
|
||||||
new() { MenuHeader = "NumPad", Key = MenuKeys.MenuKeyNumPad },
|
new() { MenuHeader = "NumPad", Key = MenuKeys.MenuKeyNumPad },
|
||||||
new() { MenuHeader = "PathPicker", Key = MenuKeys.PathPicker, Status = "New" },
|
new() { MenuHeader = "PathPicker", Key = MenuKeys.MenuKeyPathPicker, Status = "New" },
|
||||||
new() { MenuHeader = "PinCode", Key = MenuKeys.MenuKeyPinCode },
|
new() { MenuHeader = "PinCode", Key = MenuKeys.MenuKeyPinCode },
|
||||||
new() { MenuHeader = "RangeSlider", Key = MenuKeys.MenuKeyRangeSlider },
|
new() { MenuHeader = "RangeSlider", Key = MenuKeys.MenuKeyRangeSlider },
|
||||||
new() { MenuHeader = "Rating", Key = MenuKeys.MenuKeyRating },
|
new() { MenuHeader = "Rating", Key = MenuKeys.MenuKeyRating },
|
||||||
@@ -67,6 +67,7 @@ public class MenuViewModel : ViewModelBase
|
|||||||
{
|
{
|
||||||
MenuHeader = "Navigation & Menus", Children = new ObservableCollection<MenuItemViewModel>
|
MenuHeader = "Navigation & Menus", Children = new ObservableCollection<MenuItemViewModel>
|
||||||
{
|
{
|
||||||
|
new() { MenuHeader = "Anchor", Key = MenuKeys.MenuKeyAnchor, Status = "New" },
|
||||||
new() { MenuHeader = "Breadcrumb", Key = MenuKeys.MenuKeyBreadcrumb, Status = "Updated" },
|
new() { MenuHeader = "Breadcrumb", Key = MenuKeys.MenuKeyBreadcrumb, Status = "Updated" },
|
||||||
new() { MenuHeader = "Nav Menu", Key = MenuKeys.MenuKeyNavMenu, Status = "Updated" },
|
new() { MenuHeader = "Nav Menu", Key = MenuKeys.MenuKeyNavMenu, Status = "Updated" },
|
||||||
new() { MenuHeader = "Pagination", Key = MenuKeys.MenuKeyPagination },
|
new() { MenuHeader = "Pagination", Key = MenuKeys.MenuKeyPagination },
|
||||||
@@ -78,7 +79,7 @@ public class MenuViewModel : ViewModelBase
|
|||||||
MenuHeader = "Layout & Display",
|
MenuHeader = "Layout & Display",
|
||||||
Children = new ObservableCollection<MenuItemViewModel>
|
Children = new ObservableCollection<MenuItemViewModel>
|
||||||
{
|
{
|
||||||
new() { MenuHeader = "AspectRatioLayout", Key = MenuKeys.AspectRatioLayout },
|
new() { MenuHeader = "AspectRatioLayout", Key = MenuKeys.MenuKeyAspectRatioLayout },
|
||||||
new() { MenuHeader = "Avatar", Key = MenuKeys.MenuKeyAvatar, Status = "WIP" },
|
new() { MenuHeader = "Avatar", Key = MenuKeys.MenuKeyAvatar, Status = "WIP" },
|
||||||
new() { MenuHeader = "Badge", Key = MenuKeys.MenuKeyBadge },
|
new() { MenuHeader = "Badge", Key = MenuKeys.MenuKeyBadge },
|
||||||
new() { MenuHeader = "Banner", Key = MenuKeys.MenuKeyBanner, Status = "Updated" },
|
new() { MenuHeader = "Banner", Key = MenuKeys.MenuKeyBanner, Status = "Updated" },
|
||||||
@@ -154,6 +155,7 @@ public static class MenuKeys
|
|||||||
public const string MenuKeyToolBar = "ToolBar";
|
public const string MenuKeyToolBar = "ToolBar";
|
||||||
public const string MenuKeyTreeComboBox = "TreeComboBox";
|
public const string MenuKeyTreeComboBox = "TreeComboBox";
|
||||||
public const string MenuKeyTwoTonePathIcon = "TwoTonePathIcon";
|
public const string MenuKeyTwoTonePathIcon = "TwoTonePathIcon";
|
||||||
public const string AspectRatioLayout = "AspectRatioLayout";
|
public const string MenuKeyAspectRatioLayout = "AspectRatioLayout";
|
||||||
public const string PathPicker = "PathPicker";
|
public const string MenuKeyPathPicker = "PathPicker";
|
||||||
|
public const string MenuKeyAnchor = "Anchor";
|
||||||
}
|
}
|
||||||
15
src/Ursa.Themes.Semi/Controls/Anchor.axaml
Normal file
15
src/Ursa.Themes.Semi/Controls/Anchor.axaml
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
<ResourceDictionary
|
||||||
|
xmlns="https://github.com/avaloniaui"
|
||||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||||
|
xmlns:u="https://irihi.tech/ursa">
|
||||||
|
<ControlTheme x:Key="{x:Type u:Anchor}" TargetType="{x:Type u:Anchor}">
|
||||||
|
<Setter Property="Template">
|
||||||
|
<ControlTemplate>
|
||||||
|
<ItemsPresenter
|
||||||
|
Name="PART_ItemsPresenter"
|
||||||
|
Margin="{TemplateBinding Padding}"
|
||||||
|
ItemsPanel="{TemplateBinding ItemsPanel}" />
|
||||||
|
</ControlTemplate>
|
||||||
|
</Setter>
|
||||||
|
</ControlTheme>
|
||||||
|
</ResourceDictionary>
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
<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="Anchor.axaml" />
|
||||||
<ResourceInclude Source="AutoCompleteBox.axaml" />
|
<ResourceInclude Source="AutoCompleteBox.axaml" />
|
||||||
<ResourceInclude Source="Avatar.axaml" />
|
<ResourceInclude Source="Avatar.axaml" />
|
||||||
<ResourceInclude Source="Badge.axaml" />
|
<ResourceInclude Source="Badge.axaml" />
|
||||||
|
|||||||
99
src/Ursa/Controls/Anchor/Anchor.cs
Normal file
99
src/Ursa/Controls/Anchor/Anchor.cs
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Animation;
|
||||||
|
using Avalonia.Animation.Easings;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Controls.Primitives;
|
||||||
|
using Avalonia.Styling;
|
||||||
|
|
||||||
|
namespace Ursa.Controls;
|
||||||
|
|
||||||
|
public class Anchor: SelectingItemsControl
|
||||||
|
{
|
||||||
|
public static readonly StyledProperty<ScrollViewer?> TargetContainerProperty = AvaloniaProperty.Register<Anchor, ScrollViewer?>(
|
||||||
|
nameof(TargetContainer));
|
||||||
|
|
||||||
|
public ScrollViewer? TargetContainer
|
||||||
|
{
|
||||||
|
get => GetValue(TargetContainerProperty);
|
||||||
|
set => SetValue(TargetContainerProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static readonly AttachedProperty<string> AnchorIdProperty =
|
||||||
|
AvaloniaProperty.RegisterAttached<Anchor, Control, string>("AnchorId");
|
||||||
|
|
||||||
|
public static void SetAnchorId(Control obj, string value) => obj.SetValue(AnchorIdProperty, value);
|
||||||
|
public static string GetAnchorId(Control obj) => obj.GetValue(AnchorIdProperty);
|
||||||
|
|
||||||
|
|
||||||
|
protected override bool NeedsContainerOverride(object? item, int index, out object? recycleKey)
|
||||||
|
{
|
||||||
|
return NeedsContainer<AnchorItem>(item, out recycleKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Control CreateContainerForItemOverride(object? item, int index, object? recycleKey)
|
||||||
|
{
|
||||||
|
var i = new AnchorItem();
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ScrollToAnchor(string anchorId)
|
||||||
|
{
|
||||||
|
if (TargetContainer is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var target = TargetContainer.FindControl<Control>(anchorId);
|
||||||
|
if (target is null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var targetPosition = target.TranslatePoint(new Point(0, 0), TargetContainer);
|
||||||
|
if (targetPosition.HasValue)
|
||||||
|
{
|
||||||
|
TargetContainer.Offset = new Vector(0, targetPosition.Value.Y);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
internal void ScrollToAnchor(Control 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)
|
||||||
|
{
|
||||||
|
to = TargetContainer.Extent.Height - TargetContainer.Bounds.Height;
|
||||||
|
}
|
||||||
|
Animation animation = new Animation()
|
||||||
|
{
|
||||||
|
Duration = TimeSpan.FromSeconds(0.3),
|
||||||
|
Easing = new QuadraticEaseOut(),
|
||||||
|
Children =
|
||||||
|
{
|
||||||
|
new KeyFrame(){
|
||||||
|
Setters =
|
||||||
|
{
|
||||||
|
new Setter(ScrollViewer.OffsetProperty, new Vector(0, from)),
|
||||||
|
},
|
||||||
|
Cue = new Cue(0.0)
|
||||||
|
},
|
||||||
|
new KeyFrame()
|
||||||
|
{
|
||||||
|
Setters =
|
||||||
|
{
|
||||||
|
new Setter(ScrollViewer.OffsetProperty, new Vector(0, to))
|
||||||
|
},
|
||||||
|
Cue = new Cue(1.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
};
|
||||||
|
animation.RunAsync(TargetContainer);
|
||||||
|
// TargetContainer.Offset = TargetContainer.Offset.WithY(TargetContainer.Offset.Y + targetPosition.Value.Y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
47
src/Ursa/Controls/Anchor/AnchorItem.cs
Normal file
47
src/Ursa/Controls/Anchor/AnchorItem.cs
Normal file
@@ -0,0 +1,47 @@
|
|||||||
|
using System.Windows.Input;
|
||||||
|
using Avalonia;
|
||||||
|
using Avalonia.Controls;
|
||||||
|
using Avalonia.Input;
|
||||||
|
using Avalonia.VisualTree;
|
||||||
|
|
||||||
|
namespace Ursa.Controls;
|
||||||
|
|
||||||
|
public class AnchorItem: ContentControl
|
||||||
|
{
|
||||||
|
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?> AnchorNameProperty = AvaloniaProperty.Register<AnchorItem, string?>(
|
||||||
|
nameof(AnchorName));
|
||||||
|
public string? AnchorName
|
||||||
|
{
|
||||||
|
get => GetValue(AnchorNameProperty);
|
||||||
|
set => SetValue(AnchorNameProperty, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Anchor? _root;
|
||||||
|
|
||||||
|
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnAttachedToVisualTree(e);
|
||||||
|
_root = this.FindAncestorOfType<Anchor>() ??
|
||||||
|
throw new InvalidOperationException("AnchorItem must be inside an Anchor control.");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnPointerPressed(PointerPressedEventArgs e)
|
||||||
|
{
|
||||||
|
base.OnPointerPressed(e);
|
||||||
|
if (_root is null)
|
||||||
|
return;
|
||||||
|
if (Target is not null)
|
||||||
|
{
|
||||||
|
_root.ScrollToAnchor(Target);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user