feat: OverlayDialogHost remake: support host with same id in different toplevel. support modal status propagation.

This commit is contained in:
rabbitism
2024-07-08 15:39:48 +08:00
parent 678bcdef9c
commit 9cbefe6307
11 changed files with 165 additions and 133 deletions

View File

@@ -129,7 +129,7 @@ public partial class OverlayDialogHost
if (layer.Modal)
{
_modalCount--;
HasModal = _modalCount > 0;
IsInModalStatus = _modalCount > 0;
if (!IsAnimationDisabled)
{
await _maskDisappearAnimation.RunAsync(layer.Mask);
@@ -170,7 +170,7 @@ public partial class OverlayDialogHost
_maskAppearAnimation.RunAsync(mask);
}
_modalCount++;
HasModal = _modalCount > 0;
IsInModalStatus = _modalCount > 0;
control.IsClosed = false;
}

View File

@@ -56,7 +56,7 @@ public partial class OverlayDialogHost
control.Arrange(new Rect(control.DesiredSize));
SetDrawerPosition(control);
_modalCount++;
HasModal = _modalCount > 0;
IsInModalStatus = _modalCount > 0;
control.AddHandler(OverlayFeedbackElement.ClosedEvent, OnDrawerControlClosing);
var animation = CreateAnimation(control.Bounds.Size, control.Position);
if (IsAnimationDisabled)
@@ -162,7 +162,7 @@ public partial class OverlayDialogHost
if (layer.Mask is not null)
{
_modalCount--;
HasModal = _modalCount > 0;
IsInModalStatus = _modalCount > 0;
layer.Mask.RemoveHandler(PointerPressedEvent, ClickMaskToCloseDialog);
if (!IsAnimationDisabled)
{

View File

@@ -6,6 +6,8 @@ using Avalonia.Input;
using Avalonia.Media;
using Ursa.Controls.OverlayShared;
using Avalonia.Styling;
using Avalonia.VisualTree;
using Irihi.Avalonia.Shared.Helpers;
using Irihi.Avalonia.Shared.Shapes;
namespace Ursa.Controls;
@@ -32,18 +34,35 @@ public partial class OverlayDialogHost: Canvas
}
private int _modalCount = 0;
public static readonly DirectProperty<OverlayDialogHost, bool> HasModalProperty = AvaloniaProperty.RegisterDirect<OverlayDialogHost, bool>(
nameof(HasModal), o => o.HasModal);
private bool _hasModal;
[Obsolete("Use IsInModalStatus")]
public bool HasModal
{
get => _hasModal;
private set => SetAndRaise(HasModalProperty, ref _hasModal, value);
}
public static readonly AttachedProperty<bool> IsModalStatusScopeProperty =
AvaloniaProperty.RegisterAttached<OverlayDialogHost, Control, bool>("IsModalStatusScope");
public static void SetIsModalStatusScope(Control obj, bool value) => obj.SetValue(IsModalStatusScopeProperty, value);
internal static bool GetIsModalStatusScope(Control obj) => obj.GetValue(IsModalStatusScopeProperty);
public static readonly AttachedProperty<bool> IsInModalStatusProperty =
AvaloniaProperty.RegisterAttached<OverlayDialogHost, Control, bool>(nameof(IsInModalStatus));
internal static void SetIsInModalStatus(Control obj, bool value) => obj.SetValue(IsInModalStatusProperty, value);
public static bool GetIsInModalStatus(Control obj) => obj.GetValue(IsInModalStatusProperty);
public bool IsInModalStatus
{
get => GetValue(IsInModalStatusProperty);
set => SetValue(IsInModalStatusProperty, value);
}
public bool IsAnimationDisabled { get; set; }
static OverlayDialogHost()
@@ -51,6 +70,11 @@ public partial class OverlayDialogHost: Canvas
ClipToBoundsProperty.OverrideDefaultValue<OverlayDialogHost>(true);
_maskAppearAnimation = CreateOpacityAnimation(true);
_maskDisappearAnimation = CreateOpacityAnimation(false);
// This is only a temporary solution, will be removed in release candidate mode.
IsInModalStatusProperty.Changed.AddClassHandler<OverlayDialogHost, bool>((host, args) =>
{
host.HasModal = args.NewValue.Value;
});
}
private static Animation CreateOpacityAnimation(bool appear)
@@ -116,11 +140,18 @@ public partial class OverlayDialogHost: Canvas
}
}
}
private IDisposable? _modalStatusSubscription;
protected sealed override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
OverlayDialogManager.RegisterHost(this, HostId);
var hash = TopLevel.GetTopLevel(this)?.GetHashCode();
var modalHost = this.GetVisualAncestors().OfType<Control>().FirstOrDefault(GetIsModalStatusScope);
if (modalHost is not null)
{
_modalStatusSubscription = this.GetObservable(IsInModalStatusProperty)
.Subscribe(a => OverlayDialogHost.SetIsInModalStatus(modalHost, a));
}
OverlayDialogManager.RegisterHost(this, HostId, hash);
}
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e)
@@ -129,7 +160,9 @@ public partial class OverlayDialogHost: Canvas
{
_layers[0].Element.Close();
}
OverlayDialogManager.UnregisterHost(HostId);
_modalStatusSubscription?.Dispose();
var hash = TopLevel.GetTopLevel(this)?.GetHashCode();
OverlayDialogManager.UnregisterHost(HostId, hash);
base.OnDetachedFromVisualTree(e);
}

View File

@@ -2,41 +2,27 @@ using System.Collections.Concurrent;
namespace Ursa.Controls;
internal record struct HostKey(string? Id, int? Hash);
internal static class OverlayDialogManager
{
private static OverlayDialogHost? _defaultHost;
private static readonly ConcurrentDictionary<string, OverlayDialogHost> Hosts = new();
private static readonly ConcurrentDictionary<HostKey, OverlayDialogHost> Hosts = new();
public static void RegisterHost(OverlayDialogHost host, string? id)
public static void RegisterHost(OverlayDialogHost host, string? id, int? hash)
{
if (id == null)
{
if (_defaultHost != null)
{
throw new InvalidOperationException("Cannot register multiple OverlayDialogHost with empty HostId");
}
_defaultHost = host;
return;
}
Hosts.TryAdd(id, host);
Hosts.TryAdd(new HostKey(id, hash), host);
}
public static void UnregisterHost(string? id)
public static void UnregisterHost(string? id, int? hash)
{
if (id is null)
{
_defaultHost = null;
return;
}
Hosts.TryRemove(id, out _);
Hosts.TryRemove(new HostKey(id, hash), out _);
}
public static OverlayDialogHost? GetHost(string? id)
public static OverlayDialogHost? GetHost(string? id, int? hash)
{
if (id is null)
{
return _defaultHost;
}
return Hosts.TryGetValue(id, out var host) ? host : null;
HostKey? key = hash is null ? Hosts.Keys.Where(k => k.Id == id).ToArray().FirstOrDefault() : Hosts.Keys.FirstOrDefault(k => k.Id == id && k.Hash == hash);
if (key is null) return null;
return Hosts.TryGetValue(key.Value, out var host) ? host : null;
}
}
}