mirror of
https://gitee.com/chinware/atomui.git
synced 2024-11-30 02:47:45 +08:00
增加 ArrowDecoratedBox 控件
This commit is contained in:
parent
af3633ac2c
commit
195542e5f9
198
src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBox.cs
Normal file
198
src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBox.cs
Normal file
@ -0,0 +1,198 @@
|
||||
using AtomUI.Data;
|
||||
using AtomUI.TokenSystem;
|
||||
using Avalonia;
|
||||
using Avalonia.LogicalTree;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
public enum ArrowPosition
|
||||
{
|
||||
/// <summary>
|
||||
/// Preferred location is below the target element.
|
||||
/// </summary>
|
||||
Bottom,
|
||||
|
||||
/// <summary>
|
||||
/// Preferred location is to the right of the target element.
|
||||
/// </summary>
|
||||
Right,
|
||||
|
||||
/// <summary>
|
||||
/// Preferred location is to the left of the target element.
|
||||
/// </summary>
|
||||
Left,
|
||||
|
||||
/// <summary>
|
||||
/// Preferred location is above the target element.
|
||||
/// </summary>
|
||||
Top,
|
||||
|
||||
/// <summary>
|
||||
/// Preferred location is above the target element, with the left edge of the popup
|
||||
/// aligned with the left edge of the target element.
|
||||
/// </summary>
|
||||
TopEdgeAlignedLeft,
|
||||
|
||||
/// <summary>
|
||||
/// Preferred location is above the target element, with the right edge of popup aligned with right edge of the target element.
|
||||
/// </summary>
|
||||
TopEdgeAlignedRight,
|
||||
|
||||
/// <summary>
|
||||
/// Preferred location is below the target element, with the left edge of popup aligned with left edge of the target element.
|
||||
/// </summary>
|
||||
BottomEdgeAlignedLeft,
|
||||
|
||||
/// <summary>
|
||||
/// Preferred location is below the target element, with the right edge of popup aligned with right edge of the target element.
|
||||
/// </summary>
|
||||
BottomEdgeAlignedRight,
|
||||
|
||||
/// <summary>
|
||||
/// Preferred location is to the left of the target element, with the top edge of popup aligned with top edge of the target element.
|
||||
/// </summary>
|
||||
LeftEdgeAlignedTop,
|
||||
|
||||
/// <summary>
|
||||
/// Preferred location is to the left of the target element, with the bottom edge of popup aligned with bottom edge of the target element.
|
||||
/// </summary>
|
||||
LeftEdgeAlignedBottom,
|
||||
|
||||
/// <summary>
|
||||
/// Preferred location is to the right of the target element, with the top edge of popup aligned with top edge of the target element.
|
||||
/// </summary>
|
||||
RightEdgeAlignedTop,
|
||||
|
||||
/// <summary>
|
||||
/// Preferred location is to the right of the target element, with the bottom edge of popup aligned with bottom edge of the target element.
|
||||
/// </summary>
|
||||
RightEdgeAlignedBottom
|
||||
}
|
||||
|
||||
public partial class ArrowDecoratedBox : BorderedStyleControl, ITokenIdProvider
|
||||
{
|
||||
string ITokenIdProvider.TokenId => ToolTipToken.ID;
|
||||
|
||||
public static readonly StyledProperty<bool> IsShowArrowProperty =
|
||||
AvaloniaProperty.Register<FlyoutPresenter, bool>(nameof(IsShowArrow), true);
|
||||
|
||||
public static readonly StyledProperty<ArrowPosition> ArrowPositionProperty =
|
||||
AvaloniaProperty.Register<FlyoutHost, ArrowPosition>(
|
||||
nameof(ArrowPosition), defaultValue: ArrowPosition.Bottom);
|
||||
|
||||
public static readonly StyledProperty<bool> IsFlippedProperty =
|
||||
AvaloniaProperty.Register<FlyoutHost, bool>(nameof(IsFlipped), false);
|
||||
|
||||
// 指针最顶点位置
|
||||
internal (double, double) ArrowVertexPoint { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// 是否显示指示箭头
|
||||
/// </summary>
|
||||
public bool IsShowArrow
|
||||
{
|
||||
get => GetValue(IsShowArrowProperty);
|
||||
set => SetValue(IsShowArrowProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 箭头渲染的位置
|
||||
/// </summary>
|
||||
public ArrowPosition ArrowPosition
|
||||
{
|
||||
get => GetValue(ArrowPositionProperty);
|
||||
set => SetValue(ArrowPositionProperty, value);
|
||||
}
|
||||
|
||||
public bool IsFlipped
|
||||
{
|
||||
get => GetValue(IsFlippedProperty);
|
||||
set => SetValue(IsFlippedProperty, value);
|
||||
}
|
||||
|
||||
static ArrowDecoratedBox()
|
||||
{
|
||||
AffectsArrange<ArrowDecoratedBox>(ArrowPositionProperty,
|
||||
IsFlippedProperty);
|
||||
AffectsMeasure<ArrowDecoratedBox>(IsShowArrowProperty);
|
||||
}
|
||||
|
||||
public ArrowDecoratedBox()
|
||||
{
|
||||
_customStyle = this;
|
||||
_controlTokenBinder = new ControlTokenBinder(this);
|
||||
}
|
||||
|
||||
public static Direction GetDirection(ArrowPosition arrowPosition)
|
||||
{
|
||||
return arrowPosition switch
|
||||
{
|
||||
ArrowPosition.Left => Direction.Left,
|
||||
ArrowPosition.LeftEdgeAlignedBottom => Direction.Left,
|
||||
ArrowPosition.LeftEdgeAlignedTop => Direction.Left,
|
||||
|
||||
ArrowPosition.Top => Direction.Top,
|
||||
ArrowPosition.TopEdgeAlignedLeft => Direction.Top,
|
||||
ArrowPosition.TopEdgeAlignedRight => Direction.Top,
|
||||
|
||||
ArrowPosition.Right => Direction.Right,
|
||||
ArrowPosition.RightEdgeAlignedBottom => Direction.Right,
|
||||
ArrowPosition.RightEdgeAlignedTop => Direction.Right,
|
||||
|
||||
ArrowPosition.Bottom => Direction.Bottom,
|
||||
ArrowPosition.BottomEdgeAlignedLeft => Direction.Bottom,
|
||||
ArrowPosition.BottomEdgeAlignedRight => Direction.Bottom,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(arrowPosition), arrowPosition,
|
||||
"Invalid value for ArrowPosition")
|
||||
};
|
||||
}
|
||||
|
||||
public static ArrowPosition GetFlipArrowPosition(ArrowPosition arrowPosition)
|
||||
{
|
||||
return arrowPosition switch
|
||||
{
|
||||
ArrowPosition.Left => ArrowPosition.Right,
|
||||
ArrowPosition.LeftEdgeAlignedTop => ArrowPosition.RightEdgeAlignedTop,
|
||||
ArrowPosition.LeftEdgeAlignedBottom => ArrowPosition.RightEdgeAlignedBottom,
|
||||
|
||||
ArrowPosition.Top => ArrowPosition.Bottom,
|
||||
ArrowPosition.TopEdgeAlignedLeft => ArrowPosition.BottomEdgeAlignedLeft,
|
||||
ArrowPosition.TopEdgeAlignedRight => ArrowPosition.BottomEdgeAlignedRight,
|
||||
|
||||
ArrowPosition.Right => ArrowPosition.Left,
|
||||
ArrowPosition.RightEdgeAlignedTop => ArrowPosition.LeftEdgeAlignedTop,
|
||||
ArrowPosition.RightEdgeAlignedBottom => ArrowPosition.LeftEdgeAlignedBottom,
|
||||
|
||||
ArrowPosition.Bottom => ArrowPosition.Top,
|
||||
ArrowPosition.BottomEdgeAlignedLeft => ArrowPosition.TopEdgeAlignedLeft,
|
||||
ArrowPosition.BottomEdgeAlignedRight => ArrowPosition.TopEdgeAlignedRight,
|
||||
_ => throw new ArgumentOutOfRangeException(nameof(arrowPosition), arrowPosition,
|
||||
"Invalid value for ArrowPosition")
|
||||
};
|
||||
}
|
||||
|
||||
protected ArrowPosition GetEffectiveArrowPosition()
|
||||
{
|
||||
if (IsFlipped) {
|
||||
return GetFlipArrowPosition(ArrowPosition);
|
||||
}
|
||||
|
||||
return ArrowPosition;
|
||||
}
|
||||
|
||||
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnAttachedToLogicalTree(e);
|
||||
if (!_initialized) {
|
||||
_customStyle.SetupUi();
|
||||
}
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
base.OnPropertyChanged(e);
|
||||
_customStyle.HandlePropertyChangedForStyle(e);
|
||||
}
|
||||
|
||||
protected virtual void NotifyCreateUi() { }
|
||||
}
|
241
src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBoxStyle.cs
Normal file
241
src/AtomUI.Controls/ArrowDecoratedBox/ArrowDecoratedBoxStyle.cs
Normal file
@ -0,0 +1,241 @@
|
||||
using AtomUI.Data;
|
||||
using AtomUI.Media;
|
||||
using AtomUI.Styling;
|
||||
using AtomUI.Utils;
|
||||
using Avalonia;
|
||||
using Avalonia.Layout;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
public partial class ArrowDecoratedBox : IControlCustomStyle
|
||||
{
|
||||
private bool _initialized = false;
|
||||
private IControlCustomStyle _customStyle;
|
||||
private ControlTokenBinder _controlTokenBinder;
|
||||
private Geometry? _arrowGeometry;
|
||||
private Direction? _lastDirection;
|
||||
private Rect _contentRect;
|
||||
private Rect _arrowRect;
|
||||
|
||||
// 组件的 Token 绑定属性
|
||||
private double _arrowSize;
|
||||
|
||||
private static readonly DirectProperty<ArrowDecoratedBox, double> ArrowSizeTokenProperty
|
||||
= AvaloniaProperty.RegisterDirect<ArrowDecoratedBox, double>(nameof(_arrowSize),
|
||||
(o) => o._arrowSize,
|
||||
(o, v) => o._arrowSize = v);
|
||||
// 组件的 Token 绑定属性
|
||||
|
||||
void IControlCustomStyle.SetupUi()
|
||||
{
|
||||
NotifyCreateUi();
|
||||
_customStyle.ApplyFixedStyleConfig();
|
||||
BuildGeometry(GetDirection(GetEffectiveArrowPosition()));
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
void IControlCustomStyle.ApplyFixedStyleConfig()
|
||||
{
|
||||
NotifyApplyFixedStyleConfig();
|
||||
}
|
||||
|
||||
void IControlCustomStyle.HandlePropertyChangedForStyle(AvaloniaPropertyChangedEventArgs e) { }
|
||||
|
||||
private void BuildGeometry(Direction direction)
|
||||
{
|
||||
if (_lastDirection != direction) {
|
||||
var arrowSize = _arrowSize;
|
||||
_arrowGeometry = CommonShapeBuilder.BuildArrow(arrowSize, 1.5);
|
||||
_lastDirection = direction;
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void NotifyApplyFixedStyleConfig()
|
||||
{
|
||||
_controlTokenBinder.AddControlBinding(MinHeightProperty, GlobalResourceKey.ControlHeight);
|
||||
_controlTokenBinder.AddControlBinding(PaddingProperty, GlobalResourceKey.PaddingXS);
|
||||
_controlTokenBinder.AddControlBinding(ArrowSizeTokenProperty, ArrowDecoratedBoxResourceKey.ArrowSize);
|
||||
}
|
||||
|
||||
public sealed override void Render(DrawingContext context)
|
||||
{
|
||||
if (IsShowArrow) {
|
||||
var direction = GetDirection(GetEffectiveArrowPosition());
|
||||
var matrix = Matrix.CreateTranslation(-_arrowSize / 2, -_arrowSize / 2);
|
||||
if (direction == Direction.Right) {
|
||||
matrix *= Matrix.CreateRotation(MathUtils.Deg2Rad(-90));
|
||||
matrix *= Matrix.CreateTranslation(0, _arrowSize / 2);
|
||||
} else if (direction == Direction.Top) {
|
||||
matrix *= Matrix.CreateRotation(MathUtils.Deg2Rad(180));
|
||||
matrix *= Matrix.CreateTranslation(_arrowSize / 2, _arrowSize / 2);
|
||||
} else if (direction == Direction.Left) {
|
||||
matrix *= Matrix.CreateRotation(MathUtils.Deg2Rad(90));
|
||||
matrix *= Matrix.CreateTranslation(_arrowSize / 2, _arrowSize / 2);
|
||||
} else {
|
||||
matrix *= Matrix.CreateTranslation(_arrowSize / 2, 0);
|
||||
}
|
||||
|
||||
matrix *= Matrix.CreateTranslation(_arrowRect.X, _arrowRect.Y);
|
||||
_arrowGeometry!.Transform = new MatrixTransform(matrix);
|
||||
context.DrawGeometry(Background, null, _arrowGeometry);
|
||||
}
|
||||
}
|
||||
|
||||
protected override Size MeasureOverride(Size availableSize)
|
||||
{
|
||||
var size = base.MeasureOverride(availableSize);
|
||||
var targetWidth = size.Width;
|
||||
var targetHeight = size.Height;
|
||||
targetHeight = Math.Max(MinHeight, targetHeight);
|
||||
if (IsShowArrow) {
|
||||
var realArrowSize = Math.Min(_arrowGeometry!.Bounds.Size.Height, _arrowGeometry!.Bounds.Size.Width);
|
||||
var direction = GetDirection(GetEffectiveArrowPosition());
|
||||
if (direction == Direction.Left || direction == Direction.Right) {
|
||||
targetWidth += realArrowSize;
|
||||
} else {
|
||||
targetHeight += realArrowSize;
|
||||
}
|
||||
}
|
||||
|
||||
return new Size(targetWidth, targetHeight);
|
||||
}
|
||||
|
||||
protected override Size ArrangeOverride(Size finalSize)
|
||||
{
|
||||
var visualChildren = VisualChildren;
|
||||
var visualCount = visualChildren.Count;
|
||||
_contentRect = GetContentRect(finalSize);
|
||||
_arrowRect = GetArrowRect(finalSize);
|
||||
for (int i = 0; i < visualCount; ++i) {
|
||||
var child = visualChildren[i];
|
||||
if (child is Layoutable layoutable) {
|
||||
layoutable.Arrange(_contentRect);
|
||||
}
|
||||
}
|
||||
|
||||
return finalSize;
|
||||
}
|
||||
|
||||
private Rect GetContentRect(Size finalSize)
|
||||
{
|
||||
var offsetX = 0d;
|
||||
var offsetY = 0d;
|
||||
var targetWidth = finalSize.Width;
|
||||
var targetHeight = finalSize.Height;
|
||||
if (IsShowArrow) {
|
||||
var arrowSize = Math.Min(_arrowGeometry!.Bounds.Size.Height, _arrowGeometry!.Bounds.Size.Width) + 0.5;
|
||||
var direction = GetDirection(GetEffectiveArrowPosition());
|
||||
if (direction == Direction.Left || direction == Direction.Right) {
|
||||
targetWidth -= arrowSize;
|
||||
} else {
|
||||
targetHeight -= arrowSize;
|
||||
}
|
||||
|
||||
if (direction == Direction.Right) {
|
||||
offsetX = arrowSize - 0.5;
|
||||
} else if (direction == Direction.Bottom) {
|
||||
offsetY = arrowSize - 0.5;
|
||||
} else if (direction == Direction.Top) {
|
||||
offsetY = 0.5;
|
||||
} else {
|
||||
offsetX = 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
return new Rect(offsetX, offsetY, targetWidth, targetHeight);
|
||||
}
|
||||
|
||||
private Rect GetArrowRect(Size finalSize)
|
||||
{
|
||||
var offsetX = 0d;
|
||||
var offsetY = 0d;
|
||||
var size = _arrowGeometry!.Bounds.Size;
|
||||
var targetWidth = 0d;
|
||||
var targetHeight = 0d;
|
||||
var position = GetEffectiveArrowPosition();
|
||||
if (IsShowArrow) {
|
||||
var minValue = Math.Min(size.Width, size.Height);
|
||||
var maxValue = Math.Max(size.Width, size.Height);
|
||||
if (position == ArrowPosition.Left ||
|
||||
position == ArrowPosition.LeftEdgeAlignedTop ||
|
||||
position == ArrowPosition.LeftEdgeAlignedBottom) {
|
||||
offsetX = finalSize.Width - minValue;
|
||||
if (position == ArrowPosition.Left) {
|
||||
offsetY = (finalSize.Height - maxValue) / 2;
|
||||
} else if (position == ArrowPosition.LeftEdgeAlignedTop) {
|
||||
if (maxValue * 2 > finalSize.Height / 2) {
|
||||
offsetY = minValue;
|
||||
} else {
|
||||
offsetY = maxValue;
|
||||
}
|
||||
} else {
|
||||
if (maxValue * 2 > finalSize.Height / 2) {
|
||||
offsetY = finalSize.Height - minValue - maxValue;
|
||||
} else {
|
||||
offsetY = finalSize.Height - maxValue * 2;
|
||||
}
|
||||
}
|
||||
|
||||
targetWidth = minValue;
|
||||
targetHeight = maxValue;
|
||||
} else if (position == ArrowPosition.Top ||
|
||||
position == ArrowPosition.TopEdgeAlignedLeft ||
|
||||
position == ArrowPosition.TopEdgeAlignedRight) {
|
||||
offsetY = finalSize.Height - minValue;
|
||||
targetWidth = maxValue;
|
||||
targetHeight = minValue;
|
||||
if (position == ArrowPosition.TopEdgeAlignedLeft) {
|
||||
offsetX = maxValue;
|
||||
} else if (position == ArrowPosition.Top) {
|
||||
offsetX = (finalSize.Width - maxValue) / 2;
|
||||
} else {
|
||||
offsetX = finalSize.Width - maxValue * 2;
|
||||
}
|
||||
} else if (position == ArrowPosition.Right ||
|
||||
position == ArrowPosition.RightEdgeAlignedTop ||
|
||||
position == ArrowPosition.RightEdgeAlignedBottom) {
|
||||
targetWidth = minValue;
|
||||
targetHeight = maxValue;
|
||||
if (position == ArrowPosition.Right) {
|
||||
offsetY = (finalSize.Height - maxValue) / 2;
|
||||
} else if (position == ArrowPosition.RightEdgeAlignedTop) {
|
||||
if (maxValue * 2 > finalSize.Height / 2) {
|
||||
offsetY = minValue;
|
||||
} else {
|
||||
offsetY = maxValue;
|
||||
}
|
||||
} else {
|
||||
if (maxValue * 2 > finalSize.Height / 2) {
|
||||
offsetY = finalSize.Height - minValue - maxValue;
|
||||
} else {
|
||||
offsetY = finalSize.Height - maxValue * 2;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (position == ArrowPosition.BottomEdgeAlignedLeft) {
|
||||
offsetX = maxValue;
|
||||
} else if (position == ArrowPosition.Bottom) {
|
||||
offsetX = (finalSize.Width - maxValue) / 2;
|
||||
} else {
|
||||
offsetX = finalSize.Width - maxValue * 2;
|
||||
}
|
||||
|
||||
targetWidth = maxValue;
|
||||
targetHeight = minValue;
|
||||
}
|
||||
}
|
||||
|
||||
var targetRect = new Rect(offsetX, offsetY, targetWidth, targetHeight);
|
||||
var center = targetRect.Center;
|
||||
// 计算中点
|
||||
var direction = GetDirection(position);
|
||||
if (direction == Direction.Left || direction == Direction.Right) {
|
||||
ArrowVertexPoint = (center.Y, finalSize.Height - center.Y);
|
||||
} else if (direction == Direction.Top || direction == Direction.Bottom) {
|
||||
ArrowVertexPoint = (center.X, finalSize.Width - center.X);
|
||||
}
|
||||
|
||||
return targetRect;
|
||||
}
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
using AtomUI.TokenSystem;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
[ControlDesignToken]
|
||||
public class ArrowDecoratedBoxToken : AbstractControlDesignToken
|
||||
{
|
||||
public const string ID = "ArrowDecoratedBox";
|
||||
|
||||
/// <summary>
|
||||
/// 箭头三角形大小
|
||||
/// </summary>
|
||||
public double ArrowSize { get; set; }
|
||||
|
||||
public ArrowDecoratedBoxToken()
|
||||
: base(ID)
|
||||
{
|
||||
}
|
||||
|
||||
internal override void CalculateFromAlias()
|
||||
{
|
||||
base.CalculateFromAlias();
|
||||
ArrowSize = _globalToken.SeedToken.SizePopupArrow / 1.3;
|
||||
}
|
||||
}
|
@ -78,7 +78,7 @@ public class BorderedStyleControl : StyledControl
|
||||
/// Gets or sets the decorated control.
|
||||
/// </summary>
|
||||
[Content]
|
||||
protected Control? Child
|
||||
public Control? Child
|
||||
{
|
||||
get => GetValue(ChildProperty);
|
||||
set => SetValue(ChildProperty, value);
|
||||
|
@ -1,11 +1,71 @@
|
||||
using Avalonia.Controls;
|
||||
using System.ComponentModel;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Metadata;
|
||||
using Avalonia.Styling;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
public class Flyout : PopupFlyoutBase
|
||||
{
|
||||
/// <summary>
|
||||
/// Defines the <see cref="Content"/> property
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<object> ContentProperty =
|
||||
AvaloniaProperty.Register<Flyout, object>(nameof(Content));
|
||||
|
||||
private Classes? _classes;
|
||||
|
||||
/// <summary>
|
||||
/// Gets the Classes collection to apply to the FlyoutPresenter this Flyout is hosting
|
||||
/// </summary>
|
||||
public Classes FlyoutPresenterClasses => _classes ??= new Classes();
|
||||
|
||||
/// <summary>
|
||||
/// Defines the <see cref="FlyoutPresenterTheme"/> property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<ControlTheme?> FlyoutPresenterThemeProperty =
|
||||
AvaloniaProperty.Register<Flyout, ControlTheme?>(nameof(FlyoutPresenterTheme));
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the <see cref="ControlTheme"/> that is applied to the container element generated for the flyout presenter.
|
||||
/// </summary>
|
||||
public ControlTheme? FlyoutPresenterTheme
|
||||
{
|
||||
get => GetValue(FlyoutPresenterThemeProperty);
|
||||
set => SetValue(FlyoutPresenterThemeProperty, value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Gets or sets the content to display in this flyout
|
||||
/// </summary>
|
||||
[Content]
|
||||
public object Content
|
||||
{
|
||||
get => GetValue(ContentProperty);
|
||||
set => SetValue(ContentProperty, value);
|
||||
}
|
||||
|
||||
protected override Control CreatePresenter()
|
||||
{
|
||||
return default!;
|
||||
return new FlyoutPresenter
|
||||
{
|
||||
[!ContentControl.ContentProperty] = this[!ContentProperty]
|
||||
};
|
||||
}
|
||||
|
||||
protected override void OnOpening(CancelEventArgs args)
|
||||
{
|
||||
if (Popup.Child is { } presenter) {
|
||||
if (_classes != null) {
|
||||
SetPresenterClasses(presenter, FlyoutPresenterClasses);
|
||||
}
|
||||
|
||||
if (FlyoutPresenterTheme is { } theme) {
|
||||
presenter.SetValue(Control.ThemeProperty, theme);
|
||||
}
|
||||
}
|
||||
|
||||
base.OnOpening(args);
|
||||
}
|
||||
}
|
@ -1,4 +1,5 @@
|
||||
using AtomUI.Input;
|
||||
using System.Reactive.Disposables;
|
||||
using AtomUI.Input;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
@ -48,9 +49,9 @@ public class FlyoutHost : Control
|
||||
/// <summary>
|
||||
/// Defines the ToolTip.Placement property.
|
||||
/// </summary>
|
||||
public static readonly StyledProperty<PlacementType> PlacementProperty =
|
||||
AvaloniaProperty.Register<FlyoutHost, PlacementType>(
|
||||
nameof(Placement), defaultValue: PlacementType.Top);
|
||||
public static readonly StyledProperty<PlacementMode> PlacementProperty =
|
||||
AvaloniaProperty.Register<FlyoutHost, PlacementMode>(
|
||||
nameof(Placement), defaultValue: PlacementMode.Top);
|
||||
|
||||
/// <summary>
|
||||
/// 距离 anchor 的边距,根据垂直和水平进行设置
|
||||
@ -104,7 +105,7 @@ public class FlyoutHost : Control
|
||||
set => SetValue(IsPointAtCenterProperty, value);
|
||||
}
|
||||
|
||||
public PlacementType Placement
|
||||
public PlacementMode Placement
|
||||
{
|
||||
get => GetValue(PlacementProperty);
|
||||
set => SetValue(PlacementProperty, value);
|
||||
@ -135,7 +136,12 @@ public class FlyoutHost : Control
|
||||
}
|
||||
|
||||
private bool _initialized = false;
|
||||
private IDisposable? _triggerDisposable;
|
||||
private CompositeDisposable _compositeDisposable;
|
||||
|
||||
public FlyoutHost()
|
||||
{
|
||||
_compositeDisposable = new CompositeDisposable();
|
||||
}
|
||||
|
||||
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
|
||||
{
|
||||
@ -150,7 +156,6 @@ public class FlyoutHost : Control
|
||||
((ISetLogicalParent)AnchorTarget).SetParent(this);
|
||||
VisualChildren.Add(AnchorTarget);
|
||||
}
|
||||
|
||||
_initialized = true;
|
||||
}
|
||||
}
|
||||
@ -159,12 +164,18 @@ public class FlyoutHost : Control
|
||||
{
|
||||
base.OnAttachedToVisualTree(e);
|
||||
SetupTriggerHandler();
|
||||
SetupFlyoutProperties();
|
||||
}
|
||||
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
|
||||
{
|
||||
base.OnDetachedFromVisualTree(e);
|
||||
_triggerDisposable?.Dispose();
|
||||
_compositeDisposable?.Dispose();
|
||||
}
|
||||
|
||||
private void SetupFlyoutProperties()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
private void SetupTriggerHandler()
|
||||
@ -174,33 +185,46 @@ public class FlyoutHost : Control
|
||||
}
|
||||
|
||||
if (Trigger == FlyoutTriggerType.Hover) {
|
||||
_triggerDisposable = IsPointAtCenterProperty.Changed.Subscribe(args =>
|
||||
_compositeDisposable.Add(IsPointAtCenterProperty.Changed.Subscribe(args =>
|
||||
{
|
||||
if (args.Sender == AnchorTarget) {
|
||||
HandleAnchorTargetHover(args);
|
||||
}
|
||||
});
|
||||
}));
|
||||
} else if (Trigger == FlyoutTriggerType.Focus) {
|
||||
_triggerDisposable = IsFocusedProperty.Changed.Subscribe(args =>
|
||||
_compositeDisposable.Add(IsFocusedProperty.Changed.Subscribe(args =>
|
||||
{
|
||||
if (args.Sender == AnchorTarget) {
|
||||
HandleAnchorTargetFocus(args);
|
||||
}
|
||||
});
|
||||
}));
|
||||
} else if (Trigger == FlyoutTriggerType.Click) {
|
||||
_triggerDisposable = InputManagerEx.SubscribeRawPointerEvent(type => type == RawPointerEventType.LeftButtonUp,
|
||||
HandleAnchorTargetClick);
|
||||
_compositeDisposable.Add(InputManagerEx.SubscribeRawPointerEvent(
|
||||
type => type == RawPointerEventType.LeftButtonUp,
|
||||
HandleAnchorTargetClick));
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleAnchorTargetHover(AvaloniaPropertyChangedEventArgs<bool> e)
|
||||
{
|
||||
Console.WriteLine(e.NewValue);
|
||||
if (Flyout is not null) {
|
||||
if (e.GetNewValue<bool>()) {
|
||||
ShowFlyout();
|
||||
} else {
|
||||
HideFlyout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleAnchorTargetFocus(AvaloniaPropertyChangedEventArgs<bool> e)
|
||||
{
|
||||
Console.WriteLine(e.NewValue);
|
||||
if (Flyout is not null) {
|
||||
if (e.GetNewValue<bool>()) {
|
||||
ShowFlyout();
|
||||
} else {
|
||||
HideFlyout();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleAnchorTargetClick(RawPointerEventArgs e)
|
||||
@ -210,9 +234,27 @@ public class FlyoutHost : Control
|
||||
if (!pos.HasValue) {
|
||||
return;
|
||||
}
|
||||
|
||||
var bounds = new Rect(pos.Value, AnchorTarget.Bounds.Size);
|
||||
if (bounds.Contains(e.Position())) {
|
||||
if (Flyout is not null) {
|
||||
if (Flyout.IsOpen) {
|
||||
HideFlyout();
|
||||
} else {
|
||||
ShowFlyout();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ShowFlyout()
|
||||
{
|
||||
HideFlyout();
|
||||
}
|
||||
|
||||
public void HideFlyout()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
13
src/AtomUI.Controls/Flyouts/FlyoutPresenter.cs
Normal file
13
src/AtomUI.Controls/Flyouts/FlyoutPresenter.cs
Normal file
@ -0,0 +1,13 @@
|
||||
using AtomUI.TokenSystem;
|
||||
using Avalonia;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
using AvaloniaFlyoutPresenter = Avalonia.Controls.FlyoutPresenter;
|
||||
|
||||
public partial class FlyoutPresenter : AvaloniaFlyoutPresenter, ITokenIdProvider
|
||||
{
|
||||
string ITokenIdProvider.TokenId => FlyoutPresenterToken.ID;
|
||||
|
||||
|
||||
}
|
22
src/AtomUI.Controls/Flyouts/FlyoutPresenterStyle.cs
Normal file
22
src/AtomUI.Controls/Flyouts/FlyoutPresenterStyle.cs
Normal file
@ -0,0 +1,22 @@
|
||||
using AtomUI.Styling;
|
||||
using Avalonia;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
public partial class FlyoutPresenter : IControlCustomStyle
|
||||
{
|
||||
void IControlCustomStyle.InitOnConstruct() {}
|
||||
|
||||
void IControlCustomStyle.SetupUi()
|
||||
{
|
||||
|
||||
}
|
||||
void IControlCustomStyle.AfterUiStructureReady() {}
|
||||
void IControlCustomStyle.SetupTransitions() {}
|
||||
void IControlCustomStyle.CollectStyleState() {}
|
||||
void IControlCustomStyle.ApplyVariableStyleConfig() {}
|
||||
void IControlCustomStyle.ApplyFixedStyleConfig() {}
|
||||
void IControlCustomStyle.ApplyRenderScalingAwareStyleConfig() {}
|
||||
void IControlCustomStyle.ApplySizeTypeStyleConfig() {}
|
||||
void IControlCustomStyle.HandlePropertyChangedForStyle(AvaloniaPropertyChangedEventArgs e) {}
|
||||
}
|
15
src/AtomUI.Controls/Flyouts/FlyoutPresenterToken.cs
Normal file
15
src/AtomUI.Controls/Flyouts/FlyoutPresenterToken.cs
Normal file
@ -0,0 +1,15 @@
|
||||
using AtomUI.TokenSystem;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
[ControlDesignToken]
|
||||
internal class FlyoutPresenterToken : AbstractControlDesignToken
|
||||
{
|
||||
public const string ID = "FlyoutPresenter";
|
||||
|
||||
public FlyoutPresenterToken()
|
||||
: base(ID)
|
||||
{
|
||||
}
|
||||
|
||||
}
|
@ -1,7 +1,4 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Primitives;
|
||||
using Avalonia.Metadata;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
@ -9,5 +6,29 @@ using AvaloniaPopupFlyoutBase = Avalonia.Controls.Primitives.PopupFlyoutBase;
|
||||
|
||||
public abstract class PopupFlyoutBase : AvaloniaPopupFlyoutBase
|
||||
{
|
||||
private PopupShadowDecorator? _popupDecorator;
|
||||
|
||||
protected override bool ShowAtCore(Control placementTarget, bool showAtPointer = false)
|
||||
{
|
||||
_popupDecorator ??= new PopupShadowDecorator(Popup);
|
||||
return base.ShowAtCore(placementTarget, showAtPointer);
|
||||
}
|
||||
|
||||
internal static void SetPresenterClasses(Control? presenter, Classes classes)
|
||||
{
|
||||
if (presenter is null) {
|
||||
return;
|
||||
}
|
||||
|
||||
//Remove any classes no longer in use, ignoring pseudo classes
|
||||
for (int i = presenter.Classes.Count - 1; i >= 0; i--) {
|
||||
if (!classes.Contains(presenter.Classes[i]) &&
|
||||
!presenter.Classes[i].Contains(':')) {
|
||||
presenter.Classes.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
//Add new classes
|
||||
presenter.Classes.AddRange(classes);
|
||||
}
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
public class PopupFlyoutBaseProperties
|
||||
{
|
||||
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
public class PopupFlyoutBaseStyle
|
||||
{
|
||||
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
public class PopupFlyoutBaseToken
|
||||
{
|
||||
|
||||
}
|
@ -13,6 +13,11 @@
|
||||
public const string ExtraElementMargin = "ExtraElementMargin";
|
||||
}
|
||||
|
||||
public static class ArrowDecoratedBoxResourceKey
|
||||
{
|
||||
public const string ArrowSize = "ArrowSize";
|
||||
}
|
||||
|
||||
public static class ButtonResourceKey
|
||||
{
|
||||
public const string FontWeight = "FontWeight";
|
||||
|
@ -1,6 +0,0 @@
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
public partial class ArrowPopup
|
||||
{
|
||||
|
||||
}
|
@ -1,6 +0,0 @@
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
public partial class ArrowPopup
|
||||
{
|
||||
|
||||
}
|
@ -3,7 +3,7 @@ using Avalonia.Media;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
public interface IShadowLayer
|
||||
public interface IShadowDecorator
|
||||
{
|
||||
public void AttachToTarget(Control control);
|
||||
public void SetShadowMaskGeometry(Geometry geometry);
|
||||
|
@ -1,14 +0,0 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.Primitives;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
public class PopupDecorator : AvaloniaObject
|
||||
{
|
||||
private Popup _popup;
|
||||
|
||||
public PopupDecorator(Popup popup)
|
||||
{
|
||||
_popup = popup;
|
||||
}
|
||||
}
|
24
src/AtomUI.Controls/Popup/PopupShadowDecorator.cs
Normal file
24
src/AtomUI.Controls/Popup/PopupShadowDecorator.cs
Normal file
@ -0,0 +1,24 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.Primitives;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
public class PopupShadowDecorator : AvaloniaObject
|
||||
{
|
||||
private Popup _popup;
|
||||
|
||||
public PopupShadowDecorator(Popup popup)
|
||||
{
|
||||
_popup = popup;
|
||||
}
|
||||
|
||||
public virtual void Open()
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public virtual void Close()
|
||||
{
|
||||
|
||||
}
|
||||
}
|
@ -4,7 +4,7 @@ using Avalonia.Media;
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
public partial class PopupShadowLayer
|
||||
public class PopupShadowLayer : AvaloniaObject, IShadowDecorator
|
||||
{
|
||||
public void AttachToTarget(Control control)
|
||||
{
|
||||
|
@ -1,6 +0,0 @@
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
public class PopupStyle
|
||||
{
|
||||
|
||||
}
|
@ -2,7 +2,13 @@
|
||||
|
||||
namespace AtomUI.Controls;
|
||||
|
||||
internal class PopupToken : AbstractDesignToken
|
||||
[ControlDesignToken]
|
||||
internal class PopupToken : AbstractControlDesignToken
|
||||
{
|
||||
|
||||
public const string ID = "Popup";
|
||||
|
||||
public PopupToken()
|
||||
: base(ID)
|
||||
{
|
||||
}
|
||||
}
|
@ -126,7 +126,6 @@ public partial class ToolTip : BorderedStyleControl, ITokenIdProvider
|
||||
|
||||
private Popup? _popup;
|
||||
private Action<IPopupHost?>? _popupHostChangedHandler;
|
||||
private CompositeDisposable? _subscriptions;
|
||||
private AvaloniaWin? _currentAnchorWindow;
|
||||
|
||||
/// <summary>
|
||||
@ -789,7 +788,6 @@ public partial class ToolTip : BorderedStyleControl, ITokenIdProvider
|
||||
|
||||
private void Close()
|
||||
{
|
||||
|
||||
if (_popup is not null) {
|
||||
_popup.IsOpen = false;
|
||||
SetPopupParent(_popup, null);
|
||||
|
@ -60,9 +60,8 @@ public static class ThemeResourceUtils
|
||||
if (application is null) {
|
||||
return null;
|
||||
}
|
||||
if (themeVariant is null) {
|
||||
themeVariant = (application as IThemeVariantHost).ActualThemeVariant;
|
||||
}
|
||||
|
||||
themeVariant ??= (application as IThemeVariantHost).ActualThemeVariant;
|
||||
if (Application.Current!.TryFindResource(resourceKey, themeVariant, out var value)) {
|
||||
return value;
|
||||
}
|
||||
@ -85,9 +84,8 @@ public static class ThemeResourceUtils
|
||||
if (application is null) {
|
||||
throw new ApplicationException("The application instance does not exist");
|
||||
}
|
||||
if (themeVariant is null) {
|
||||
themeVariant = (application as IThemeVariantHost).ActualThemeVariant;
|
||||
}
|
||||
|
||||
themeVariant ??= (application as IThemeVariantHost).ActualThemeVariant;
|
||||
return application.Styles.GetResourceObservable(resourceKey, themeVariant, converter);
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user