Merge pull request #647 from irihitech/ursa_host

Make UrsaWindow/UrsaView's built in OverlayDialogHost part of logical tree.
This commit is contained in:
Zhang Dian
2025-04-11 14:10:15 +08:00
committed by GitHub
4 changed files with 201 additions and 57 deletions

View File

@@ -38,7 +38,7 @@
IsVisible="{TemplateBinding IsTitleBarVisible}" IsVisible="{TemplateBinding IsTitleBarVisible}"
Content="{TemplateBinding RightContent}" /> Content="{TemplateBinding RightContent}" />
</Grid> </Grid>
<u:OverlayDialogHost IsModalStatusReporter="True" /> <u:OverlayDialogHost Name="{x:Static u:UrsaView.PART_DialogHost}" IsModalStatusReporter="True" />
</Panel> </Panel>
</Panel> </Panel>
</ControlTemplate> </ControlTemplate>

View File

@@ -74,7 +74,10 @@
RightContent="{Binding $parent[u:UrsaWindow].RightContent}" /> RightContent="{Binding $parent[u:UrsaWindow].RightContent}" />
<VisualLayerManager> <VisualLayerManager>
<Panel> <Panel>
<u:OverlayDialogHost IsModalStatusReporter="True" IsTopLevel="True" /> <u:OverlayDialogHost
Name="{x:Static u:UrsaWindow.PART_DialogHost}"
IsModalStatusReporter="True"
IsTopLevel="True" />
<u:WindowResizer <u:WindowResizer
HorizontalAlignment="Stretch" HorizontalAlignment="Stretch"
VerticalAlignment="Stretch" VerticalAlignment="Stretch"

View File

@@ -1,55 +1,103 @@
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives;
namespace Ursa.Controls; namespace Ursa.Controls;
/// <summary> /// <summary>
/// Ursa window is designed to /// Represents a custom content control with additional properties for managing a title bar and related content.
/// This control can be used as the top level container for platforms without windowing toplevel support.
/// </summary> /// </summary>
public class UrsaView: ContentControl public class UrsaView : ContentControl
{ {
/// <summary>
/// The name of the dialog host part in the control template.
/// </summary>
public const string PART_DialogHost = "PART_DialogHost";
/// <summary>
/// Defines the visibility of the title bar.
/// </summary>
public static readonly StyledProperty<bool> IsTitleBarVisibleProperty = public static readonly StyledProperty<bool> IsTitleBarVisibleProperty =
UrsaWindow.IsTitleBarVisibleProperty.AddOwner<UrsaView>(); UrsaWindow.IsTitleBarVisibleProperty.AddOwner<UrsaView>();
/// <summary>
/// Defines the content on the left side of the control.
/// </summary>
public static readonly StyledProperty<object?> LeftContentProperty =
UrsaWindow.LeftContentProperty.AddOwner<UrsaView>();
/// <summary>
/// Defines the content on the right side of the control.
/// </summary>
public static readonly StyledProperty<object?> RightContentProperty =
UrsaWindow.RightContentProperty.AddOwner<UrsaView>();
/// <summary>
/// Defines the content of the title bar.
/// </summary>
public static readonly StyledProperty<object?> TitleBarContentProperty =
UrsaWindow.TitleBarContentProperty.AddOwner<UrsaView>();
/// <summary>
/// Defines the margin of the title bar.
/// </summary>
public static readonly StyledProperty<Thickness> TitleBarMarginProperty =
UrsaWindow.TitleBarMarginProperty.AddOwner<UrsaView>();
/// <summary>
/// Gets or sets a value indicating whether the title bar is visible.
/// </summary>
public bool IsTitleBarVisible public bool IsTitleBarVisible
{ {
get => GetValue(IsTitleBarVisibleProperty); get => GetValue(IsTitleBarVisibleProperty);
set => SetValue(IsTitleBarVisibleProperty, value); set => SetValue(IsTitleBarVisibleProperty, value);
} }
public static readonly StyledProperty<object?> LeftContentProperty = /// <summary>
UrsaWindow.LeftContentProperty.AddOwner<UrsaView>(); /// Gets or sets the content on the left side of the control.
/// </summary>
public object? LeftContent public object? LeftContent
{ {
get => GetValue(LeftContentProperty); get => GetValue(LeftContentProperty);
set => SetValue(LeftContentProperty, value); set => SetValue(LeftContentProperty, value);
} }
public static readonly StyledProperty<object?> RightContentProperty = /// <summary>
UrsaWindow.RightContentProperty.AddOwner<UrsaView>(); /// Gets or sets the content on the right side of the control.
/// </summary>
public object? RightContent public object? RightContent
{ {
get => GetValue(RightContentProperty); get => GetValue(RightContentProperty);
set => SetValue(RightContentProperty, value); set => SetValue(RightContentProperty, value);
} }
public static readonly StyledProperty<object?> TitleBarContentProperty = /// <summary>
UrsaWindow.TitleBarContentProperty.AddOwner<UrsaView>(); /// Gets or sets the content of the title bar.
/// </summary>
public object? TitleBarContent public object? TitleBarContent
{ {
get => GetValue(TitleBarContentProperty); get => GetValue(TitleBarContentProperty);
set => SetValue(TitleBarContentProperty, value); set => SetValue(TitleBarContentProperty, value);
} }
public static readonly StyledProperty<Thickness> TitleBarMarginProperty = /// <summary>
UrsaWindow.TitleBarMarginProperty.AddOwner<UrsaView>(); /// Gets or sets the margin of the title bar.
/// </summary>
public Thickness TitleBarMargin public Thickness TitleBarMargin
{ {
get => GetValue(TitleBarMarginProperty); get => GetValue(TitleBarMarginProperty);
set => SetValue(TitleBarMarginProperty, value); set => SetValue(TitleBarMarginProperty, value);
} }
/// <summary>
/// Applies the control template and initializes the dialog host if present.
/// </summary>
/// <param name="e">The event arguments containing the template information.</param>
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
var host = e.NameScope.Find<OverlayDialogHost>(PART_DialogHost);
if (host is not null) LogicalChildren.Add(host);
}
} }

View File

@@ -1,112 +1,205 @@
using System.ComponentModel;
using Avalonia; using Avalonia;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Controls.Primitives;
namespace Ursa.Controls; namespace Ursa.Controls;
/// <summary> /// <summary>
/// Ursa Window is an advanced Window control that provides a lot of features and customization options. /// Ursa Window is an advanced Window control that provides a lot of features and customization options.
/// </summary> /// </summary>
public class UrsaWindow: Window public class UrsaWindow : Window
{ {
/// <summary>
/// The name of the dialog host part in the control template.
/// </summary>
public const string PART_DialogHost = "PART_DialogHost";
/// <summary>
/// Defines the visibility of the full-screen button.
/// </summary>
public static readonly StyledProperty<bool> IsFullScreenButtonVisibleProperty =
AvaloniaProperty.Register<UrsaWindow, bool>(
nameof(IsFullScreenButtonVisible));
/// <summary>
/// Defines the visibility of the minimize button.
/// </summary>
public static readonly StyledProperty<bool> IsMinimizeButtonVisibleProperty =
AvaloniaProperty.Register<UrsaWindow, bool>(
nameof(IsMinimizeButtonVisible), true);
/// <summary>
/// Defines the visibility of the restore button.
/// </summary>
public static readonly StyledProperty<bool> IsRestoreButtonVisibleProperty =
AvaloniaProperty.Register<UrsaWindow, bool>(
nameof(IsRestoreButtonVisible), true);
/// <summary>
/// Defines the visibility of the close button.
/// </summary>
public static readonly StyledProperty<bool> IsCloseButtonVisibleProperty =
AvaloniaProperty.Register<UrsaWindow, bool>(
nameof(IsCloseButtonVisible), true);
/// <summary>
/// Defines the visibility of the title bar.
/// </summary>
public static readonly StyledProperty<bool> IsTitleBarVisibleProperty = AvaloniaProperty.Register<UrsaWindow, bool>(
nameof(IsTitleBarVisible), true);
/// <summary>
/// Defines the visibility of the managed resizer.
/// </summary>
public static readonly StyledProperty<bool> IsManagedResizerVisibleProperty =
AvaloniaProperty.Register<UrsaWindow, bool>(
nameof(IsManagedResizerVisible));
/// <summary>
/// Defines the content of the title bar.
/// </summary>
public static readonly StyledProperty<object?> TitleBarContentProperty =
AvaloniaProperty.Register<UrsaWindow, object?>(
nameof(TitleBarContent));
/// <summary>
/// Defines the content on the left side of the window.
/// </summary>
public static readonly StyledProperty<object?> LeftContentProperty = AvaloniaProperty.Register<UrsaWindow, object?>(
nameof(LeftContent));
/// <summary>
/// Defines the content on the right side of the window.
/// </summary>
public static readonly StyledProperty<object?> RightContentProperty =
AvaloniaProperty.Register<UrsaWindow, object?>(
nameof(RightContent));
/// <summary>
/// Defines the margin of the title bar.
/// </summary>
public static readonly StyledProperty<Thickness> TitleBarMarginProperty =
AvaloniaProperty.Register<UrsaWindow, Thickness>(
nameof(TitleBarMargin));
private bool _canClose;
/// <summary>
/// Gets the style key override for the control.
/// </summary>
protected override Type StyleKeyOverride => typeof(UrsaWindow); protected override Type StyleKeyOverride => typeof(UrsaWindow);
public static readonly StyledProperty<bool> IsFullScreenButtonVisibleProperty = AvaloniaProperty.Register<UrsaWindow, bool>( /// <summary>
nameof(IsFullScreenButtonVisible)); /// Gets or sets a value indicating whether the full-screen button is visible.
/// </summary>
public bool IsFullScreenButtonVisible public bool IsFullScreenButtonVisible
{ {
get => GetValue(IsFullScreenButtonVisibleProperty); get => GetValue(IsFullScreenButtonVisibleProperty);
set => SetValue(IsFullScreenButtonVisibleProperty, value); set => SetValue(IsFullScreenButtonVisibleProperty, value);
} }
public static readonly StyledProperty<bool> IsMinimizeButtonVisibleProperty = AvaloniaProperty.Register<UrsaWindow, bool>( /// <summary>
nameof(IsMinimizeButtonVisible), true); /// Gets or sets a value indicating whether the minimize button is visible.
/// </summary>
public bool IsMinimizeButtonVisible public bool IsMinimizeButtonVisible
{ {
get => GetValue(IsMinimizeButtonVisibleProperty); get => GetValue(IsMinimizeButtonVisibleProperty);
set => SetValue(IsMinimizeButtonVisibleProperty, value); set => SetValue(IsMinimizeButtonVisibleProperty, value);
} }
public static readonly StyledProperty<bool> IsRestoreButtonVisibleProperty = AvaloniaProperty.Register<UrsaWindow, bool>( /// <summary>
nameof(IsRestoreButtonVisible), true); /// Gets or sets a value indicating whether the restore button is visible.
/// </summary>
public bool IsRestoreButtonVisible public bool IsRestoreButtonVisible
{ {
get => GetValue(IsRestoreButtonVisibleProperty); get => GetValue(IsRestoreButtonVisibleProperty);
set => SetValue(IsRestoreButtonVisibleProperty, value); set => SetValue(IsRestoreButtonVisibleProperty, value);
} }
public static readonly StyledProperty<bool> IsCloseButtonVisibleProperty = AvaloniaProperty.Register<UrsaWindow, bool>( /// <summary>
nameof(IsCloseButtonVisible), true); /// Gets or sets a value indicating whether the close button is visible.
/// </summary>
public bool IsCloseButtonVisible public bool IsCloseButtonVisible
{ {
get => GetValue(IsCloseButtonVisibleProperty); get => GetValue(IsCloseButtonVisibleProperty);
set => SetValue(IsCloseButtonVisibleProperty, value); set => SetValue(IsCloseButtonVisibleProperty, value);
} }
public static readonly StyledProperty<bool> IsTitleBarVisibleProperty = AvaloniaProperty.Register<UrsaWindow, bool>( /// <summary>
nameof(IsTitleBarVisible), true); /// Gets or sets a value indicating whether the title bar is visible.
/// </summary>
public bool IsTitleBarVisible public bool IsTitleBarVisible
{ {
get => GetValue(IsTitleBarVisibleProperty); get => GetValue(IsTitleBarVisibleProperty);
set => SetValue(IsTitleBarVisibleProperty, value); set => SetValue(IsTitleBarVisibleProperty, value);
} }
public static readonly StyledProperty<bool> IsManagedResizerVisibleProperty = AvaloniaProperty.Register<UrsaWindow, bool>( /// <summary>
nameof(IsManagedResizerVisible)); /// Gets or sets a value indicating whether the managed resizer is visible.
/// </summary>
public bool IsManagedResizerVisible public bool IsManagedResizerVisible
{ {
get => GetValue(IsManagedResizerVisibleProperty); get => GetValue(IsManagedResizerVisibleProperty);
set => SetValue(IsManagedResizerVisibleProperty, value); set => SetValue(IsManagedResizerVisibleProperty, value);
} }
public static readonly StyledProperty<object?> TitleBarContentProperty = AvaloniaProperty.Register<UrsaWindow, object?>( /// <summary>
nameof(TitleBarContent)); /// Gets or sets the content of the title bar.
/// </summary>
public object? TitleBarContent public object? TitleBarContent
{ {
get => GetValue(TitleBarContentProperty); get => GetValue(TitleBarContentProperty);
set => SetValue(TitleBarContentProperty, value); set => SetValue(TitleBarContentProperty, value);
} }
public static readonly StyledProperty<object?> LeftContentProperty = AvaloniaProperty.Register<UrsaWindow, object?>( /// <summary>
nameof(LeftContent)); /// Gets or sets the content on the left side of the window.
/// </summary>
public object? LeftContent public object? LeftContent
{ {
get => GetValue(LeftContentProperty); get => GetValue(LeftContentProperty);
set => SetValue(LeftContentProperty, value); set => SetValue(LeftContentProperty, value);
} }
public static readonly StyledProperty<object?> RightContentProperty = AvaloniaProperty.Register<UrsaWindow, object?>( /// <summary>
nameof(RightContent)); /// Gets or sets the content on the right side of the window.
/// </summary>
public object? RightContent public object? RightContent
{ {
get => GetValue(RightContentProperty); get => GetValue(RightContentProperty);
set => SetValue(RightContentProperty, value); set => SetValue(RightContentProperty, value);
} }
public static readonly StyledProperty<Thickness> TitleBarMarginProperty = AvaloniaProperty.Register<UrsaWindow, Thickness>( /// <summary>
nameof(TitleBarMargin)); /// Gets or sets the margin of the title bar.
/// </summary>
public Thickness TitleBarMargin public Thickness TitleBarMargin
{ {
get => GetValue(TitleBarMarginProperty); get => GetValue(TitleBarMarginProperty);
set => SetValue(TitleBarMarginProperty, value); set => SetValue(TitleBarMarginProperty, value);
} }
/// <inheritdoc/>
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
base.OnApplyTemplate(e);
var host = e.NameScope.Find<OverlayDialogHost>(PART_DialogHost);
if (host is not null) LogicalChildren.Add(host);
}
/// <summary>
/// Determines whether the window can close.
/// </summary>
/// <returns>A task that resolves to true if the window can close; otherwise, false.</returns>
protected virtual async Task<bool> CanClose() protected virtual async Task<bool> CanClose()
{ {
return await Task.FromResult(true); return await Task.FromResult(true);
} }
private bool _canClose = false; /// <summary>
/// Handles the window closing event and determines whether the window should close.
/// </summary>
/// <param name="e">The event arguments for the closing event.</param>
protected override async void OnClosing(WindowClosingEventArgs e) protected override async void OnClosing(WindowClosingEventArgs e)
{ {
VerifyAccess(); VerifyAccess();