mirror of
https://gitee.com/chinware/atomui.git
synced 2024-12-02 03:47:52 +08:00
添加 LiteWindow
This commit is contained in:
parent
995f4d0680
commit
900a57f414
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
@ -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));
|
||||
}
|
||||
}
|
@ -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);
|
||||
|
108
src/AtomUI.Controls/Popup/LiteWindow.cs
Normal file
108
src/AtomUI.Controls/Popup/LiteWindow.cs
Normal 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;
|
||||
}
|
||||
}
|
@ -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!;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
62
src/AtomUI.Controls/Popup/ShadowPopupHost.cs
Normal file
62
src/AtomUI.Controls/Popup/ShadowPopupHost.cs
Normal 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));
|
||||
}
|
||||
}
|
@ -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));
|
||||
// }
|
||||
}
|
40
src/AtomUI/Media/BoxShadowExtensions.cs
Normal file
40
src/AtomUI/Media/BoxShadowExtensions.cs
Normal 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);
|
||||
}
|
||||
}
|
@ -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
|
||||
{
|
||||
|
@ -2,7 +2,7 @@
|
||||
using Avalonia.Animation.Easings;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace AtomUI.Media;
|
||||
namespace AtomUI.MotionScene;
|
||||
|
||||
public class CollapseMotion : AbstractMotion
|
||||
{
|
||||
|
@ -2,7 +2,7 @@
|
||||
using Avalonia.Animation.Easings;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace AtomUI.Media;
|
||||
namespace AtomUI.MotionScene;
|
||||
|
||||
public class FadeInMotion : AbstractMotion
|
||||
{
|
||||
|
@ -1,7 +1,7 @@
|
||||
using Avalonia.Animation;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace AtomUI.Media;
|
||||
namespace AtomUI.MotionScene;
|
||||
|
||||
public interface IMotion
|
||||
{
|
||||
|
@ -1,7 +1,7 @@
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace AtomUI.Media;
|
||||
namespace AtomUI.MotionScene;
|
||||
|
||||
public interface IMotionAbilityTarget
|
||||
{
|
||||
|
@ -1,6 +1,6 @@
|
||||
using Avalonia.Animation;
|
||||
|
||||
namespace AtomUI.Media;
|
||||
namespace AtomUI.MotionScene;
|
||||
|
||||
public interface IMotionBuilder
|
||||
{
|
||||
|
@ -3,7 +3,7 @@ using Avalonia;
|
||||
using Avalonia.Animation.Easings;
|
||||
using Avalonia.Controls;
|
||||
|
||||
namespace AtomUI.Media;
|
||||
namespace AtomUI.MotionScene;
|
||||
|
||||
public class MoveDownInMotion : AbstractMotion
|
||||
{
|
||||
|
@ -4,7 +4,7 @@ using Avalonia.Animation.Easings;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace AtomUI.Media;
|
||||
namespace AtomUI.MotionScene;
|
||||
|
||||
public class SlideUpInMotion : AbstractMotion
|
||||
{
|
||||
|
@ -3,7 +3,7 @@ using Avalonia.Animation.Easings;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Media;
|
||||
|
||||
namespace AtomUI.Media;
|
||||
namespace AtomUI.MotionScene;
|
||||
|
||||
public class ZoomInMotion : AbstractMotion
|
||||
{
|
Loading…
Reference in New Issue
Block a user