添加 LiteWindow

This commit is contained in:
polarboy 2024-06-29 19:07:01 +08:00
parent 995f4d0680
commit 900a57f414
19 changed files with 419 additions and 101 deletions

View File

@ -56,7 +56,7 @@ public partial class ArrowDecoratedBox : IControlCustomStyle
_controlTokenBinder.AddControlBinding(MinHeightProperty, GlobalResourceKey.ControlHeight);
_controlTokenBinder.AddControlBinding(PaddingProperty, GlobalResourceKey.PaddingXS);
_controlTokenBinder.AddControlBinding(ArrowSizeTokenProperty, ArrowDecoratedBoxResourceKey.ArrowSize);
_controlTokenBinder.AddControlBinding(BackgroundProperty, GlobalResourceKey.ColorBgContainer);
_controlTokenBinder.AddControlBinding(BackgroundProperty, GlobalResourceKey.ColorPrimary);
_controlTokenBinder.AddControlBinding(CornerRadiusProperty, GlobalResourceKey.BorderRadius);
}

View File

@ -1,6 +1,6 @@
using AtomUI.Data;
using Avalonia;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Layout;
namespace AtomUI.Controls;
@ -23,7 +23,7 @@ public abstract class PopupFlyoutBase : AvaloniaPopupFlyoutBase
/// </summary>
public static readonly StyledProperty<bool> IsPointAtCenterProperty =
AvaloniaProperty.Register<PopupFlyoutBase, bool>(nameof(IsPointAtCenter), false);
/// <summary>
/// 距离 anchor 的边距,根据垂直和水平进行设置
/// 但是对某些组合无效,比如跟随鼠标的情况
@ -31,7 +31,7 @@ public abstract class PopupFlyoutBase : AvaloniaPopupFlyoutBase
/// </summary>
public static readonly StyledProperty<double> MarginToAnchorProperty =
AvaloniaProperty.Register<PopupFlyoutBase, double>(nameof(MarginToAnchor), 0);
public bool IsShowArrow
{
get => GetValue(IsShowArrowProperty);
@ -43,13 +43,13 @@ public abstract class PopupFlyoutBase : AvaloniaPopupFlyoutBase
get => GetValue(IsPointAtCenterProperty);
set => SetValue(IsPointAtCenterProperty, value);
}
public double MarginToAnchor
{
get => GetValue(MarginToAnchorProperty);
set => SetValue(MarginToAnchorProperty, value);
}
internal static void SetPresenterClasses(Control? presenter, Classes classes)
{
if (presenter is null) {
@ -68,5 +68,38 @@ public abstract class PopupFlyoutBase : AvaloniaPopupFlyoutBase
presenter.Classes.AddRange(classes);
}
internal virtual void NotifyPopupCreated(Popup popup) { }
protected internal virtual void NotifyPopupCreated(Popup popup) { }
protected internal virtual void NotifyPositionPopup(bool showAtPointer)
{
Size sz;
// Popup.Child can't be null here, it was set in ShowAtCore.
if (Popup.Child!.DesiredSize == default) {
// Popup may not have been shown yet. Measure content
sz = LayoutHelper.MeasureChild(Popup.Child, Size.Infinity, new Thickness());
} else {
sz = Popup.Child.DesiredSize;
}
Popup.VerticalOffset = VerticalOffset;
Popup.HorizontalOffset = HorizontalOffset;
Popup.PlacementAnchor = PlacementAnchor;
Popup.PlacementGravity = PlacementGravity;
if (showAtPointer) {
Popup.Placement = PlacementMode.Pointer;
} else {
Popup.Placement = Placement;
Popup.PlacementConstraintAdjustment = PlacementConstraintAdjustment;
}
}
protected override void OnOpened()
{
base.OnOpened();
}
protected override void OnClosed()
{
base.OnClosed();
}
}

View File

@ -67,6 +67,12 @@ internal static class PopupFlyoutBaseInterceptor
{
__instance.NotifyPopupHostPositionUpdated(popupHost, placementTarget);
}
public static bool PositionPopupInterceptor(PopupFlyoutBase __instance, bool showAtPointer)
{
__instance.NotifyPositionPopup(showAtPointer);
return false;
}
}
internal static class PopupFlyoutBaseInterceptorRegister
@ -75,6 +81,7 @@ internal static class PopupFlyoutBaseInterceptorRegister
{
RegisterPopupFlyoutBaseCreatePopup(harmony);
RegisterPopupUpdateHostPosition(harmony);
RegisterPopupPositionPopup(harmony);
}
private static void RegisterPopupFlyoutBaseCreatePopup(Harmony harmony)
@ -94,4 +101,13 @@ internal static class PopupFlyoutBaseInterceptorRegister
BindingFlags.Static | BindingFlags.Public);
harmony.Patch(origin, postfix: new HarmonyMethod(postfixInterceptor));
}
private static void RegisterPopupPositionPopup(Harmony harmony)
{
var origin = typeof(AvaloniaPopupFlyoutBase).GetMethod("PositionPopup", BindingFlags.Instance | BindingFlags.NonPublic);
var prefixInterceptor = typeof(PopupFlyoutBaseInterceptor)
.GetMethod(nameof(PopupFlyoutBaseInterceptor.PositionPopupInterceptor),
BindingFlags.Static | BindingFlags.Public);
harmony.Patch(origin, prefix: new HarmonyMethod(prefixInterceptor));
}
}

View File

@ -22,7 +22,7 @@ public abstract class AbstractPopup : AvaloniaPopup
}
}
internal virtual void NotifyPopupHostPositionUpdated(IPopupHost popupHost, Control placementTarget)
protected internal virtual void NotifyPopupHostPositionUpdated(IPopupHost popupHost, Control placementTarget)
{
if (_popupHost is null) {
_popupHost = new WeakReference<IPopupHost>(popupHost);

View File

@ -0,0 +1,108 @@
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.VisualTree;
namespace AtomUI.Controls;
public class LiteWindow : WindowBase, IHostedVisualTreeRoot, IDisposable
{
static LiteWindow()
{
BackgroundProperty.OverrideDefaultValue(typeof(LiteWindow), Brushes.White);
}
public LiteWindow(TopLevel parent, IPopupImpl impl)
: this(parent, impl, null) { }
/// <summary>
/// Initializes a new instance of the <see cref="PopupRoot"/> class.
/// </summary>
/// <param name="parent">The popup parent.</param>
/// <param name="impl">The popup implementation.</param>
/// <param name="dependencyResolver">
/// The dependency resolver to use. If null the default dependency resolver will be used.
/// </param>
public LiteWindow(TopLevel parent, IPopupImpl impl, IAvaloniaDependencyResolver? dependencyResolver)
: base(impl, dependencyResolver)
{
ParentTopLevel = parent;
impl.SetWindowManagerAddShadowHint(false);
}
/// <summary>
/// Gets the platform-specific window implementation.
/// </summary>
public new IPopupImpl? PlatformImpl => (IPopupImpl?)base.PlatformImpl;
/// <summary>
/// Gets the control that is hosting the popup root.
/// </summary>
Visual? IHostedVisualTreeRoot.Host
{
get
{
// If the parent is attached to a visual tree, then return that. However the parent
// will possibly be a standalone Popup (i.e. a Popup not attached to a visual tree,
// created by e.g. a ContextMenu): if this is the case, return the ParentTopLevel
// if set. This helps to allow the focus manager to restore the focus to the outer
// scope when the popup is closed.
var parentVisual = Parent as Visual;
if (parentVisual?.GetVisualRoot() != null) return parentVisual;
return ParentTopLevel ?? parentVisual;
}
}
public TopLevel ParentTopLevel { get; }
/// <inheritdoc/>
public void Dispose()
{
PlatformImpl?.Dispose();
}
public void SetChild(Control? control) => Content = control;
protected override Size MeasureOverride(Size availableSize)
{
var maxAutoSize = PlatformImpl?.MaxAutoSizeHint ?? Size.Infinity;
var constraint = availableSize;
if (double.IsInfinity(constraint.Width)) {
constraint = constraint.WithWidth(maxAutoSize.Width);
}
if (double.IsInfinity(constraint.Height)) {
constraint = constraint.WithHeight(maxAutoSize.Height);
}
var measured = base.MeasureOverride(constraint);
var width = measured.Width;
var height = measured.Height;
var widthCache = Width;
var heightCache = Height;
if (!double.IsNaN(widthCache)) {
width = widthCache;
}
width = Math.Min(width, MaxWidth);
width = Math.Max(width, MinWidth);
if (!double.IsNaN(heightCache)) {
height = heightCache;
}
height = Math.Min(height, MaxHeight);
height = Math.Max(height, MinHeight);
return new Size(width, height);
}
protected override Size ArrangeSetBounds(Size size)
{
return ClientSize;
}
}

View File

@ -1,4 +1,5 @@
using AtomUI.Data;
using System.Reactive.Disposables;
using AtomUI.Data;
using AtomUI.Styling;
using AtomUI.Utils;
using Avalonia;
@ -21,6 +22,7 @@ public class Popup : AbstractPopup
private PopupShadowLayer _shadowLayer;
private GlobalTokenBinder _globalTokenBinder;
private CompositeDisposable _compositeDisposable;
private bool _initialized;
public Popup()
@ -28,9 +30,8 @@ public class Popup : AbstractPopup
IsLightDismissEnabled = false;
_shadowLayer = new PopupShadowLayer();
_shadowLayer.AttachToTarget(this);
// TODO 是否需要释放
BindUtils.RelayBind(this, MaskShadowsProperty, _shadowLayer);
_globalTokenBinder = new GlobalTokenBinder();
_compositeDisposable = new CompositeDisposable();
}
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
@ -38,7 +39,19 @@ public class Popup : AbstractPopup
base.OnAttachedToLogicalTree(e);
if (!_initialized) {
_globalTokenBinder.AddGlobalBinding(this, MaskShadowsProperty, GlobalResourceKey.BoxShadowsSecondary);
_compositeDisposable.Add(BindUtils.RelayBind(this, MaskShadowsProperty, _shadowLayer));
_initialized = true;
}
}
protected override void OnDetachedFromLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnDetachedFromLogicalTree(e);
_compositeDisposable.Dispose();
}
private PopupShadowLayer CreateShadowLayer()
{
return default!;
}
}

View File

@ -1,8 +1,12 @@
using AtomUI.Platform.Windows;
using System.Reactive.Disposables;
using System.Reflection;
using AtomUI.Media;
using AtomUI.Platform.Windows;
using AtomUI.Utils;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Media;
namespace AtomUI.Controls;
@ -18,19 +22,31 @@ internal class PopupShadowLayer : AbstractPopup, IShadowDecorator
set => SetValue(MaskShadowsProperty, value);
}
private static readonly FieldInfo ManagedPopupPositionerPopupInfo;
private IManagedPopupPositionerPopup? _managedPopupPositionerPopup;
static PopupShadowLayer()
{
ManagedPopupPositionerPopupInfo = typeof(ManagedPopupPositioner).GetField("_popup",
BindingFlags.Instance | BindingFlags.NonPublic)!;
}
public PopupShadowLayer()
{
WindowManagerAddShadowHint = false;
IsLightDismissEnabled = false;
_compositeDisposable = new CompositeDisposable();
}
private Popup? _target;
private ShadowRenderer? _shadowRenderer;
private CompositeDisposable _compositeDisposable;
public void AttachToTarget(Popup popup)
{
if (_target is not null && _target != popup) {
// 释放资源
_compositeDisposable.Dispose();
}
_target = popup;
ConfigureShadowPopup();
@ -43,17 +59,14 @@ internal class PopupShadowLayer : AbstractPopup, IShadowDecorator
private void ConfigureShadowPopup()
{
var offset = CalculateOffset();
var offset = CalculatePopupOffset();
HorizontalOffset = offset.X;
VerticalOffset = offset.Y;
// // 绑定资源要管理起来
if (_target is not null) {
BindUtils.RelayBind(_target, PlacementProperty, this);
BindUtils.RelayBind(_target, PlacementGravityProperty, this);
BindUtils.RelayBind(_target, PlacementAnchorProperty, this);
BindUtils.RelayBind(_target, PlacementTargetProperty, this);
_target.Opened += HandleTargetOpened;
_target.Closed += HandleTargetClosed;
SetupRelayBindings();
}
if (_shadowRenderer is null) {
_shadowRenderer ??= new ShadowRenderer();
@ -61,31 +74,50 @@ internal class PopupShadowLayer : AbstractPopup, IShadowDecorator
}
}
private void SetupRelayBindings()
{
if (_target is not null) {
// _compositeDisposable.Add(BindUtils.RelayBind(_target, PlacementProperty, this));
// _compositeDisposable.Add(BindUtils.RelayBind(_target, PlacementGravityProperty, this));
// _compositeDisposable.Add(BindUtils.RelayBind(_target, PlacementAnchorProperty, this));
_compositeDisposable.Add(BindUtils.RelayBind(_target, PlacementTargetProperty, this));
}
}
private void HandleTargetOpened(object? sender, EventArgs args)
{
SetupShadowRenderer();
// Open();
Open();
}
private void HandleTargetClosed(object? sender, EventArgs args)
{
// Close();
_compositeDisposable.Dispose();
Close();
}
protected override void NotifyClosed()
{
base.NotifyClosed();
_managedPopupPositionerPopup = null;
}
private void SetupShadowRenderer()
{
var popupContent = _target!.Child;
if (popupContent is not null) {
SetupRelayBindings();
if (_target?.Child is not null && _shadowRenderer is not null) {
// 理论上现在已经有大小了
_shadowRenderer!.Width = popupContent.DesiredSize.Width;
_shadowRenderer.Height = popupContent.DesiredSize.Height;
var content = _target?.Child!;
var rendererSize = CalculateShadowRendererSize(content);
_shadowRenderer.Shadows = MaskShadows;
_shadowRenderer.Width = rendererSize.Width;
_shadowRenderer.Height = rendererSize.Height;
if (popupContent is IShadowMaskInfoProvider shadowMaskInfoProvider) {
if (content is IShadowMaskInfoProvider shadowMaskInfoProvider) {
_shadowRenderer.MaskCornerRadius = shadowMaskInfoProvider.GetMaskCornerRadius();
} else if (popupContent is BorderedStyleControl bordered) {
} else if (content is BorderedStyleControl bordered) {
_shadowRenderer.MaskCornerRadius = bordered.CornerRadius;
} else if (popupContent is TemplatedControl templatedControl) {
} else if (content is TemplatedControl templatedControl) {
_shadowRenderer.MaskCornerRadius = templatedControl.CornerRadius;
}
}
@ -97,11 +129,36 @@ internal class PopupShadowLayer : AbstractPopup, IShadowDecorator
if (popupHost is WindowBase window) {
window.Background = new SolidColorBrush(Colors.Transparent);
window.SetTransparentForMouseEvents(true);
if (_managedPopupPositionerPopup is null) {
if (popupHost is PopupRoot popupRoot) {
if (popupRoot.PlatformImpl?.PopupPositioner is ManagedPopupPositioner managedPopupPositioner) {
_managedPopupPositionerPopup = ManagedPopupPositionerPopupInfo.GetValue(managedPopupPositioner) as IManagedPopupPositionerPopup;
}
}
}
}
}
private Point CalculateOffset()
private Size CalculateShadowRendererSize(Control content)
{
var shadowThickness = MaskShadows.Thickness();
var targetWidth = content.DesiredSize.Width + shadowThickness.Left + shadowThickness.Right;
var targetHeight = content.DesiredSize.Height + shadowThickness.Top + shadowThickness.Bottom;
return new Size(targetWidth, targetHeight);
}
private Point CalculatePopupOffset()
{
return default;
}
protected internal override void NotifyPopupHostPositionUpdated(IPopupHost popupHost, Control placementTarget)
{
base.NotifyPopupHostPositionUpdated(popupHost, placementTarget);
popupHost.ConfigurePosition(placementTarget,
PlacementMode.Pointer,
offset: new Point(-40, 1),
anchor: PopupAnchor.Top,
gravity: PopupGravity.Top);
}
}

View File

@ -0,0 +1,62 @@
using System.Reactive.Disposables;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Controls.Primitives;
namespace AtomUI.Controls;
public class ShadowPopupHost : LiteWindow
{
public static readonly StyledProperty<bool> IsOpenProperty = AvaloniaProperty.Register<Popup, bool>(nameof(IsOpen));
public bool IsOpen
{
get => GetValue(IsOpenProperty);
set => SetValue(IsOpenProperty, value);
}
private CompositeDisposable? _compositeDisposable;
private PopupRoot _popupRoot;
public ShadowPopupHost(PopupRoot popupRoot)
: base(popupRoot, popupRoot.PlatformImpl!)
{
_popupRoot = popupRoot;
}
private void Open()
{
if (_compositeDisposable != null) {
return;
}
Topmost = true;
// SubscribeToEventHandler<PopupRoot, EventHandler<PixelPointEventArgs>>(parentPopupRoot, ParentPopupPositionChanged,
// (x, handler) => x.PositionChanged += handler,
// (x, handler) => x.PositionChanged -= handler).DisposeWith(handlerCleanup);
//
// if (parentPopupRoot.Parent is Popup popup)
// {
// SubscribeToEventHandler<Popup, EventHandler<EventArgs>>(popup, ParentClosed,
// (x, handler) => x.Closed += handler,
// (x, handler) => x.Closed -= handler).DisposeWith(handlerCleanup);
// }
_compositeDisposable = new CompositeDisposable();
_compositeDisposable.Add(Disposable.Create(this, state =>
{
state.SetChild(null);
Hide();
((ISetLogicalParent)state).SetParent(null);
Dispose();
}));
Show();
}
private static IDisposable SubscribeToEventHandler<T, TEventHandler>(T target, TEventHandler handler,
Action<T, TEventHandler> subscribe,
Action<T, TEventHandler> unsubscribe)
{
subscribe(target, handler);
return Disposable.Create((unsubscribe, target, handler), state => state.unsubscribe(state.target, state.handler));
}
}

View File

@ -1,4 +1,5 @@
using AtomUI.Utils;
using AtomUI.Media;
using AtomUI.Utils;
using Avalonia;
using Avalonia.Controls;
using Avalonia.Layout;
@ -9,77 +10,39 @@ namespace AtomUI.Controls;
internal class ShadowRenderer : Control
{
public static readonly DirectProperty<ShadowRenderer, BoxShadows> ShadowsProperty =
AvaloniaProperty.RegisterDirect<ShadowRenderer, BoxShadows>(
nameof(Shadows),
o => o.Shadows,
(o, v) => o.Shadows = v);
public static readonly StyledProperty<BoxShadows> ShadowsProperty =
Border.BoxShadowProperty.AddOwner<ShadowRenderer>();
public static readonly DirectProperty<ShadowRenderer, Point> MaskOffsetProperty =
AvaloniaProperty.RegisterDirect<ShadowRenderer, Point>(
nameof(MaskOffset),
o => o.MaskOffset,
(o, v) => o.MaskOffset = v);
public static readonly DirectProperty<ShadowRenderer, Size> MaskSizeProperty =
AvaloniaProperty.RegisterDirect<ShadowRenderer, Size>(
nameof(MaskSize),
o => o.MaskSize,
(o, v) => o.MaskSize = v);
public static readonly DirectProperty<ShadowRenderer, CornerRadius> MaskCornerRadiusProperty =
AvaloniaProperty.RegisterDirect<ShadowRenderer, CornerRadius>(
nameof(MaskCornerRadius),
o => o.MaskCornerRadius,
(o, v) => o.MaskCornerRadius = v);
private BoxShadows _shadows;
public static readonly StyledProperty<CornerRadius> MaskCornerRadiusProperty =
Border.CornerRadiusProperty.AddOwner<ShadowRenderer>();
/// <summary>
/// 渲染的阴影值
/// </summary>
public BoxShadows Shadows
{
get => _shadows;
set => SetAndRaise(ShadowsProperty, ref _shadows, value);
get => GetValue(ShadowsProperty);
set => SetValue(ShadowsProperty, value);
}
private Point _maskOffset;
/// <summary>
/// mask 渲染的位移
/// </summary>
public Point MaskOffset
{
get => _maskOffset;
set => SetAndRaise(MaskOffsetProperty, ref _maskOffset, value);
}
private Size _maskSize;
/// <summary>
/// mask 渲染大小
/// </summary>
public Size MaskSize
{
get => _maskSize;
set => SetAndRaise(MaskSizeProperty, ref _maskSize, value);
}
private CornerRadius _maskCornerRadius;
/// <summary>
/// mask 的圆角大小
/// </summary>
public CornerRadius MaskCornerRadius
{
get => _maskCornerRadius;
set => SetAndRaise(MaskCornerRadiusProperty, ref _maskCornerRadius, value);
get => GetValue(MaskCornerRadiusProperty);
set => SetValue(MaskCornerRadiusProperty, value);
}
private Border? _maskContent;
private bool _initialized = false;
private Canvas? _layout;
static ShadowRenderer()
{
AffectsMeasure<ShadowRenderer>(ShadowsProperty);
}
protected override void OnAttachedToLogicalTree(LogicalTreeAttachmentEventArgs e)
{
base.OnAttachedToLogicalTree(e);
@ -92,6 +55,7 @@ internal class ShadowRenderer : Control
VisualChildren.Add(_layout);
((ISetLogicalParent)_layout).SetParent(this);
_maskContent = CreateMaskContent();
SetupContentSizeAndPos();
_layout.Children.Add(_maskContent);
_initialized = true;
}
@ -99,21 +63,44 @@ internal class ShadowRenderer : Control
private Border CreateMaskContent()
{
var maskContent = new Border()
var maskContent = new Border
{
Width = 100,
Height = 40,
BorderThickness = new Thickness(0),
Background = new SolidColorBrush(Colors.White)
Background = new SolidColorBrush(Colors.Red),
HorizontalAlignment = HorizontalAlignment.Stretch,
VerticalAlignment = VerticalAlignment.Stretch
};
Canvas.SetLeft(maskContent,30);
Canvas.SetTop(maskContent, 20);
// TODO 需要考虑释放
BindUtils.RelayBind(this, ShadowsProperty, maskContent, Border.BoxShadowProperty);
// TODO 这个是否需要资源管理起来
BindUtils.RelayBind(this, ShadowsProperty, maskContent, ShadowsProperty);
BindUtils.RelayBind(this, MaskCornerRadiusProperty, maskContent, Border.CornerRadiusProperty);
return maskContent;
}
protected override void OnPropertyChanged(AvaloniaPropertyChangedEventArgs e)
{
base.OnPropertyChanged(e);
if (e.Property == ShadowsProperty ||
e.Property == WidthProperty ||
e.Property == HeightProperty) {
SetupContentSizeAndPos();
}
}
private void SetupContentSizeAndPos()
{
if (_maskContent is not null) {
var shadowThickness = Shadows.Thickness();
var targetWidth = Width - shadowThickness.Left - shadowThickness.Right;
var targetHeight = Height - shadowThickness.Top - shadowThickness.Bottom;
_maskContent.Width = targetWidth;
_maskContent.Height = targetHeight;
Canvas.SetLeft(_maskContent, shadowThickness.Left);
Canvas.SetTop(_maskContent, shadowThickness.Top);
}
}
// public sealed override void Render(DrawingContext context)
// {
// context.FillRectangle(new SolidColorBrush(Colors.Bisque), new Rect(new Point(0, 0), DesiredSize));
// }
}

View File

@ -0,0 +1,40 @@
using Avalonia;
using Avalonia.Media;
using Math = System.Math;
namespace AtomUI.Media;
public static class BoxShadowExtensions
{
public static Thickness Thickness(this BoxShadow boxShadow)
{
var offsetX = boxShadow.OffsetX;
var offsetY = boxShadow.OffsetY;
var blurRadius = boxShadow.Blur;
var spreadRadius = boxShadow.Spread;
var value = Math.Max(blurRadius + spreadRadius, 0.0); // 可以正负抵消
var left = Math.Max(value - offsetX, 0.0);
var right = Math.Max(value + offsetX, 0.0);
var top = Math.Max(value - offsetY, 0.0);
var bottom = Math.Max(value + offsetY, 0.0);
return new Thickness(left, top, right, bottom);
}
public static Thickness Thickness(this BoxShadows boxShadows)
{
double leftThickness = 0;
double topThickness = 0;
double rightThickness = 0;
double bottomThickness = 0;
foreach (var shadow in boxShadows) {
var thickness = shadow.Thickness();
leftThickness = Math.Max(leftThickness, thickness.Left);
topThickness = Math.Max(topThickness, thickness.Top);
rightThickness = Math.Max(rightThickness, thickness.Right);
bottomThickness = Math.Max(bottomThickness, thickness.Bottom);
}
return new Thickness(leftThickness, topThickness, rightThickness, bottomThickness);
}
}

View File

@ -1,4 +1,5 @@
using System.Diagnostics;
using AtomUI.Media;
using Avalonia;
using Avalonia.Animation;
using Avalonia.Animation.Easings;
@ -6,8 +7,9 @@ using Avalonia.Controls;
using Avalonia.Layout;
using Avalonia.Media;
using Microsoft.CSharp.RuntimeBinder;
using ColorTransition = Avalonia.Animation.ColorTransition;
namespace AtomUI.Media;
namespace AtomUI.MotionScene;
public enum TransitionKind
{

View File

@ -2,7 +2,7 @@
using Avalonia.Animation.Easings;
using Avalonia.Controls;
namespace AtomUI.Media;
namespace AtomUI.MotionScene;
public class CollapseMotion : AbstractMotion
{

View File

@ -2,7 +2,7 @@
using Avalonia.Animation.Easings;
using Avalonia.Controls;
namespace AtomUI.Media;
namespace AtomUI.MotionScene;
public class FadeInMotion : AbstractMotion
{

View File

@ -1,7 +1,7 @@
using Avalonia.Animation;
using Avalonia.Controls;
namespace AtomUI.Media;
namespace AtomUI.MotionScene;
public interface IMotion
{

View File

@ -1,7 +1,7 @@
using Avalonia;
using Avalonia.Controls;
namespace AtomUI.Media;
namespace AtomUI.MotionScene;
public interface IMotionAbilityTarget
{

View File

@ -1,6 +1,6 @@
using Avalonia.Animation;
namespace AtomUI.Media;
namespace AtomUI.MotionScene;
public interface IMotionBuilder
{

View File

@ -3,7 +3,7 @@ using Avalonia;
using Avalonia.Animation.Easings;
using Avalonia.Controls;
namespace AtomUI.Media;
namespace AtomUI.MotionScene;
public class MoveDownInMotion : AbstractMotion
{

View File

@ -4,7 +4,7 @@ using Avalonia.Animation.Easings;
using Avalonia.Controls;
using Avalonia.Media;
namespace AtomUI.Media;
namespace AtomUI.MotionScene;
public class SlideUpInMotion : AbstractMotion
{

View File

@ -3,7 +3,7 @@ using Avalonia.Animation.Easings;
using Avalonia.Controls;
using Avalonia.Media;
namespace AtomUI.Media;
namespace AtomUI.MotionScene;
public class ZoomInMotion : AbstractMotion
{