Browse Source
* Implemented new drawn window decorations API TODO: check if it works on Win32, bring back titlebar automation peer * Adjusting naming a bit * Naming / configuration changes * Various fixes * popover fix? * wip * Address review * Extra window roles * WIP * Fixed drawn titlebar automation * Purge ExtendClientAreaChromeHints. * Fixed dynamically enabling drawn decorations * api diff * Add automation IDs for drawn decorations buttons * Resolved the issues * build * Retry a few times when Pager isn't available after test is finished * Only do faulty test detection if asked * duplicate package reference * Try disabling faulty tests on appium1 * Fix ExtendClientAreaWindowTests * Apply initial button states * Enable CSD shadow for X11 * net8? * Address review * more review comments * Moar review comments * Extra hit-test checks * Moar review * Prefix integration test app exitfullscreen to avoid clashes * Disable drawn decorations if parts = None * Respect SystemDecorations value on mac in extend-client-area mode * Tidy up logic a bit * Adjust win32 tests to titlebar not being in the tree when CSD are not enabled --------- Co-authored-by: Julien Lebosquain <julien@lebosquain.net>pull/20797/head
committed by
GitHub
66 changed files with 2417 additions and 1146 deletions
@ -0,0 +1,100 @@ |
|||||
|
namespace Avalonia.Input; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the cross-platform role of a visual element for non-client hit-testing.
|
||||
|
/// Used to mark elements as titlebar drag areas, resize grips, etc.
|
||||
|
/// </summary>
|
||||
|
public enum WindowDecorationsElementRole |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// No special role. The element is invisible to chrome hit-testing.
|
||||
|
/// </summary>
|
||||
|
None, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// An interactive element that is part of the decorations chrome (e.g., a caption button).
|
||||
|
/// Set by themes on decoration template elements. Input is passed through to the element
|
||||
|
/// rather than being intercepted for non-client actions.
|
||||
|
/// </summary>
|
||||
|
DecorationsElement, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// An interactive element set by user code that should receive input even when
|
||||
|
/// overlapping chrome areas. Has the same effect as <see cref="DecorationsElement"/>
|
||||
|
/// but is intended for use by application developers.
|
||||
|
/// </summary>
|
||||
|
User, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The element acts as a titlebar drag area.
|
||||
|
/// Clicking and dragging on this element initiates a platform window move.
|
||||
|
/// </summary>
|
||||
|
TitleBar, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Resize grip for the north (top) edge.
|
||||
|
/// </summary>
|
||||
|
ResizeN, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Resize grip for the south (bottom) edge.
|
||||
|
/// </summary>
|
||||
|
ResizeS, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Resize grip for the east (right) edge.
|
||||
|
/// </summary>
|
||||
|
ResizeE, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Resize grip for the west (left) edge.
|
||||
|
/// </summary>
|
||||
|
ResizeW, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Resize grip for the northeast corner.
|
||||
|
/// </summary>
|
||||
|
ResizeNE, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Resize grip for the northwest corner.
|
||||
|
/// </summary>
|
||||
|
ResizeNW, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Resize grip for the southeast corner.
|
||||
|
/// </summary>
|
||||
|
ResizeSE, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Resize grip for the southwest corner.
|
||||
|
/// </summary>
|
||||
|
ResizeSW, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The element acts as the window close button.
|
||||
|
/// On Win32, maps to HTCLOSE for system close behavior.
|
||||
|
/// On other platforms, treated as an interactive decoration element.
|
||||
|
/// </summary>
|
||||
|
CloseButton, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The element acts as the window minimize button.
|
||||
|
/// On Win32, maps to HTMINBUTTON for system minimize behavior.
|
||||
|
/// On other platforms, treated as an interactive decoration element.
|
||||
|
/// </summary>
|
||||
|
MinimizeButton, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The element acts as the window maximize/restore button.
|
||||
|
/// On Win32, maps to HTMAXBUTTON for system maximize behavior.
|
||||
|
/// On other platforms, treated as an interactive decoration element.
|
||||
|
/// </summary>
|
||||
|
MaximizeButton, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The element acts as the window fullscreen toggle button.
|
||||
|
/// Treated as an interactive decoration element on all platforms.
|
||||
|
/// </summary>
|
||||
|
FullScreenButton |
||||
|
} |
||||
@ -1,26 +0,0 @@ |
|||||
using Avalonia.Automation; |
|
||||
using Avalonia.Automation.Peers; |
|
||||
using Avalonia.Controls.Chrome; |
|
||||
|
|
||||
namespace Avalonia.Controls.Automation.Peers; |
|
||||
|
|
||||
internal class TitleBarAutomationPeer : ControlAutomationPeer |
|
||||
{ |
|
||||
public TitleBarAutomationPeer(TitleBar owner) : base(owner) |
|
||||
{ |
|
||||
} |
|
||||
|
|
||||
protected override bool IsContentElementCore() => false; |
|
||||
|
|
||||
protected override string GetClassNameCore() |
|
||||
{ |
|
||||
return "TitleBar"; |
|
||||
} |
|
||||
|
|
||||
protected override string? GetAutomationIdCore() => base.GetAutomationIdCore() ?? "AvaloniaTitleBar"; |
|
||||
|
|
||||
protected override AutomationControlType GetAutomationControlTypeCore() |
|
||||
{ |
|
||||
return AutomationControlType.TitleBar; |
|
||||
} |
|
||||
} |
|
||||
@ -1,187 +0,0 @@ |
|||||
using System; |
|
||||
using Avalonia.Reactive; |
|
||||
using Avalonia.Controls.Metadata; |
|
||||
using Avalonia.Controls.Primitives; |
|
||||
|
|
||||
namespace Avalonia.Controls.Chrome |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Draws window minimize / maximize / close buttons in a <see cref="TitleBar"/> when managed client decorations are enabled.
|
|
||||
/// </summary>
|
|
||||
[TemplatePart(PART_CloseButton, typeof(Button))] |
|
||||
[TemplatePart(PART_RestoreButton, typeof(Button))] |
|
||||
[TemplatePart(PART_MinimizeButton, typeof(Button))] |
|
||||
[TemplatePart(PART_FullScreenButton, typeof(Button))] |
|
||||
[PseudoClasses(":minimized", ":normal", ":maximized", ":fullscreen")] |
|
||||
public class CaptionButtons : TemplatedControl |
|
||||
{ |
|
||||
internal const string PART_CloseButton = "PART_CloseButton"; |
|
||||
internal const string PART_RestoreButton = "PART_RestoreButton"; |
|
||||
internal const string PART_MinimizeButton = "PART_MinimizeButton"; |
|
||||
internal const string PART_FullScreenButton = "PART_FullScreenButton"; |
|
||||
|
|
||||
private Button? _restoreButton; |
|
||||
private Button? _minimizeButton; |
|
||||
private Button? _fullScreenButton; |
|
||||
private IDisposable? _disposables; |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Currently attached window.
|
|
||||
/// </summary>
|
|
||||
protected Window? HostWindow { get; private set; } |
|
||||
|
|
||||
public virtual void Attach(Window hostWindow) |
|
||||
{ |
|
||||
if (_disposables == null) |
|
||||
{ |
|
||||
HostWindow = hostWindow; |
|
||||
|
|
||||
_disposables = new CompositeDisposable |
|
||||
{ |
|
||||
HostWindow.GetObservable(Window.CanMaximizeProperty) |
|
||||
.Subscribe(_ => |
|
||||
{ |
|
||||
UpdateRestoreButtonState(); |
|
||||
UpdateFullScreenButtonState(); |
|
||||
}), |
|
||||
HostWindow.GetObservable(Window.CanMinimizeProperty) |
|
||||
.Subscribe(_ => |
|
||||
{ |
|
||||
UpdateMinimizeButtonState(); |
|
||||
}), |
|
||||
HostWindow.GetObservable(Window.WindowStateProperty) |
|
||||
.Subscribe(x => |
|
||||
{ |
|
||||
PseudoClasses.Set(":minimized", x == WindowState.Minimized); |
|
||||
PseudoClasses.Set(":normal", x == WindowState.Normal); |
|
||||
PseudoClasses.Set(":maximized", x == WindowState.Maximized); |
|
||||
PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); |
|
||||
UpdateRestoreButtonState(); |
|
||||
UpdateMinimizeButtonState(); |
|
||||
UpdateFullScreenButtonState(); |
|
||||
}), |
|
||||
}; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
public virtual void Detach() |
|
||||
{ |
|
||||
if (_disposables != null) |
|
||||
{ |
|
||||
_disposables.Dispose(); |
|
||||
_disposables = null; |
|
||||
|
|
||||
HostWindow = null; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
protected virtual void OnClose() |
|
||||
{ |
|
||||
HostWindow?.Close(); |
|
||||
} |
|
||||
|
|
||||
protected virtual void OnRestore() |
|
||||
{ |
|
||||
if (HostWindow != null) |
|
||||
{ |
|
||||
HostWindow.WindowState = HostWindow.WindowState == WindowState.Maximized ? WindowState.Normal : WindowState.Maximized; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
protected virtual void OnMinimize() |
|
||||
{ |
|
||||
if (HostWindow != null) |
|
||||
{ |
|
||||
HostWindow.WindowState = WindowState.Minimized; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
protected virtual void OnToggleFullScreen() |
|
||||
{ |
|
||||
if (HostWindow != null) |
|
||||
{ |
|
||||
HostWindow.WindowState = HostWindow.WindowState == WindowState.FullScreen |
|
||||
? WindowState.Normal |
|
||||
: WindowState.FullScreen; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
|
||||
{ |
|
||||
base.OnApplyTemplate(e); |
|
||||
|
|
||||
if (e.NameScope.Find<Button>(PART_CloseButton) is { } closeButton) |
|
||||
{ |
|
||||
closeButton.Click += (_, args) => |
|
||||
{ |
|
||||
OnClose(); |
|
||||
args.Handled = true; |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
if (e.NameScope.Find<Button>(PART_RestoreButton) is { } restoreButton) |
|
||||
{ |
|
||||
restoreButton.Click += (_, args) => |
|
||||
{ |
|
||||
OnRestore(); |
|
||||
args.Handled = true; |
|
||||
}; |
|
||||
_restoreButton = restoreButton; |
|
||||
UpdateRestoreButtonState(); |
|
||||
} |
|
||||
|
|
||||
if (e.NameScope.Find<Button>(PART_MinimizeButton) is { } minimizeButton) |
|
||||
{ |
|
||||
minimizeButton.Click += (_, args) => |
|
||||
{ |
|
||||
OnMinimize(); |
|
||||
args.Handled = true; |
|
||||
}; |
|
||||
_minimizeButton = minimizeButton; |
|
||||
UpdateMinimizeButtonState(); |
|
||||
} |
|
||||
|
|
||||
if (e.NameScope.Find<Button>(PART_FullScreenButton) is { } fullScreenButton) |
|
||||
{ |
|
||||
fullScreenButton.Click += (_, args) => |
|
||||
{ |
|
||||
OnToggleFullScreen(); |
|
||||
args.Handled = true; |
|
||||
}; |
|
||||
_fullScreenButton = fullScreenButton; |
|
||||
UpdateFullScreenButtonState(); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
private void UpdateRestoreButtonState() |
|
||||
{ |
|
||||
if (_restoreButton is null) |
|
||||
return; |
|
||||
|
|
||||
_restoreButton.IsEnabled = HostWindow?.WindowState switch |
|
||||
{ |
|
||||
WindowState.Maximized or WindowState.FullScreen => HostWindow.CanResize, |
|
||||
WindowState.Normal => HostWindow.CanMaximize, |
|
||||
_ => true |
|
||||
}; |
|
||||
} |
|
||||
|
|
||||
private void UpdateMinimizeButtonState() |
|
||||
{ |
|
||||
if (_minimizeButton is null) |
|
||||
return; |
|
||||
|
|
||||
_minimizeButton.IsEnabled = HostWindow?.CanMinimize ?? true; |
|
||||
} |
|
||||
|
|
||||
private void UpdateFullScreenButtonState() |
|
||||
{ |
|
||||
if (_fullScreenButton is null) |
|
||||
return; |
|
||||
|
|
||||
_fullScreenButton.IsEnabled = HostWindow?.WindowState == WindowState.FullScreen ? |
|
||||
HostWindow.CanResize : |
|
||||
HostWindow?.CanMaximize ?? true; |
|
||||
} |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,41 @@ |
|||||
|
using System; |
||||
|
|
||||
|
namespace Avalonia.Controls.Chrome; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Flags controlling which parts of drawn window decorations are active.
|
||||
|
/// Set by Window based on platform capabilities and user preferences.
|
||||
|
/// </summary>
|
||||
|
[Flags] |
||||
|
internal enum DrawnWindowDecorationParts |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// No decoration parts are active.
|
||||
|
/// </summary>
|
||||
|
None = 0, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Shadow/outer area is active.
|
||||
|
/// </summary>
|
||||
|
Shadow = 1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Frame border is active.
|
||||
|
/// </summary>
|
||||
|
Border = 2, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Titlebar is active.
|
||||
|
/// </summary>
|
||||
|
TitleBar = 4, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Resize grips are active.
|
||||
|
/// </summary>
|
||||
|
ResizeGrips = 8, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// All decoration parts are active.
|
||||
|
/// </summary>
|
||||
|
All = Shadow | Border | TitleBar | ResizeGrips |
||||
|
} |
||||
@ -0,0 +1,20 @@ |
|||||
|
using Avalonia.Controls.Templates; |
||||
|
using Avalonia.Metadata; |
||||
|
using Avalonia.Styling; |
||||
|
|
||||
|
namespace Avalonia.Controls.Chrome; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Interface for a template that produces <see cref="WindowDrawnDecorationsContent"/>.
|
||||
|
/// Implemented by the XAML template class in Avalonia.Markup.Xaml.
|
||||
|
/// Extends <see cref="ITemplate"/> so the XAML compiler assigns the template object directly
|
||||
|
/// instead of auto-calling Build().
|
||||
|
/// </summary>
|
||||
|
[ControlTemplateScope] |
||||
|
public interface IWindowDrawnDecorationsTemplate : ITemplate |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Builds the template and returns the content with its name scope.
|
||||
|
/// </summary>
|
||||
|
new TemplateResult<WindowDrawnDecorationsContent> Build(); |
||||
|
} |
||||
@ -0,0 +1,106 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Layout; |
||||
|
using Avalonia.Media; |
||||
|
|
||||
|
namespace Avalonia.Controls.Chrome; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// An invisible layer that provides resize grip hit-test zones at window edges.
|
||||
|
/// Grips only cover the frame/shadow area outside the client area.
|
||||
|
/// </summary>
|
||||
|
internal class ResizeGripLayer : Control |
||||
|
{ |
||||
|
private readonly Control _top = CreateGrip(WindowDecorationsElementRole.ResizeN, StandardCursorType.TopSide); |
||||
|
private readonly Control _bottom = CreateGrip(WindowDecorationsElementRole.ResizeS, StandardCursorType.BottomSide); |
||||
|
private readonly Control _left = CreateGrip(WindowDecorationsElementRole.ResizeW, StandardCursorType.LeftSide); |
||||
|
private readonly Control _right = CreateGrip(WindowDecorationsElementRole.ResizeE, StandardCursorType.RightSide); |
||||
|
private readonly Control _topLeft = CreateGrip(WindowDecorationsElementRole.ResizeNW, StandardCursorType.TopLeftCorner); |
||||
|
private readonly Control _topRight = CreateGrip(WindowDecorationsElementRole.ResizeNE, StandardCursorType.TopRightCorner); |
||||
|
private readonly Control _bottomLeft = CreateGrip(WindowDecorationsElementRole.ResizeSW, StandardCursorType.BottomLeftCorner); |
||||
|
private readonly Control _bottomRight = CreateGrip(WindowDecorationsElementRole.ResizeSE, StandardCursorType.BottomRightCorner); |
||||
|
|
||||
|
private Thickness _gripThickness; |
||||
|
|
||||
|
public ResizeGripLayer() |
||||
|
{ |
||||
|
IsHitTestVisible = true; |
||||
|
VisualChildren.Add(_top); |
||||
|
VisualChildren.Add(_bottom); |
||||
|
VisualChildren.Add(_left); |
||||
|
VisualChildren.Add(_right); |
||||
|
VisualChildren.Add(_topLeft); |
||||
|
VisualChildren.Add(_topRight); |
||||
|
VisualChildren.Add(_bottomLeft); |
||||
|
VisualChildren.Add(_bottomRight); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// The thickness of the resize grip area at each edge.
|
||||
|
/// Grips are placed outside the client area (covering frame + shadow).
|
||||
|
/// </summary>
|
||||
|
internal Thickness GripThickness |
||||
|
{ |
||||
|
get => _gripThickness; |
||||
|
set |
||||
|
{ |
||||
|
if (_gripThickness != value) |
||||
|
{ |
||||
|
_gripThickness = value; |
||||
|
InvalidateArrange(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected override Size ArrangeOverride(Size finalSize) |
||||
|
{ |
||||
|
var gt = _gripThickness; |
||||
|
var w = finalSize.Width; |
||||
|
var h = finalSize.Height; |
||||
|
|
||||
|
// Hide all grips when thickness is zero (e.g. maximized/fullscreen)
|
||||
|
var hasGrips = gt.Left > 0 || gt.Top > 0 || gt.Right > 0 || gt.Bottom > 0; |
||||
|
IsHitTestVisible = hasGrips; |
||||
|
if (!hasGrips) |
||||
|
{ |
||||
|
var empty = new Rect(); |
||||
|
_top.Arrange(empty); |
||||
|
_bottom.Arrange(empty); |
||||
|
_left.Arrange(empty); |
||||
|
_right.Arrange(empty); |
||||
|
_topLeft.Arrange(empty); |
||||
|
_topRight.Arrange(empty); |
||||
|
_bottomLeft.Arrange(empty); |
||||
|
_bottomRight.Arrange(empty); |
||||
|
return finalSize; |
||||
|
} |
||||
|
|
||||
|
// Edges fill the space between their adjacent corners
|
||||
|
_top.Arrange(new Rect(gt.Left, 0, Math.Max(0, w - gt.Left - gt.Right), gt.Top)); |
||||
|
_bottom.Arrange(new Rect(gt.Left, h - gt.Bottom, Math.Max(0, w - gt.Left - gt.Right), gt.Bottom)); |
||||
|
_left.Arrange(new Rect(0, gt.Top, gt.Left, Math.Max(0, h - gt.Top - gt.Bottom))); |
||||
|
_right.Arrange(new Rect(w - gt.Right, gt.Top, gt.Right, Math.Max(0, h - gt.Top - gt.Bottom))); |
||||
|
|
||||
|
// Corners use the thickness of their adjacent edges
|
||||
|
_topLeft.Arrange(new Rect(0, 0, gt.Left, gt.Top)); |
||||
|
_topRight.Arrange(new Rect(w - gt.Right, 0, gt.Right, gt.Top)); |
||||
|
_bottomLeft.Arrange(new Rect(0, h - gt.Bottom, gt.Left, gt.Bottom)); |
||||
|
_bottomRight.Arrange(new Rect(w - gt.Right, h - gt.Bottom, gt.Right, gt.Bottom)); |
||||
|
|
||||
|
return finalSize; |
||||
|
} |
||||
|
|
||||
|
private static Control CreateGrip(WindowDecorationsElementRole role, StandardCursorType cursorType) |
||||
|
{ |
||||
|
var grip = new Border |
||||
|
{ |
||||
|
Background = Brushes.Transparent, |
||||
|
Cursor = new Cursor(cursorType), |
||||
|
IsHitTestVisible = true, |
||||
|
HorizontalAlignment = HorizontalAlignment.Stretch, |
||||
|
VerticalAlignment = VerticalAlignment.Stretch, |
||||
|
}; |
||||
|
WindowDecorationProperties.SetElementRole(grip, role); |
||||
|
return grip; |
||||
|
} |
||||
|
} |
||||
@ -1,111 +0,0 @@ |
|||||
using System; |
|
||||
using Avalonia.Automation.Peers; |
|
||||
using Avalonia.Controls.Automation.Peers; |
|
||||
using Avalonia.Reactive; |
|
||||
using Avalonia.Controls.Metadata; |
|
||||
using Avalonia.Controls.Primitives; |
|
||||
|
|
||||
namespace Avalonia.Controls.Chrome |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Draws a titlebar when managed client decorations are enabled.
|
|
||||
/// </summary>
|
|
||||
[TemplatePart("PART_CaptionButtons", typeof(CaptionButtons), IsRequired = true)] |
|
||||
[PseudoClasses(":minimized", ":normal", ":maximized", ":fullscreen")] |
|
||||
public class TitleBar : TemplatedControl |
|
||||
{ |
|
||||
private CompositeDisposable? _disposables; |
|
||||
private CaptionButtons? _captionButtons; |
|
||||
|
|
||||
private void UpdateSize(Window window) |
|
||||
{ |
|
||||
Margin = new Thickness( |
|
||||
window.OffScreenMargin.Left, |
|
||||
window.OffScreenMargin.Top, |
|
||||
window.OffScreenMargin.Right, |
|
||||
window.OffScreenMargin.Bottom); |
|
||||
|
|
||||
if (window.WindowState != WindowState.FullScreen) |
|
||||
{ |
|
||||
var height = Math.Max(0, window.WindowDecorationMargin.Top); |
|
||||
Height = height; |
|
||||
_captionButtons?.Height = window.SystemDecorations == SystemDecorations.Full ? height : 0; |
|
||||
} |
|
||||
else |
|
||||
{ |
|
||||
// Note: apparently the titlebar was supposed to be displayed when hovering the top of the screen,
|
|
||||
// to mimic macOS behavior. This has been broken for years. It actually only partially works if the
|
|
||||
// window is FullScreen right on startup, and only once. Any size change will then break it.
|
|
||||
// Disable it for now.
|
|
||||
// TODO: restore that behavior so that it works in all cases
|
|
||||
Height = 0; |
|
||||
_captionButtons?.Height = 0; |
|
||||
} |
|
||||
|
|
||||
IsVisible = window.PlatformImpl?.NeedsManagedDecorations ?? false; |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc />
|
|
||||
protected override void OnApplyTemplate(TemplateAppliedEventArgs e) |
|
||||
{ |
|
||||
base.OnApplyTemplate(e); |
|
||||
|
|
||||
_captionButtons?.Detach(); |
|
||||
|
|
||||
_captionButtons = e.NameScope.Get<CaptionButtons>("PART_CaptionButtons"); |
|
||||
|
|
||||
if (TopLevel.GetTopLevel(this) is Window window) |
|
||||
{ |
|
||||
_captionButtons?.Attach(window); |
|
||||
|
|
||||
UpdateSize(window); |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc />
|
|
||||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) |
|
||||
{ |
|
||||
base.OnAttachedToVisualTree(e); |
|
||||
|
|
||||
if (TopLevel.GetTopLevel(this) is Window window) |
|
||||
{ |
|
||||
_disposables = new CompositeDisposable(6) |
|
||||
{ |
|
||||
window.GetObservable(Window.WindowDecorationMarginProperty) |
|
||||
.Subscribe(_ => UpdateSize(window)), |
|
||||
window.GetObservable(Window.ExtendClientAreaTitleBarHeightHintProperty) |
|
||||
.Subscribe(_ => UpdateSize(window)), |
|
||||
window.GetObservable(Window.OffScreenMarginProperty) |
|
||||
.Subscribe(_ => UpdateSize(window)), |
|
||||
window.GetObservable(Window.ExtendClientAreaChromeHintsProperty) |
|
||||
.Subscribe(_ => UpdateSize(window)), |
|
||||
window.GetObservable(Window.WindowStateProperty) |
|
||||
.Subscribe(x => |
|
||||
{ |
|
||||
PseudoClasses.Set(":minimized", x == WindowState.Minimized); |
|
||||
PseudoClasses.Set(":normal", x == WindowState.Normal); |
|
||||
PseudoClasses.Set(":maximized", x == WindowState.Maximized); |
|
||||
PseudoClasses.Set(":fullscreen", x == WindowState.FullScreen); |
|
||||
UpdateSize(window); |
|
||||
}), |
|
||||
window.GetObservable(Window.IsExtendedIntoWindowDecorationsProperty) |
|
||||
.Subscribe(_ => UpdateSize(window)) |
|
||||
}; |
|
||||
} |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc />
|
|
||||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) |
|
||||
{ |
|
||||
base.OnDetachedFromVisualTree(e); |
|
||||
|
|
||||
_disposables?.Dispose(); |
|
||||
|
|
||||
_captionButtons?.Detach(); |
|
||||
_captionButtons = null; |
|
||||
} |
|
||||
|
|
||||
/// <inheritdoc />
|
|
||||
protected override AutomationPeer OnCreateAutomationPeer() => new TitleBarAutomationPeer(this); |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,27 @@ |
|||||
|
using Avalonia.Input; |
||||
|
|
||||
|
namespace Avalonia.Controls.Chrome; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Provides attached properties for window decoration hit-testing.
|
||||
|
/// </summary>
|
||||
|
public static class WindowDecorationProperties |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="WindowDecorationsElementRole"/> attached property.
|
||||
|
/// Marks a visual element with a specific role for non-client hit-testing.
|
||||
|
/// Can be applied to any element in the visual tree, not limited to decoration children.
|
||||
|
/// </summary>
|
||||
|
public static readonly AttachedProperty<WindowDecorationsElementRole> ElementRoleProperty = |
||||
|
AvaloniaProperty.RegisterAttached<Visual, WindowDecorationsElementRole>("ElementRole", typeof(WindowDecorationProperties)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the <see cref="WindowDecorationsElementRole"/> for the specified element.
|
||||
|
/// </summary>
|
||||
|
public static WindowDecorationsElementRole GetElementRole(Visual element) => element.GetValue(ElementRoleProperty); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Sets the <see cref="WindowDecorationsElementRole"/> for the specified element.
|
||||
|
/// </summary>
|
||||
|
public static void SetElementRole(Visual element, WindowDecorationsElementRole value) => element.SetValue(ElementRoleProperty, value); |
||||
|
} |
||||
@ -0,0 +1,590 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Automation; |
||||
|
using Avalonia.Controls.Metadata; |
||||
|
using Avalonia.Controls.Primitives; |
||||
|
using Avalonia.LogicalTree; |
||||
|
using Avalonia.Reactive; |
||||
|
using Avalonia.Styling; |
||||
|
|
||||
|
namespace Avalonia.Controls.Chrome; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Manages client-side window decorations (app-drawn window frame).
|
||||
|
/// This is a logical element that holds the decorations template and properties.
|
||||
|
/// TopLevelHost extracts overlay/underlay/popover visuals from the template content
|
||||
|
/// and inserts them into its own visual tree.
|
||||
|
/// </summary>
|
||||
|
[PseudoClasses(pcNormal, pcMaximized, pcFullscreen, pcHasShadow, pcHasBorder, pcHasTitlebar)] |
||||
|
[TemplatePart(PART_CloseButton, typeof(Button))] |
||||
|
[TemplatePart(PART_MinimizeButton, typeof(Button))] |
||||
|
[TemplatePart(PART_MaximizeButton, typeof(Button))] |
||||
|
[TemplatePart(PART_FullScreenButton, typeof(Button))] |
||||
|
[TemplatePart(PART_PopoverCloseButton, typeof(Button))] |
||||
|
[TemplatePart(PART_PopoverFullScreenButton, typeof(Button))] |
||||
|
[TemplatePart(PART_TitleBar, typeof(Panel))] |
||||
|
public class WindowDrawnDecorations : StyledElement |
||||
|
{ |
||||
|
internal const string pcNormal = ":normal"; |
||||
|
internal const string pcMaximized = ":maximized"; |
||||
|
internal const string pcFullscreen = ":fullscreen"; |
||||
|
internal const string pcHasShadow = ":has-shadow"; |
||||
|
internal const string pcHasBorder = ":has-border"; |
||||
|
internal const string pcHasTitlebar = ":has-titlebar"; |
||||
|
|
||||
|
// Template part names for caption buttons
|
||||
|
internal const string PART_CloseButton = "PART_CloseButton"; |
||||
|
internal const string PART_MinimizeButton = "PART_MinimizeButton"; |
||||
|
internal const string PART_MaximizeButton = "PART_MaximizeButton"; |
||||
|
internal const string PART_FullScreenButton = "PART_FullScreenButton"; |
||||
|
// Popover caption buttons (separate names to avoid name scope conflicts)
|
||||
|
internal const string PART_PopoverCloseButton = "PART_PopoverCloseButton"; |
||||
|
internal const string PART_PopoverFullScreenButton = "PART_PopoverFullScreenButton"; |
||||
|
// Titlebar panel
|
||||
|
internal const string PART_TitleBar = "PART_TitleBar"; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="Template"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<IWindowDrawnDecorationsTemplate?> TemplateProperty = |
||||
|
AvaloniaProperty.Register<WindowDrawnDecorations, IWindowDrawnDecorationsTemplate?>(nameof(Template)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="DefaultTitleBarHeight"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<double> DefaultTitleBarHeightProperty = |
||||
|
AvaloniaProperty.Register<WindowDrawnDecorations, double>(nameof(DefaultTitleBarHeight)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="DefaultFrameThickness"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<Thickness> DefaultFrameThicknessProperty = |
||||
|
AvaloniaProperty.Register<WindowDrawnDecorations, Thickness>(nameof(DefaultFrameThickness)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="DefaultShadowThickness"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<Thickness> DefaultShadowThicknessProperty = |
||||
|
AvaloniaProperty.Register<WindowDrawnDecorations, Thickness>(nameof(DefaultShadowThickness)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="TitleBarHeight"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<WindowDrawnDecorations, double> TitleBarHeightProperty = |
||||
|
AvaloniaProperty.RegisterDirect<WindowDrawnDecorations, double>( |
||||
|
nameof(TitleBarHeight), |
||||
|
o => o.TitleBarHeight); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="FrameThickness"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<WindowDrawnDecorations, Thickness> FrameThicknessProperty = |
||||
|
AvaloniaProperty.RegisterDirect<WindowDrawnDecorations, Thickness>( |
||||
|
nameof(FrameThickness), |
||||
|
o => o.FrameThickness); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="ShadowThickness"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<WindowDrawnDecorations, Thickness> ShadowThicknessProperty = |
||||
|
AvaloniaProperty.RegisterDirect<WindowDrawnDecorations, Thickness>( |
||||
|
nameof(ShadowThickness), |
||||
|
o => o.ShadowThickness); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="HasShadow"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<WindowDrawnDecorations, bool> HasShadowProperty = |
||||
|
AvaloniaProperty.RegisterDirect<WindowDrawnDecorations, bool>( |
||||
|
nameof(HasShadow), |
||||
|
o => o.HasShadow); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="HasBorder"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<WindowDrawnDecorations, bool> HasBorderProperty = |
||||
|
AvaloniaProperty.RegisterDirect<WindowDrawnDecorations, bool>( |
||||
|
nameof(HasBorder), |
||||
|
o => o.HasBorder); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="HasTitleBar"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly DirectProperty<WindowDrawnDecorations, bool> HasTitleBarProperty = |
||||
|
AvaloniaProperty.RegisterDirect<WindowDrawnDecorations, bool>( |
||||
|
nameof(HasTitleBar), |
||||
|
o => o.HasTitleBar); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="Title"/> property.
|
||||
|
/// </summary>
|
||||
|
public static readonly StyledProperty<string?> TitleProperty = |
||||
|
AvaloniaProperty.Register<WindowDrawnDecorations, string?>(nameof(Title)); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Defines the <see cref="EnabledParts"/> property.
|
||||
|
/// </summary>
|
||||
|
internal static readonly StyledProperty<DrawnWindowDecorationParts> EnabledPartsProperty = |
||||
|
AvaloniaProperty.Register<WindowDrawnDecorations, DrawnWindowDecorationParts>(nameof(EnabledParts), |
||||
|
defaultValue: DrawnWindowDecorationParts.None); |
||||
|
|
||||
|
private IWindowDrawnDecorationsTemplate? _appliedTemplate; |
||||
|
private INameScope? _templateNameScope; |
||||
|
private Button? _closeButton; |
||||
|
private Button? _minimizeButton; |
||||
|
private Button? _maximizeButton; |
||||
|
private Button? _fullScreenButton; |
||||
|
private Button? _popoverCloseButton; |
||||
|
private Button? _popoverFullScreenButton; |
||||
|
private IDisposable? _windowSubscriptions; |
||||
|
private Window? _hostWindow; |
||||
|
private double _titleBarHeightOverride = -1; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Raised when any property affecting the effective geometry changes
|
||||
|
/// (effective titlebar height, frame thickness, or shadow thickness).
|
||||
|
/// </summary>
|
||||
|
internal event Action? EffectiveGeometryChanged; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the decorations template.
|
||||
|
/// </summary>
|
||||
|
public IWindowDrawnDecorationsTemplate? Template |
||||
|
{ |
||||
|
get => GetValue(TemplateProperty); |
||||
|
set => SetValue(TemplateProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the theme-set default titlebar height.
|
||||
|
/// </summary>
|
||||
|
public double DefaultTitleBarHeight |
||||
|
{ |
||||
|
get => GetValue(DefaultTitleBarHeightProperty); |
||||
|
set => SetValue(DefaultTitleBarHeightProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the theme-set default frame thickness.
|
||||
|
/// </summary>
|
||||
|
public Thickness DefaultFrameThickness |
||||
|
{ |
||||
|
get => GetValue(DefaultFrameThicknessProperty); |
||||
|
set => SetValue(DefaultFrameThicknessProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the theme-set default shadow thickness.
|
||||
|
/// </summary>
|
||||
|
public Thickness DefaultShadowThickness |
||||
|
{ |
||||
|
get => GetValue(DefaultShadowThicknessProperty); |
||||
|
set => SetValue(DefaultShadowThicknessProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the titlebar height override.
|
||||
|
/// When -1, falls back to <see cref="DefaultTitleBarHeight"/>.
|
||||
|
/// </summary>
|
||||
|
internal double TitleBarHeightOverride |
||||
|
{ |
||||
|
get => _titleBarHeightOverride; |
||||
|
set { _titleBarHeightOverride = value; UpdateEffectiveGeometry(); } |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the frame thickness override.
|
||||
|
/// When set, takes precedence over <see cref="DefaultFrameThickness"/>.
|
||||
|
/// </summary>
|
||||
|
internal Thickness? FrameThicknessOverride |
||||
|
{ |
||||
|
get; |
||||
|
set |
||||
|
{ |
||||
|
field = value; |
||||
|
UpdateEffectiveGeometry(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the shadow thickness override.
|
||||
|
/// When set, takes precedence over <see cref="DefaultShadowThickness"/>.
|
||||
|
/// </summary>
|
||||
|
internal Thickness? ShadowThicknessOverride |
||||
|
{ |
||||
|
get; |
||||
|
set |
||||
|
{ |
||||
|
field = value; |
||||
|
UpdateEffectiveGeometry(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the window title displayed in the decorations.
|
||||
|
/// </summary>
|
||||
|
public string? Title |
||||
|
{ |
||||
|
get => GetValue(TitleProperty); |
||||
|
set => SetValue(TitleProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets which decoration parts are enabled.
|
||||
|
/// Set by Window based on platform capabilities and user preferences.
|
||||
|
/// </summary>
|
||||
|
internal DrawnWindowDecorationParts EnabledParts |
||||
|
{ |
||||
|
get => GetValue(EnabledPartsProperty); |
||||
|
set => SetValue(EnabledPartsProperty, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the built template content.
|
||||
|
/// </summary>
|
||||
|
public WindowDrawnDecorationsContent? Content { get; private set; } |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the effective titlebar height, resolving -1 override to the default.
|
||||
|
/// Returns 0 if titlebar part is disabled.
|
||||
|
/// </summary>
|
||||
|
public double TitleBarHeight |
||||
|
{ |
||||
|
get; |
||||
|
private set => SetAndRaise(TitleBarHeightProperty, ref field, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the effective frame thickness.
|
||||
|
/// Uses FrameThicknessOverride if explicitly set, otherwise DefaultFrameThickness.
|
||||
|
/// Returns zero if border part is disabled.
|
||||
|
/// </summary>
|
||||
|
public Thickness FrameThickness |
||||
|
{ |
||||
|
get; |
||||
|
private set => SetAndRaise(FrameThicknessProperty, ref field, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the effective shadow thickness.
|
||||
|
/// Uses ShadowThicknessOverride if explicitly set, otherwise DefaultShadowThickness.
|
||||
|
/// Returns zero if shadow part is disabled.
|
||||
|
/// </summary>
|
||||
|
public Thickness ShadowThickness |
||||
|
{ |
||||
|
get; |
||||
|
private set => SetAndRaise(ShadowThicknessProperty, ref field, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether the shadow decoration part is enabled.
|
||||
|
/// </summary>
|
||||
|
public bool HasShadow |
||||
|
{ |
||||
|
get; |
||||
|
private set => SetAndRaise(HasShadowProperty, ref field, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether the border decoration part is enabled.
|
||||
|
/// </summary>
|
||||
|
public bool HasBorder |
||||
|
{ |
||||
|
get; |
||||
|
private set => SetAndRaise(HasBorderProperty, ref field, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets a value indicating whether the title bar decoration part is enabled.
|
||||
|
/// </summary>
|
||||
|
public bool HasTitleBar |
||||
|
{ |
||||
|
get; |
||||
|
private set => SetAndRaise(HasTitleBarProperty, ref field, value); |
||||
|
} |
||||
|
|
||||
|
static WindowDrawnDecorations() |
||||
|
{ |
||||
|
TemplateProperty.Changed.AddClassHandler<WindowDrawnDecorations>((x, _) => x.InvalidateTemplate()); |
||||
|
EnabledPartsProperty.Changed.AddClassHandler<WindowDrawnDecorations>((x, _) => |
||||
|
{ |
||||
|
x.UpdateEnabledPartsPseudoClasses(); |
||||
|
x.UpdateEffectiveGeometry(); |
||||
|
}); |
||||
|
|
||||
|
DefaultTitleBarHeightProperty.Changed.AddClassHandler<WindowDrawnDecorations>((x, _) => x.UpdateEffectiveGeometry()); |
||||
|
DefaultFrameThicknessProperty.Changed.AddClassHandler<WindowDrawnDecorations>((x, _) => x.UpdateEffectiveGeometry()); |
||||
|
DefaultShadowThicknessProperty.Changed.AddClassHandler<WindowDrawnDecorations>((x, _) => x.UpdateEffectiveGeometry()); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Applies the template if it has changed.
|
||||
|
/// </summary>
|
||||
|
internal void ApplyTemplate() |
||||
|
{ |
||||
|
var template = Template; |
||||
|
if (template == _appliedTemplate) |
||||
|
return; |
||||
|
|
||||
|
// Clean up old content
|
||||
|
if (Content != null) |
||||
|
{ |
||||
|
DetachCaptionButtons(); |
||||
|
LogicalChildren.Remove(Content); |
||||
|
((ISetLogicalParent)Content).SetParent(null); |
||||
|
Content = null; |
||||
|
_templateNameScope = null; |
||||
|
} |
||||
|
|
||||
|
_appliedTemplate = template; |
||||
|
|
||||
|
if (template == null) |
||||
|
return; |
||||
|
|
||||
|
var result = template.Build(); |
||||
|
Content = result.Result; |
||||
|
_templateNameScope = result.NameScope; |
||||
|
|
||||
|
if (Content != null) |
||||
|
{ |
||||
|
TemplatedControl.ApplyTemplatedParent(Content, this); |
||||
|
LogicalChildren.Add(Content); |
||||
|
((ISetLogicalParent)Content).SetParent(this); |
||||
|
} |
||||
|
|
||||
|
AttachCaptionButtons(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Attaches to the specified window for caption button interactions and state tracking.
|
||||
|
/// </summary>
|
||||
|
internal void Attach(Window window) |
||||
|
{ |
||||
|
if (_hostWindow == window) |
||||
|
return; |
||||
|
|
||||
|
Detach(); |
||||
|
_hostWindow = window; |
||||
|
|
||||
|
_windowSubscriptions = new CompositeDisposable |
||||
|
{ |
||||
|
window.GetObservable(Window.TitleProperty) |
||||
|
.Subscribe(title => SetCurrentValue(TitleProperty, title)), |
||||
|
window.GetObservable(Window.CanMaximizeProperty) |
||||
|
.Subscribe(_ => |
||||
|
{ |
||||
|
UpdateMaximizeButtonState(); |
||||
|
UpdateFullScreenButtonState(); |
||||
|
}), |
||||
|
window.GetObservable(Window.CanMinimizeProperty) |
||||
|
.Subscribe(_ => UpdateMinimizeButtonState()), |
||||
|
window.GetObservable(Window.WindowStateProperty) |
||||
|
.Subscribe(state => |
||||
|
{ |
||||
|
PseudoClasses.Set(pcNormal, state == WindowState.Normal); |
||||
|
PseudoClasses.Set(pcMaximized, state == WindowState.Maximized); |
||||
|
PseudoClasses.Set(pcFullscreen, state == WindowState.FullScreen); |
||||
|
UpdateMaximizeButtonState(); |
||||
|
UpdateMinimizeButtonState(); |
||||
|
UpdateFullScreenButtonState(); |
||||
|
}), |
||||
|
}; |
||||
|
|
||||
|
UpdateMaximizeButtonState(); |
||||
|
UpdateMinimizeButtonState(); |
||||
|
UpdateFullScreenButtonState(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Detaches from the current window.
|
||||
|
/// </summary>
|
||||
|
internal void Detach() |
||||
|
{ |
||||
|
_windowSubscriptions?.Dispose(); |
||||
|
_windowSubscriptions = null; |
||||
|
_hostWindow = null; |
||||
|
} |
||||
|
|
||||
|
private void InvalidateTemplate() |
||||
|
{ |
||||
|
_appliedTemplate = null; |
||||
|
} |
||||
|
|
||||
|
private void AttachCaptionButtons() |
||||
|
{ |
||||
|
if (_templateNameScope == null) |
||||
|
return; |
||||
|
|
||||
|
_closeButton = _templateNameScope.Find<Button>(PART_CloseButton); |
||||
|
_minimizeButton = _templateNameScope.Find<Button>(PART_MinimizeButton); |
||||
|
_maximizeButton = _templateNameScope.Find<Button>(PART_MaximizeButton); |
||||
|
_fullScreenButton = _templateNameScope.Find<Button>(PART_FullScreenButton); |
||||
|
_popoverCloseButton = _templateNameScope.Find<Button>(PART_PopoverCloseButton); |
||||
|
_popoverFullScreenButton = _templateNameScope.Find<Button>(PART_PopoverFullScreenButton); |
||||
|
|
||||
|
var titleBar = _templateNameScope.Find<Control>(PART_TitleBar); |
||||
|
if (titleBar != null) |
||||
|
{ |
||||
|
AutomationProperties.SetIsControlElementOverride(titleBar, true); |
||||
|
AutomationProperties.SetAutomationId(titleBar, "AvaloniaTitleBar"); |
||||
|
AutomationProperties.SetName(titleBar, "TitleBar"); |
||||
|
} |
||||
|
|
||||
|
if (_closeButton != null) |
||||
|
{ |
||||
|
AutomationProperties.SetAutomationId(_closeButton, "Close"); |
||||
|
AutomationProperties.SetName(_closeButton, "Close"); |
||||
|
_closeButton.Click += OnCloseButtonClick; |
||||
|
} |
||||
|
|
||||
|
if (_minimizeButton != null) |
||||
|
{ |
||||
|
AutomationProperties.SetAutomationId(_minimizeButton, "Minimize"); |
||||
|
AutomationProperties.SetName(_minimizeButton, "Minimize"); |
||||
|
_minimizeButton.Click += OnMinimizeButtonClick; |
||||
|
UpdateMinimizeButtonState(); |
||||
|
} |
||||
|
|
||||
|
if (_maximizeButton != null) |
||||
|
{ |
||||
|
_maximizeButton.Click += OnMaximizeButtonClick; |
||||
|
AutomationProperties.SetAutomationId(_maximizeButton, "Maximize"); |
||||
|
AutomationProperties.SetName(_maximizeButton, "Maximize"); |
||||
|
UpdateMaximizeButtonState(); |
||||
|
} |
||||
|
|
||||
|
if (_fullScreenButton != null) |
||||
|
{ |
||||
|
_fullScreenButton.Click += OnFullScreenButtonClick; |
||||
|
AutomationProperties.SetAutomationId(_fullScreenButton, "Fullscreen"); |
||||
|
AutomationProperties.SetName(_fullScreenButton, "Fullscreen"); |
||||
|
UpdateFullScreenButtonState(); |
||||
|
} |
||||
|
|
||||
|
if (_popoverCloseButton != null) |
||||
|
{ |
||||
|
_popoverCloseButton.Click += OnCloseButtonClick; |
||||
|
AutomationProperties.SetAutomationId(_popoverCloseButton, "FullscreenClose"); |
||||
|
AutomationProperties.SetName(_popoverCloseButton, "Close"); |
||||
|
} |
||||
|
|
||||
|
if (_popoverFullScreenButton != null) |
||||
|
{ |
||||
|
_popoverFullScreenButton.Click += OnFullScreenButtonClick; |
||||
|
AutomationProperties.SetAutomationId(_popoverFullScreenButton, "ExitFullscreen"); |
||||
|
AutomationProperties.SetName(_popoverFullScreenButton, "ExitFullscreen"); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void DetachCaptionButtons() |
||||
|
{ |
||||
|
if (_closeButton != null) |
||||
|
_closeButton.Click -= OnCloseButtonClick; |
||||
|
if (_minimizeButton != null) |
||||
|
_minimizeButton.Click -= OnMinimizeButtonClick; |
||||
|
if (_maximizeButton != null) |
||||
|
_maximizeButton.Click -= OnMaximizeButtonClick; |
||||
|
if (_fullScreenButton != null) |
||||
|
_fullScreenButton.Click -= OnFullScreenButtonClick; |
||||
|
if (_popoverCloseButton != null) |
||||
|
_popoverCloseButton.Click -= OnCloseButtonClick; |
||||
|
if (_popoverFullScreenButton != null) |
||||
|
_popoverFullScreenButton.Click -= OnFullScreenButtonClick; |
||||
|
|
||||
|
_closeButton = null; |
||||
|
_minimizeButton = null; |
||||
|
_maximizeButton = null; |
||||
|
_fullScreenButton = null; |
||||
|
_popoverCloseButton = null; |
||||
|
_popoverFullScreenButton = null; |
||||
|
} |
||||
|
|
||||
|
private void OnCloseButtonClick(object? sender, Interactivity.RoutedEventArgs e) |
||||
|
{ |
||||
|
_hostWindow?.Close(); |
||||
|
e.Handled = true; |
||||
|
} |
||||
|
|
||||
|
private void OnMinimizeButtonClick(object? sender, Interactivity.RoutedEventArgs e) |
||||
|
{ |
||||
|
if (_hostWindow != null) |
||||
|
_hostWindow.WindowState = WindowState.Minimized; |
||||
|
e.Handled = true; |
||||
|
} |
||||
|
|
||||
|
private void OnMaximizeButtonClick(object? sender, Interactivity.RoutedEventArgs e) |
||||
|
{ |
||||
|
if (_hostWindow != null) |
||||
|
_hostWindow.WindowState = _hostWindow.WindowState == WindowState.Maximized |
||||
|
? WindowState.Normal |
||||
|
: WindowState.Maximized; |
||||
|
e.Handled = true; |
||||
|
} |
||||
|
|
||||
|
private void OnFullScreenButtonClick(object? sender, Interactivity.RoutedEventArgs e) |
||||
|
{ |
||||
|
if (_hostWindow != null) |
||||
|
_hostWindow.WindowState = _hostWindow.WindowState == WindowState.FullScreen |
||||
|
? WindowState.Normal |
||||
|
: WindowState.FullScreen; |
||||
|
e.Handled = true; |
||||
|
} |
||||
|
|
||||
|
private void UpdateMaximizeButtonState() |
||||
|
{ |
||||
|
if (_maximizeButton == null) |
||||
|
return; |
||||
|
_maximizeButton.IsEnabled = _hostWindow?.WindowState switch |
||||
|
{ |
||||
|
WindowState.Maximized or WindowState.FullScreen => _hostWindow.CanResize, |
||||
|
WindowState.Normal => _hostWindow.CanMaximize, |
||||
|
_ => true |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private void UpdateMinimizeButtonState() |
||||
|
{ |
||||
|
if (_minimizeButton == null) |
||||
|
return; |
||||
|
_minimizeButton.IsEnabled = _hostWindow?.CanMinimize ?? true; |
||||
|
} |
||||
|
|
||||
|
private void UpdateFullScreenButtonState() |
||||
|
{ |
||||
|
if (_fullScreenButton == null) |
||||
|
return; |
||||
|
_fullScreenButton.IsEnabled = _hostWindow?.WindowState == WindowState.FullScreen |
||||
|
? _hostWindow.CanResize |
||||
|
: _hostWindow?.CanMaximize ?? true; |
||||
|
} |
||||
|
|
||||
|
private void UpdateEffectiveGeometry() |
||||
|
{ |
||||
|
TitleBarHeight = EnabledParts.HasFlag(DrawnWindowDecorationParts.TitleBar) |
||||
|
? (TitleBarHeightOverride == -1 ? DefaultTitleBarHeight : TitleBarHeightOverride) |
||||
|
: 0; |
||||
|
|
||||
|
FrameThickness = EnabledParts.HasFlag(DrawnWindowDecorationParts.Border) |
||||
|
? (FrameThicknessOverride ?? DefaultFrameThickness) |
||||
|
: default; |
||||
|
|
||||
|
ShadowThickness = EnabledParts.HasFlag(DrawnWindowDecorationParts.Shadow) |
||||
|
? (ShadowThicknessOverride ?? DefaultShadowThickness) |
||||
|
: default; |
||||
|
|
||||
|
EffectiveGeometryChanged?.Invoke(); |
||||
|
} |
||||
|
|
||||
|
private void UpdateEnabledPartsPseudoClasses() |
||||
|
{ |
||||
|
var parts = EnabledParts; |
||||
|
var hasShadow = parts.HasFlag(DrawnWindowDecorationParts.Shadow); |
||||
|
var hasBorder = parts.HasFlag(DrawnWindowDecorationParts.Border); |
||||
|
var hasTitleBar = parts.HasFlag(DrawnWindowDecorationParts.TitleBar); |
||||
|
HasShadow = hasShadow; |
||||
|
HasBorder = hasBorder; |
||||
|
HasTitleBar = hasTitleBar; |
||||
|
PseudoClasses.Set(pcHasShadow, hasShadow); |
||||
|
PseudoClasses.Set(pcHasBorder, hasBorder); |
||||
|
PseudoClasses.Set(pcHasTitlebar, hasTitleBar); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,59 @@ |
|||||
|
using Avalonia.LogicalTree; |
||||
|
using Avalonia.Styling; |
||||
|
|
||||
|
namespace Avalonia.Controls.Chrome; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Holds the template content for <see cref="WindowDrawnDecorations"/>.
|
||||
|
/// Contains three visual slots: Underlay, Overlay, and FullscreenPopover.
|
||||
|
/// </summary>
|
||||
|
public class WindowDrawnDecorationsContent : StyledElement |
||||
|
{ |
||||
|
/// <summary>
|
||||
|
/// Gets or sets the overlay layer content (titlebar, caption buttons).
|
||||
|
/// Positioned above the client area.
|
||||
|
/// </summary>
|
||||
|
public Control? Overlay |
||||
|
{ |
||||
|
get => field; |
||||
|
set => HandleLogicalChild(ref field, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the underlay layer content (borders, background, shadow area).
|
||||
|
/// Positioned below the client area.
|
||||
|
/// </summary>
|
||||
|
public Control? Underlay |
||||
|
{ |
||||
|
get => field; |
||||
|
set => HandleLogicalChild(ref field, value); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets or sets the fullscreen popover content.
|
||||
|
/// Shown when the user hovers the pointer at the top of the window in fullscreen mode.
|
||||
|
/// </summary>
|
||||
|
public Control? FullscreenPopover |
||||
|
{ |
||||
|
get => field; |
||||
|
set => HandleLogicalChild(ref field, value); |
||||
|
} |
||||
|
|
||||
|
private void HandleLogicalChild(ref Control? field, Control? value) |
||||
|
{ |
||||
|
if (field == value) |
||||
|
return; |
||||
|
if (field != null) |
||||
|
{ |
||||
|
LogicalChildren.Remove(field); |
||||
|
((ISetLogicalParent)field).SetParent(null); |
||||
|
} |
||||
|
|
||||
|
field = value; |
||||
|
if (field != null) |
||||
|
{ |
||||
|
LogicalChildren.Add(field); |
||||
|
((ISetLogicalParent)field).SetParent(this); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -1,38 +0,0 @@ |
|||||
using System; |
|
||||
|
|
||||
namespace Avalonia.Platform |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// Hint for Window Chrome when ClientArea is Extended.
|
|
||||
/// </summary>
|
|
||||
[Flags] |
|
||||
public enum ExtendClientAreaChromeHints |
|
||||
{ |
|
||||
/// <summary>
|
|
||||
/// There will be no chrome at all.
|
|
||||
/// </summary>
|
|
||||
NoChrome, |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// The default for the platform.
|
|
||||
/// </summary>
|
|
||||
Default = PreferSystemChrome, |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Use SystemChrome
|
|
||||
/// </summary>
|
|
||||
SystemChrome = 0x01, |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// Use system chrome where possible. OSX system chrome is used, Windows managed chrome is used.
|
|
||||
/// This is because Windows Chrome can not be shown on top of user content.
|
|
||||
/// </summary>
|
|
||||
PreferSystemChrome = 0x02, |
|
||||
|
|
||||
/// <summary>
|
|
||||
/// On OSX the titlebar is the thicker toolbar kind. Causes traffic lights to be positioned
|
|
||||
/// slightly lower than normal.
|
|
||||
/// </summary>
|
|
||||
OSXThickTitleBar = 0x08, |
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,33 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Metadata; |
||||
|
|
||||
|
namespace Avalonia.Controls.Platform; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Flags indicating which drawn decoration parts a platform backend requires.
|
||||
|
/// </summary>
|
||||
|
[Flags, PrivateApi] |
||||
|
public enum PlatformRequestedDrawnDecoration |
||||
|
{ |
||||
|
None = 0, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Platform needs app-drawn window shadow.
|
||||
|
/// </summary>
|
||||
|
Shadow = 1, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Platform needs app-drawn window border/frame.
|
||||
|
/// </summary>
|
||||
|
Border = 2, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Platform needs app-drawn resize grips.
|
||||
|
/// </summary>
|
||||
|
ResizeGrips = 4, |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Platform needs app-drawn window titlebar.
|
||||
|
/// </summary>
|
||||
|
TitleBar = 8, |
||||
|
} |
||||
@ -1,11 +0,0 @@ |
|||||
using System.Linq; |
|
||||
using Avalonia.Rendering; |
|
||||
using Avalonia.VisualTree; |
|
||||
|
|
||||
namespace Avalonia.Controls.Primitives |
|
||||
{ |
|
||||
internal class ChromeOverlayLayer : Panel |
|
||||
{ |
|
||||
|
|
||||
} |
|
||||
} |
|
||||
@ -0,0 +1,252 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using Avalonia.Automation; |
||||
|
using Avalonia.Automation.Peers; |
||||
|
using Avalonia.Controls.Chrome; |
||||
|
using Avalonia.Input; |
||||
|
using Avalonia.Reactive; |
||||
|
|
||||
|
namespace Avalonia.Controls; |
||||
|
|
||||
|
internal partial class TopLevelHost |
||||
|
{ |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Wrapper that holds a single visual child, used to host decoration layer content
|
||||
|
/// extracted from the decorations template.
|
||||
|
/// </summary>
|
||||
|
private class LayerWrapper : Control |
||||
|
{ |
||||
|
public Control? Inner |
||||
|
{ |
||||
|
get => field; |
||||
|
set |
||||
|
{ |
||||
|
if (field == value) |
||||
|
return; |
||||
|
if (field != null) |
||||
|
VisualChildren.Remove(field); |
||||
|
field = value; |
||||
|
if (field != null) |
||||
|
VisualChildren.Add(field); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private readonly TopLevel _topLevel; |
||||
|
private WindowDrawnDecorations? _decorations; |
||||
|
private LayerWrapper? _underlay; |
||||
|
private LayerWrapper? _overlay; |
||||
|
private LayerWrapper? _fullscreenPopover; |
||||
|
private ResizeGripLayer? _resizeGrips; |
||||
|
private IDisposable? _decorationsSubscriptions; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Gets the current drawn decorations instance, if active.
|
||||
|
/// </summary>
|
||||
|
internal WindowDrawnDecorations? Decorations => _decorations; |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Enables drawn window decorations with the specified parts.
|
||||
|
/// Creates the decorations instance, applies the template, and inserts layers into the visual tree.
|
||||
|
/// </summary>
|
||||
|
internal void EnableDecorations(DrawnWindowDecorationParts parts) |
||||
|
{ |
||||
|
if (_decorations != null) |
||||
|
{ |
||||
|
// Layers persist across part changes; pseudo-classes driven by EnabledParts
|
||||
|
// control visibility of individual decoration elements in the theme.
|
||||
|
_decorations.EnabledParts = parts; |
||||
|
if (_resizeGrips != null) |
||||
|
_resizeGrips.IsVisible = parts.HasFlag(DrawnWindowDecorationParts.ResizeGrips); |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
_decorations = new WindowDrawnDecorations(); |
||||
|
_decorations.EnabledParts = parts; |
||||
|
|
||||
|
// Set up logical parenting
|
||||
|
LogicalChildren.Add(_decorations); |
||||
|
|
||||
|
// Create layer wrappers
|
||||
|
_underlay = new LayerWrapper() { [AutomationProperties.AutomationIdProperty] = "WindowChromeUnderlay" }; |
||||
|
_overlay = new LayerWrapper() { [AutomationProperties.AutomationIdProperty] = "WindowChromeOverlay" }; |
||||
|
_fullscreenPopover = new LayerWrapper() |
||||
|
{ |
||||
|
IsVisible = false, [AutomationProperties.AutomationIdProperty] = "PopoverWindowChrome" |
||||
|
}; |
||||
|
|
||||
|
// Insert layers: underlay below TopLevel, overlay and popover above
|
||||
|
// Visual order: underlay(0), TopLevel(1), overlay(2), fullscreenPopover(3), resizeGrips(4)
|
||||
|
VisualChildren.Insert(0, _underlay); |
||||
|
VisualChildren.Add(_overlay); |
||||
|
VisualChildren.Add(_fullscreenPopover); |
||||
|
|
||||
|
// Always create resize grips; visibility is controlled by EnabledParts
|
||||
|
_resizeGrips = new ResizeGripLayer(); |
||||
|
_resizeGrips.IsVisible = parts.HasFlag(DrawnWindowDecorationParts.ResizeGrips); |
||||
|
VisualChildren.Add(_resizeGrips); |
||||
|
|
||||
|
// Attach to window if available
|
||||
|
if (_topLevel is Window window) |
||||
|
_decorations.Attach(window); |
||||
|
|
||||
|
// Subscribe to template changes to re-apply and geometry changes for resize grips
|
||||
|
_decorations.EffectiveGeometryChanged += OnDecorationsGeometryChanged; |
||||
|
_decorationsSubscriptions = _decorations.GetObservable(WindowDrawnDecorations.TemplateProperty) |
||||
|
.Subscribe(_ => ApplyDecorationsTemplate()); |
||||
|
|
||||
|
ApplyDecorationsTemplate(); |
||||
|
InvalidateMeasure(); |
||||
|
_decorationsOverlayPeer?.InvalidateChildren(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Disables drawn window decorations and removes all layers.
|
||||
|
/// </summary>
|
||||
|
internal void DisableDecorations() |
||||
|
{ |
||||
|
if (_decorations == null) |
||||
|
return; |
||||
|
|
||||
|
_decorationsSubscriptions?.Dispose(); |
||||
|
_decorationsSubscriptions = null; |
||||
|
|
||||
|
_decorations.EffectiveGeometryChanged -= OnDecorationsGeometryChanged; |
||||
|
_decorations.Detach(); |
||||
|
|
||||
|
// Remove layers
|
||||
|
if (_underlay != null) |
||||
|
{ |
||||
|
VisualChildren.Remove(_underlay); |
||||
|
_underlay = null; |
||||
|
} |
||||
|
if (_overlay != null) |
||||
|
{ |
||||
|
VisualChildren.Remove(_overlay); |
||||
|
_overlay = null; |
||||
|
} |
||||
|
if (_fullscreenPopover != null) |
||||
|
{ |
||||
|
VisualChildren.Remove(_fullscreenPopover); |
||||
|
_fullscreenPopover = null; |
||||
|
} |
||||
|
if (_resizeGrips != null) |
||||
|
{ |
||||
|
VisualChildren.Remove(_resizeGrips); |
||||
|
_resizeGrips = null; |
||||
|
} |
||||
|
|
||||
|
// Clean up logical tree
|
||||
|
LogicalChildren.Remove(_decorations); |
||||
|
_decorations = null; |
||||
|
_decorationsOverlayPeer?.InvalidateChildren(); |
||||
|
} |
||||
|
|
||||
|
private void ApplyDecorationsTemplate() |
||||
|
{ |
||||
|
if (_decorations == null) |
||||
|
return; |
||||
|
|
||||
|
_decorations.ApplyTemplate(); |
||||
|
|
||||
|
var content = _decorations.Content; |
||||
|
if (_underlay != null) |
||||
|
_underlay.Inner = content?.Underlay; |
||||
|
if (_overlay != null) |
||||
|
_overlay.Inner = content?.Overlay; |
||||
|
if (_fullscreenPopover != null) |
||||
|
_fullscreenPopover.Inner = content?.FullscreenPopover; |
||||
|
|
||||
|
UpdateResizeGripThickness(); |
||||
|
} |
||||
|
|
||||
|
private void UpdateResizeGripThickness() |
||||
|
{ |
||||
|
if (_resizeGrips == null || _decorations == null) |
||||
|
return; |
||||
|
|
||||
|
var frame = _decorations.FrameThickness; |
||||
|
var shadow = _decorations.ShadowThickness; |
||||
|
// Grips strictly cover frame + shadow area, never client area
|
||||
|
_resizeGrips.GripThickness = new Thickness( |
||||
|
frame.Left + shadow.Left, |
||||
|
frame.Top + shadow.Top, |
||||
|
frame.Right + shadow.Right, |
||||
|
frame.Bottom + shadow.Bottom); |
||||
|
} |
||||
|
|
||||
|
internal void UpdateResizeGrips() |
||||
|
{ |
||||
|
UpdateResizeGripThickness(); |
||||
|
} |
||||
|
|
||||
|
private void OnDecorationsGeometryChanged() |
||||
|
{ |
||||
|
UpdateResizeGripThickness(); |
||||
|
|
||||
|
// Notify Window to update margins
|
||||
|
if (_topLevel is Window window) |
||||
|
window.OnDrawnDecorationsGeometryChanged(); |
||||
|
} |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Shows or hides the fullscreen popover based on the window state.
|
||||
|
/// Called by Window when window state changes.
|
||||
|
/// </summary>
|
||||
|
internal void SetFullscreenPopoverEnabled(bool enabled) |
||||
|
{ |
||||
|
if (_fullscreenPopover == null) |
||||
|
return; |
||||
|
|
||||
|
if (enabled) |
||||
|
{ |
||||
|
// In fullscreen mode, hide overlay and underlay, enable popover hover detection
|
||||
|
if (_overlay != null) |
||||
|
_overlay.IsVisible = false; |
||||
|
if (_underlay != null) |
||||
|
_underlay.IsVisible = false; |
||||
|
// Popover starts hidden, will show on hover at top edge
|
||||
|
_fullscreenPopover.IsVisible = false; |
||||
|
_fullscreenPopoverEnabled = true; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
// Not fullscreen: show overlay and underlay, hide popover
|
||||
|
if (_overlay != null) |
||||
|
_overlay.IsVisible = true; |
||||
|
if (_underlay != null) |
||||
|
_underlay.IsVisible = true; |
||||
|
_fullscreenPopover.IsVisible = false; |
||||
|
_fullscreenPopoverEnabled = false; |
||||
|
} |
||||
|
_decorationsOverlayPeer?.InvalidateChildren(); |
||||
|
} |
||||
|
|
||||
|
private bool _fullscreenPopoverEnabled; |
||||
|
private const double PopoverTriggerZoneHeight = 1; |
||||
|
|
||||
|
protected override void OnPointerMoved(PointerEventArgs e) |
||||
|
{ |
||||
|
base.OnPointerMoved(e); |
||||
|
|
||||
|
if (_fullscreenPopoverEnabled && _fullscreenPopover != null) |
||||
|
{ |
||||
|
var pos = e.GetPosition(this); |
||||
|
// Use DefaultTitleBarHeight since TitleBarHeight is 0 in fullscreen
|
||||
|
var titleBarHeight = _decorations?.DefaultTitleBarHeight ?? 30; |
||||
|
|
||||
|
if (!_fullscreenPopover.IsVisible && pos.Y <= PopoverTriggerZoneHeight) |
||||
|
{ |
||||
|
_fullscreenPopover.IsVisible = true; |
||||
|
_decorationsOverlayPeer?.InvalidateChildren(); |
||||
|
} |
||||
|
else if (pos.Y > titleBarHeight && _fullscreenPopover.IsVisible) |
||||
|
{ |
||||
|
_fullscreenPopover.IsVisible = false; |
||||
|
_decorationsOverlayPeer?.InvalidateChildren(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,98 @@ |
|||||
|
using System.Collections.Generic; |
||||
|
using System.Linq; |
||||
|
using Avalonia.Automation; |
||||
|
using Avalonia.Automation.Peers; |
||||
|
|
||||
|
namespace Avalonia.Controls; |
||||
|
|
||||
|
partial class TopLevelHost |
||||
|
{ |
||||
|
|
||||
|
protected override AutomationPeer OnCreateAutomationPeer() |
||||
|
=> new TopLevelHostAutomationPeer(this); |
||||
|
|
||||
|
private DecorationsOverlaysAutomationPeer? _decorationsOverlayPeer; |
||||
|
|
||||
|
public AutomationPeer GetOrCreateDecorationsOverlaysPeer() => |
||||
|
_decorationsOverlayPeer ??= new DecorationsOverlaysAutomationPeer(this, _topLevel); |
||||
|
|
||||
|
/// <summary>
|
||||
|
/// Automation peer that returns no children. The automation tree is managed
|
||||
|
/// by WindowAutomationPeer, which directly includes decoration content.
|
||||
|
/// Without this, EnsureConnected would walk up through TopLevelHost and
|
||||
|
/// set Window's parent peer to TopLevelHost's peer, breaking the root.
|
||||
|
/// </summary>
|
||||
|
private class TopLevelHostAutomationPeer(TopLevelHost owner) : ControlAutomationPeer(owner) |
||||
|
{ |
||||
|
protected override IReadOnlyList<AutomationPeer>? GetChildrenCore() => null; |
||||
|
} |
||||
|
|
||||
|
private class DecorationsOverlaysAutomationPeer(TopLevelHost host, TopLevel topLevel) : AutomationPeer |
||||
|
{ |
||||
|
private List<AutomationPeer> _children = new(); |
||||
|
private bool _childrenValid = false; |
||||
|
protected override void BringIntoViewCore() => topLevel.GetOrCreateAutomationPeer().BringIntoView(); |
||||
|
|
||||
|
protected override string? GetAcceleratorKeyCore() => null; |
||||
|
|
||||
|
protected override string? GetAccessKeyCore() => null; |
||||
|
|
||||
|
protected override AutomationControlType GetAutomationControlTypeCore() => AutomationControlType.Group; |
||||
|
|
||||
|
protected override string? GetAutomationIdCore() => "AvaloniaWindowChrome"; |
||||
|
|
||||
|
protected override Rect GetBoundingRectangleCore() => host.Bounds; |
||||
|
|
||||
|
protected override IReadOnlyList<AutomationPeer> GetOrCreateChildrenCore() |
||||
|
{ |
||||
|
if (!_childrenValid) |
||||
|
{ |
||||
|
var newChildren = (new LayerWrapper?[] { host._fullscreenPopover, host._overlay, host._underlay }) |
||||
|
.Where(c => c?.IsVisible == true) |
||||
|
.Select(c => c!.GetOrCreateAutomationPeer()).ToList(); |
||||
|
|
||||
|
foreach (var peer in _children.Except(newChildren)) |
||||
|
peer.TrySetParent(null); |
||||
|
foreach (var peer in newChildren) |
||||
|
peer.TrySetParent(this); |
||||
|
_children = newChildren; |
||||
|
_childrenValid = true; |
||||
|
} |
||||
|
|
||||
|
return _children; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
public void InvalidateChildren() |
||||
|
{ |
||||
|
if (_childrenValid) |
||||
|
{ |
||||
|
_childrenValid = false; |
||||
|
RaiseChildrenChangedEvent(); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
protected override string GetClassNameCore() => "WindowChrome"; |
||||
|
|
||||
|
protected override AutomationPeer? GetLabeledByCore() => null; |
||||
|
|
||||
|
protected override string? GetNameCore() => "WindowChrome"; |
||||
|
|
||||
|
protected override AutomationPeer? GetParentCore() => topLevel.GetOrCreateAutomationPeer(); |
||||
|
|
||||
|
protected override bool HasKeyboardFocusCore() => _children?.Any(x => x.HasKeyboardFocus()) == true; |
||||
|
protected override bool IsKeyboardFocusableCore() => false; |
||||
|
|
||||
|
protected override bool IsContentElementCore() => false; |
||||
|
|
||||
|
protected override bool IsControlElementCore() => true; |
||||
|
|
||||
|
protected override bool IsEnabledCore() => true; |
||||
|
|
||||
|
protected override void SetFocusCore(){} |
||||
|
|
||||
|
protected override bool ShowContextMenuCore() => false; |
||||
|
|
||||
|
protected internal override bool TrySetParent(AutomationPeer? parent) => false; |
||||
|
} |
||||
|
} |
||||
@ -1,119 +0,0 @@ |
|||||
<ResourceDictionary xmlns="https://github.com/avaloniaui" |
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|
||||
x:ClassModifier="internal"> |
|
||||
|
|
||||
<x:Double x:Key="CaptionButtonWidth">45</x:Double> |
|
||||
<x:Double x:Key="CaptionButtonHeight">30</x:Double> |
|
||||
|
|
||||
<Design.PreviewWith> |
|
||||
<Border Padding="20"> |
|
||||
<StackPanel Spacing="20"> |
|
||||
<ThemeVariantScope RequestedThemeVariant="Dark"> |
|
||||
<Border Background="Black"> |
|
||||
<CaptionButtons Height="30"/> |
|
||||
</Border> |
|
||||
</ThemeVariantScope> |
|
||||
<ThemeVariantScope RequestedThemeVariant="Light"> |
|
||||
<CaptionButtons Height="30"/> |
|
||||
</ThemeVariantScope> |
|
||||
</StackPanel> |
|
||||
</Border> |
|
||||
</Design.PreviewWith> |
|
||||
|
|
||||
<ControlTheme x:Key="FluentCaptionButton" TargetType="Button"> |
|
||||
<Setter Property="Background" Value="{DynamicResource CaptionButtonBackground}" /> |
|
||||
<!-- Reusing BorderBrush to define pressed background color, as it's not used otherwise --> |
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource CaptionButtonBorderBrush}" /> |
|
||||
<Setter Property="Foreground" Value="{DynamicResource CaptionButtonForeground}"/> |
|
||||
<Setter Property="Width" Value="{DynamicResource CaptionButtonWidth}"/> |
|
||||
<Setter Property="Height" Value="{DynamicResource CaptionButtonHeight}"/> |
|
||||
<Setter Property="VerticalAlignment" Value="Stretch"/> |
|
||||
<Setter Property="Template"> |
|
||||
<ControlTemplate> |
|
||||
<ContentPresenter Name="PART_ContentPresenter" |
|
||||
Background="Transparent" |
|
||||
Content="{TemplateBinding Content}"/> |
|
||||
</ControlTemplate> |
|
||||
</Setter> |
|
||||
|
|
||||
<Style Selector="^:pointerover /template/ ContentPresenter"> |
|
||||
<Setter Property="Background" Value="{TemplateBinding Background}" /> |
|
||||
</Style> |
|
||||
|
|
||||
<Style Selector="^:pressed /template/ ContentPresenter"> |
|
||||
<Setter Property="Background" Value="{TemplateBinding BorderBrush}" /> |
|
||||
</Style> |
|
||||
</ControlTheme> |
|
||||
|
|
||||
<ControlTheme x:Key="{x:Type CaptionButtons}" TargetType="CaptionButtons"> |
|
||||
<Setter Property="MaxHeight" Value="30" /> |
|
||||
<Setter Property="Template"> |
|
||||
<ControlTemplate> |
|
||||
<StackPanel Spacing="2" VerticalAlignment="Stretch" TextElement.FontSize="10" Orientation="Horizontal"> |
|
||||
<Button x:Name="PART_FullScreenButton" |
|
||||
Theme="{StaticResource FluentCaptionButton}" |
|
||||
IsVisible="False"> |
|
||||
<Viewbox Width="11" Margin="2"> |
|
||||
<Path Name="FullScreenButtonPath" |
|
||||
Stretch="UniformToFill" |
|
||||
Fill="{TemplateBinding Foreground}" |
|
||||
Data="M2048 2048v-819h-205v469l-1493 -1493h469v-205h-819v819h205v-469l1493 1493h-469v205h819z" /> |
|
||||
</Viewbox> |
|
||||
</Button> |
|
||||
<Button x:Name="PART_MinimizeButton" |
|
||||
Theme="{StaticResource FluentCaptionButton}" |
|
||||
AutomationProperties.Name="Minimize" |
|
||||
Win32Properties.NonClientHitTestResult="MinButton"> |
|
||||
<Viewbox Width="11" Margin="2"> |
|
||||
<Path Stretch="UniformToFill" |
|
||||
Fill="{TemplateBinding Foreground}" |
|
||||
Data="M2048 1229v-205h-2048v205h2048z" /> |
|
||||
</Viewbox> |
|
||||
</Button> |
|
||||
<Button x:Name="PART_RestoreButton" |
|
||||
Theme="{StaticResource FluentCaptionButton}" |
|
||||
AutomationProperties.Name="Maximize" |
|
||||
Win32Properties.NonClientHitTestResult="MaxButton"> |
|
||||
<Viewbox Width="11" Margin="2"> |
|
||||
<Viewbox.RenderTransform> |
|
||||
<RotateTransform Angle="-90" /> |
|
||||
</Viewbox.RenderTransform> |
|
||||
<Path Name="RestoreButtonPath" |
|
||||
Stretch="UniformToFill" |
|
||||
Fill="{TemplateBinding Foreground}" |
|
||||
Data="M2048 2048v-2048h-2048v2048h2048zM1843 1843h-1638v-1638h1638v1638z"/> |
|
||||
</Viewbox> |
|
||||
</Button> |
|
||||
<Button x:Name="PART_CloseButton" |
|
||||
Background="#ffe81123" |
|
||||
BorderBrush="#fff1707a" |
|
||||
Theme="{StaticResource FluentCaptionButton}" |
|
||||
AutomationProperties.Name="Close" |
|
||||
Win32Properties.NonClientHitTestResult="Close"> |
|
||||
<Viewbox Width="11" Margin="2"> |
|
||||
<Path Stretch="UniformToFill" |
|
||||
Fill="{TemplateBinding Foreground}" |
|
||||
Data="M1169 1024l879 -879l-145 -145l-879 879l-879 -879l-145 145l879 879l-879 879l145 145l879 -879l879 879l145 -145z" /> |
|
||||
</Viewbox> |
|
||||
</Button> |
|
||||
</StackPanel> |
|
||||
</ControlTemplate> |
|
||||
</Setter> |
|
||||
<Style Selector="^:maximized /template/ Path#RestoreButtonPath"> |
|
||||
<Setter Property="Data" Value="M2048 410h-410v-410h-1638v1638h410v410h1638v-1638zM1434 1434h-1229v-1229h1229v1229zM1843 1843h-1229v-205h1024v-1024h205v1229z" /> |
|
||||
</Style> |
|
||||
<Style Selector="^:fullscreen /template/ Path#FullScreenButtonPath"> |
|
||||
<Setter Property="IsVisible" Value="True" /> |
|
||||
<Setter Property="Data" Value="M205 1024h819v-819h-205v469l-674 -674l-145 145l674 674h-469v205zM1374 1229h469v-205h-819v819h205v-469l674 674l145 -145z" /> |
|
||||
</Style> |
|
||||
<Style Selector="^:fullscreen /template/ Button#PART_RestoreButton"> |
|
||||
<Setter Property="IsVisible" Value="False" /> |
|
||||
</Style> |
|
||||
<Style Selector="^:fullscreen /template/ Button#PART_MinimizeButton"> |
|
||||
<Setter Property="IsVisible" Value="False" /> |
|
||||
</Style> |
|
||||
<Style Selector="^ /template/ Button:disabled"> |
|
||||
<Setter Property="Opacity" Value="0.2"/> |
|
||||
</Style> |
|
||||
</ControlTheme> |
|
||||
</ResourceDictionary> |
|
||||
@ -1,62 +0,0 @@ |
|||||
<ResourceDictionary xmlns="https://github.com/avaloniaui" |
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|
||||
x:ClassModifier="internal"> |
|
||||
<Design.PreviewWith> |
|
||||
<Border Height="30" Width="300"> |
|
||||
<TitleBar Background="SkyBlue" Foreground="Black" /> |
|
||||
</Border> |
|
||||
</Design.PreviewWith> |
|
||||
|
|
||||
<ControlTheme x:Key="{x:Type TitleBar}" TargetType="TitleBar"> |
|
||||
<Setter Property="Foreground" Value="{DynamicResource SystemControlForegroundBaseHighBrush}" /> |
|
||||
<Setter Property="VerticalAlignment" Value="Top" /> |
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" /> |
|
||||
<Setter Property="Background" Value="Transparent" /> |
|
||||
<Setter Property="Template"> |
|
||||
<ControlTemplate> |
|
||||
<Panel HorizontalAlignment="{TemplateBinding HorizontalAlignment}" |
|
||||
VerticalAlignment="Stretch"> |
|
||||
<Panel x:Name="PART_MouseTracker" |
|
||||
Height="1" |
|
||||
VerticalAlignment="Top" /> |
|
||||
<Panel x:Name="PART_Container"> |
|
||||
<Border x:Name="PART_Background" |
|
||||
Background="{TemplateBinding Background}" |
|
||||
IsHitTestVisible="False" |
|
||||
Win32Properties.NonClientHitTestResult="Caption" /> |
|
||||
<CaptionButtons x:Name="PART_CaptionButtons" |
|
||||
VerticalAlignment="Top" |
|
||||
HorizontalAlignment="Right" |
|
||||
Foreground="{TemplateBinding Foreground}" |
|
||||
Win32Properties.NonClientHitTestResult="Client" /> |
|
||||
</Panel> |
|
||||
</Panel> |
|
||||
</ControlTemplate> |
|
||||
</Setter> |
|
||||
|
|
||||
<Style Selector="^:fullscreen"> |
|
||||
<Setter Property="Background" Value="{DynamicResource SystemAccentColor}" /> |
|
||||
</Style> |
|
||||
|
|
||||
<Style Selector="^:fullscreen /template/ Border#PART_Background"> |
|
||||
<Setter Property="IsHitTestVisible" Value="True" /> |
|
||||
</Style> |
|
||||
|
|
||||
<Style Selector="^:fullscreen /template/ Panel#PART_MouseTracker"> |
|
||||
<Setter Property="Background" Value="Transparent" /> |
|
||||
</Style> |
|
||||
|
|
||||
<Style Selector="^:fullscreen /template/ Panel#PART_Container"> |
|
||||
<Setter Property="RenderTransform" Value="translateY(-30px)" /> |
|
||||
<Setter Property="Transitions"> |
|
||||
<Transitions> |
|
||||
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:.25" /> |
|
||||
</Transitions> |
|
||||
</Setter> |
|
||||
</Style> |
|
||||
|
|
||||
<Style Selector="^:fullscreen:pointerover /template/ Panel#PART_Container"> |
|
||||
<Setter Property="RenderTransform" Value="none" /> |
|
||||
</Style> |
|
||||
</ControlTheme> |
|
||||
</ResourceDictionary> |
|
||||
@ -0,0 +1,215 @@ |
|||||
|
<ResourceDictionary xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
x:ClassModifier="internal"> |
||||
|
|
||||
|
<x:Double x:Key="CaptionButtonWidth">45</x:Double> |
||||
|
<x:Double x:Key="CaptionButtonHeight">30</x:Double> |
||||
|
|
||||
|
<ControlTheme x:Key="FluentDrawnCaptionButton" TargetType="Button"> |
||||
|
<Setter Property="Background" Value="{DynamicResource CaptionButtonBackground}" /> |
||||
|
<Setter Property="BorderBrush" Value="{DynamicResource CaptionButtonBorderBrush}" /> |
||||
|
<Setter Property="Foreground" Value="{DynamicResource CaptionButtonForeground}"/> |
||||
|
<Setter Property="Width" Value="{DynamicResource CaptionButtonWidth}"/> |
||||
|
<Setter Property="Height" Value="{DynamicResource CaptionButtonHeight}"/> |
||||
|
<Setter Property="VerticalAlignment" Value="Stretch"/> |
||||
|
<Setter Property="(WindowDecorationProperties.ElementRole)" Value="DecorationsElement"/> |
||||
|
<Setter Property="Template"> |
||||
|
<ControlTemplate> |
||||
|
<ContentPresenter Name="PART_ContentPresenter" |
||||
|
Background="Transparent" |
||||
|
Content="{TemplateBinding Content}"/> |
||||
|
</ControlTemplate> |
||||
|
</Setter> |
||||
|
|
||||
|
<Style Selector="^:pointerover /template/ ContentPresenter"> |
||||
|
<Setter Property="Background" Value="{TemplateBinding Background}" /> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="^:pressed /template/ ContentPresenter"> |
||||
|
<Setter Property="Background" Value="{TemplateBinding BorderBrush}" /> |
||||
|
</Style> |
||||
|
</ControlTheme> |
||||
|
|
||||
|
<ControlTheme x:Key="{x:Type WindowDrawnDecorations}" TargetType="WindowDrawnDecorations"> |
||||
|
<Setter Property="DefaultTitleBarHeight" Value="30"/> |
||||
|
<Setter Property="DefaultFrameThickness" Value="1"/> |
||||
|
<Setter Property="DefaultShadowThickness" Value="8"/> |
||||
|
<Setter Property="Template"> |
||||
|
<WindowDrawnDecorationsTemplate> |
||||
|
<WindowDrawnDecorationsContent> |
||||
|
|
||||
|
<WindowDrawnDecorationsContent.Underlay> |
||||
|
<Panel x:Name="PART_UnderlayWrapper"> |
||||
|
<Border x:Name="PART_WindowBorder" |
||||
|
Background="{DynamicResource SystemControlBackgroundAltHighBrush}" |
||||
|
BorderThickness="{TemplateBinding FrameThickness}" |
||||
|
BorderBrush="{DynamicResource SystemControlForegroundBaseMediumBrush}" |
||||
|
IsHitTestVisible="False" /> |
||||
|
<!-- Titlebar: background, title text, and drag area live in underlay --> |
||||
|
<Panel x:Name="PART_TitleBar" VerticalAlignment="Top" |
||||
|
Height="{TemplateBinding TitleBarHeight}" |
||||
|
Background="{DynamicResource SystemControlBackgroundAltHighBrush}" |
||||
|
IsVisible="{TemplateBinding HasTitleBar}" |
||||
|
WindowDecorationProperties.ElementRole="TitleBar" /> |
||||
|
|
||||
|
</Panel> |
||||
|
</WindowDrawnDecorationsContent.Underlay> |
||||
|
|
||||
|
<WindowDrawnDecorationsContent.Overlay> |
||||
|
<!-- Overlay: only interactive caption buttons --> |
||||
|
<Panel x:Name="PART_OverlayWrapper"> |
||||
|
<!-- Title text lives in overlay so it renders above client content --> |
||||
|
<Panel x:Name="PART_TitleTextPanel" VerticalAlignment="Top" |
||||
|
Height="{TemplateBinding TitleBarHeight}" |
||||
|
IsHitTestVisible="False" |
||||
|
IsVisible="{TemplateBinding HasTitleBar}"> |
||||
|
<TextBlock Text="{TemplateBinding Title}" |
||||
|
VerticalAlignment="Center" |
||||
|
Margin="12,0,0,0" |
||||
|
FontSize="12" /> |
||||
|
</Panel> |
||||
|
<StackPanel x:Name="PART_OverlayPanel" |
||||
|
VerticalAlignment="Top" |
||||
|
HorizontalAlignment="Right" |
||||
|
Height="{TemplateBinding TitleBarHeight}" |
||||
|
IsVisible="{TemplateBinding HasTitleBar}" |
||||
|
Orientation="Horizontal" |
||||
|
Spacing="2" |
||||
|
TextElement.FontSize="10"> |
||||
|
<Button x:Name="PART_FullScreenButton" |
||||
|
Theme="{StaticResource FluentDrawnCaptionButton}" |
||||
|
WindowDecorationProperties.ElementRole="FullScreenButton"> |
||||
|
<Viewbox Width="11" Margin="2"> |
||||
|
<Path Name="FullScreenButtonPath" |
||||
|
Stretch="UniformToFill" |
||||
|
Fill="{DynamicResource CaptionButtonForeground}" |
||||
|
Data="M2048 2048v-819h-205v469l-1493 -1493h469v-205h-819v819h205v-469l1493 1493h-469v205h819z" /> |
||||
|
</Viewbox> |
||||
|
</Button> |
||||
|
<Button x:Name="PART_MinimizeButton" |
||||
|
Theme="{StaticResource FluentDrawnCaptionButton}" |
||||
|
WindowDecorationProperties.ElementRole="MinimizeButton"> |
||||
|
<Viewbox Width="11" Margin="2"> |
||||
|
<Path Stretch="UniformToFill" |
||||
|
Fill="{DynamicResource CaptionButtonForeground}" |
||||
|
Data="M2048 1229v-205h-2048v205h2048z" /> |
||||
|
</Viewbox> |
||||
|
</Button> |
||||
|
<Button x:Name="PART_MaximizeButton" |
||||
|
Theme="{StaticResource FluentDrawnCaptionButton}" |
||||
|
WindowDecorationProperties.ElementRole="MaximizeButton"> |
||||
|
<Viewbox Width="11" Margin="2"> |
||||
|
<Viewbox.RenderTransform> |
||||
|
<RotateTransform Angle="-90" /> |
||||
|
</Viewbox.RenderTransform> |
||||
|
<Path Name="RestoreButtonPath" |
||||
|
Stretch="UniformToFill" |
||||
|
Fill="{DynamicResource CaptionButtonForeground}" |
||||
|
Data="M2048 2048v-2048h-2048v2048h2048zM1843 1843h-1638v-1638h1638v1638z"/> |
||||
|
</Viewbox> |
||||
|
</Button> |
||||
|
<Button x:Name="PART_CloseButton" |
||||
|
Background="#ffe81123" |
||||
|
BorderBrush="#fff1707a" |
||||
|
Theme="{StaticResource FluentDrawnCaptionButton}" |
||||
|
WindowDecorationProperties.ElementRole="CloseButton"> |
||||
|
<Viewbox Width="11" Margin="2"> |
||||
|
<Path Stretch="UniformToFill" |
||||
|
Fill="{DynamicResource CaptionButtonForeground}" |
||||
|
Data="M1169 1024l879 -879l-145 -145l-879 879l-879 -879l-145 145l879 879l-879 879l145 145l879 -879l879 879l145 -145z" /> |
||||
|
</Viewbox> |
||||
|
</Button> |
||||
|
</StackPanel> |
||||
|
</Panel> |
||||
|
</WindowDrawnDecorationsContent.Overlay> |
||||
|
|
||||
|
<WindowDrawnDecorationsContent.FullscreenPopover> |
||||
|
<Panel Height="{TemplateBinding DefaultTitleBarHeight}" |
||||
|
VerticalAlignment="Top" |
||||
|
Background="{DynamicResource SystemControlBackgroundChromeMediumLowBrush}" |
||||
|
WindowDecorationProperties.ElementRole="TitleBar"> |
||||
|
<TextBlock Text="{TemplateBinding Title}" |
||||
|
VerticalAlignment="Center" |
||||
|
Margin="12,0,0,0" |
||||
|
FontSize="12" /> |
||||
|
<StackPanel HorizontalAlignment="Right" |
||||
|
Orientation="Horizontal" |
||||
|
Spacing="2" |
||||
|
TextElement.FontSize="10"> |
||||
|
<Button x:Name="PART_PopoverFullScreenButton" |
||||
|
Theme="{StaticResource FluentDrawnCaptionButton}" |
||||
|
WindowDecorationProperties.ElementRole="FullScreenButton"> |
||||
|
<Viewbox Width="11" Margin="2"> |
||||
|
<Path Stretch="UniformToFill" |
||||
|
Fill="{DynamicResource CaptionButtonForeground}" |
||||
|
Data="M205 1024h819v-819h-205v469l-674 -674l-145 145l674 674h-469v205zM1374 1229h469v-205h-819v819h205v-469l674 674l145 -145z" /> |
||||
|
</Viewbox> |
||||
|
</Button> |
||||
|
<Button x:Name="PART_PopoverCloseButton" |
||||
|
Background="#ffe81123" |
||||
|
BorderBrush="#fff1707a" |
||||
|
Theme="{StaticResource FluentDrawnCaptionButton}" |
||||
|
WindowDecorationProperties.ElementRole="CloseButton"> |
||||
|
<Viewbox Width="11" Margin="2"> |
||||
|
<Path Stretch="UniformToFill" |
||||
|
Fill="{DynamicResource CaptionButtonForeground}" |
||||
|
Data="M1169 1024l879 -879l-145 -145l-879 879l-879 -879l-145 145l879 879l-879 879l145 145l879 -879l879 879l145 -145z" /> |
||||
|
</Viewbox> |
||||
|
</Button> |
||||
|
</StackPanel> |
||||
|
</Panel> |
||||
|
</WindowDrawnDecorationsContent.FullscreenPopover> |
||||
|
|
||||
|
</WindowDrawnDecorationsContent> |
||||
|
</WindowDrawnDecorationsTemplate> |
||||
|
</Setter> |
||||
|
|
||||
|
<!-- Shadow: inset border and add drop shadow --> |
||||
|
<Style Selector="^:has-shadow /template/ Panel#PART_UnderlayWrapper"> |
||||
|
<Setter Property="Margin" Value="{TemplateBinding ShadowThickness}"/> |
||||
|
</Style> |
||||
|
<Style Selector="^:has-shadow /template/ Border#PART_WindowBorder"> |
||||
|
<Setter Property="BoxShadow" Value="0 2 10 2 #80000000"/> |
||||
|
</Style> |
||||
|
<Style Selector="^:has-shadow /template/ Panel#PART_OverlayWrapper"> |
||||
|
<Setter Property="Margin" Value="{TemplateBinding ShadowThickness}"/> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- Border: inset titlebar and buttons inside frame --> |
||||
|
<Style Selector="^:has-border /template/ Panel#PART_TitleTextPanel"> |
||||
|
<Setter Property="Margin" Value="1,1,1,0"/> |
||||
|
</Style> |
||||
|
<Style Selector="^:has-border /template/ Panel#PART_TitleBar"> |
||||
|
<Setter Property="Margin" Value="1,1,1,0"/> |
||||
|
</Style> |
||||
|
<Style Selector="^:has-border /template/ StackPanel#PART_OverlayPanel"> |
||||
|
<Setter Property="Margin" Value="0,1,1,0"/> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- Maximized: restore button path changes --> |
||||
|
<Style Selector="^:maximized /template/ Path#RestoreButtonPath"> |
||||
|
<Setter Property="Data" Value="M2048 410h-410v-410h-1638v1638h410v410h1638v-1638zM1434 1434h-1229v-1229h1229v1229zM1843 1843h-1229v-205h1024v-1024h205v1229z" /> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- Fullscreen: update fullscreen button icon to "exit fullscreen" --> |
||||
|
<Style Selector="^:fullscreen /template/ Path#FullScreenButtonPath"> |
||||
|
<Setter Property="Data" Value="M205 1024h819v-819h-205v469l-674 -674l-145 145l674 674h-469v205zM1374 1229h469v-205h-819v819h205v-469l674 674l145 -145z" /> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- Disabled buttons --> |
||||
|
<Style Selector="^ /template/ Button:disabled"> |
||||
|
<Setter Property="Opacity" Value="0.2"/> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- Fullscreen: hide overlay and titlebar (popover takes over) --> |
||||
|
<Style Selector="^:fullscreen /template/ Panel#PART_TitleTextPanel"> |
||||
|
<Setter Property="IsVisible" Value="False" /> |
||||
|
</Style> |
||||
|
<Style Selector="^:fullscreen /template/ StackPanel#PART_OverlayPanel"> |
||||
|
<Setter Property="IsVisible" Value="False" /> |
||||
|
</Style> |
||||
|
<Style Selector="^:fullscreen /template/ Panel#PART_TitleBar"> |
||||
|
<Setter Property="IsVisible" Value="False" /> |
||||
|
</Style> |
||||
|
</ControlTheme> |
||||
|
</ResourceDictionary> |
||||
@ -1,126 +0,0 @@ |
|||||
<ResourceDictionary xmlns="https://github.com/avaloniaui" |
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|
||||
x:ClassModifier="internal"> |
|
||||
<Design.PreviewWith> |
|
||||
<Border Padding="20"> |
|
||||
<StackPanel Spacing="20"> |
|
||||
<ThemeVariantScope RequestedThemeVariant="Dark"> |
|
||||
<Border Background="Black"> |
|
||||
<CaptionButtons Height="30"/> |
|
||||
</Border> |
|
||||
</ThemeVariantScope> |
|
||||
<ThemeVariantScope RequestedThemeVariant="Light"> |
|
||||
<CaptionButtons Height="30"/> |
|
||||
</ThemeVariantScope> |
|
||||
</StackPanel> |
|
||||
</Border> |
|
||||
</Design.PreviewWith> |
|
||||
|
|
||||
<x:Double x:Key="CaptionButtonWidth">45</x:Double> |
|
||||
<x:Double x:Key="CaptionButtonHeight">30</x:Double> |
|
||||
|
|
||||
<ControlTheme x:Key="SimpleCaptionButton" |
|
||||
TargetType="Button"> |
|
||||
<Setter Property="Background" Value="{DynamicResource CaptionButtonBackground}" /> |
|
||||
<!-- Reusing BorderBrush to define pressed background color, as it's not used otherwise --> |
|
||||
<Setter Property="BorderBrush" Value="{DynamicResource CaptionButtonBorderBrush}" /> |
|
||||
<Setter Property="Foreground" Value="{DynamicResource CaptionButtonForeground}"/> <Setter Property="Width" Value="{DynamicResource CaptionButtonWidth}"/> |
|
||||
<Setter Property="Height" Value="{DynamicResource CaptionButtonHeight}"/> |
|
||||
<Setter Property="VerticalAlignment" Value="Stretch" /> |
|
||||
<Setter Property="Template"> |
|
||||
<ControlTemplate> |
|
||||
<ContentPresenter Name="PART_ContentPresenter" |
|
||||
Background="Transparent" |
|
||||
Content="{TemplateBinding Content}" /> |
|
||||
</ControlTemplate> |
|
||||
</Setter> |
|
||||
|
|
||||
<Style Selector="^:pointerover /template/ ContentPresenter"> |
|
||||
<Setter Property="Background" Value="{TemplateBinding Background}" /> |
|
||||
</Style> |
|
||||
|
|
||||
<Style Selector="^:pressed /template/ ContentPresenter"> |
|
||||
<Setter Property="Background" Value="{TemplateBinding BorderBrush}" /> |
|
||||
</Style> |
|
||||
</ControlTheme> |
|
||||
|
|
||||
<ControlTheme x:Key="{x:Type CaptionButtons}" |
|
||||
TargetType="CaptionButtons"> |
|
||||
<Setter Property="MaxHeight" Value="30" /> |
|
||||
<Setter Property="Template"> |
|
||||
<ControlTemplate> |
|
||||
<StackPanel VerticalAlignment="Stretch" |
|
||||
Orientation="Horizontal" |
|
||||
Spacing="2" |
|
||||
TextElement.FontSize="10"> |
|
||||
<Button x:Name="PART_FullScreenButton" |
|
||||
IsVisible="False" |
|
||||
Theme="{StaticResource SimpleCaptionButton}"> |
|
||||
<Viewbox Width="11" |
|
||||
Margin="2"> |
|
||||
<Path Name="FullScreenButtonPath" |
|
||||
Data="M2048 2048v-819h-205v469l-1493 -1493h469v-205h-819v819h205v-469l1493 1493h-469v205h819z" |
|
||||
Fill="{TemplateBinding Foreground}" |
|
||||
Stretch="UniformToFill" /> |
|
||||
</Viewbox> |
|
||||
</Button> |
|
||||
<Button x:Name="PART_MinimizeButton" |
|
||||
Theme="{StaticResource SimpleCaptionButton}" |
|
||||
AutomationProperties.Name="Minimize" |
|
||||
Win32Properties.NonClientHitTestResult="MinButton"> |
|
||||
<Viewbox Width="11" |
|
||||
Margin="2"> |
|
||||
<Path Data="M2048 1229v-205h-2048v205h2048z" |
|
||||
Fill="{TemplateBinding Foreground}" |
|
||||
Stretch="UniformToFill" /> |
|
||||
</Viewbox> |
|
||||
</Button> |
|
||||
<Button x:Name="PART_RestoreButton" |
|
||||
Theme="{StaticResource SimpleCaptionButton}" |
|
||||
AutomationProperties.Name="Maximize" |
|
||||
Win32Properties.NonClientHitTestResult="MaxButton"> |
|
||||
<Viewbox Width="11" |
|
||||
Margin="2"> |
|
||||
<Viewbox.RenderTransform> |
|
||||
<RotateTransform Angle="-90" /> |
|
||||
</Viewbox.RenderTransform> |
|
||||
<Path Name="RestoreButtonPath" |
|
||||
Data="M2048 2048v-2048h-2048v2048h2048zM1843 1843h-1638v-1638h1638v1638z" |
|
||||
Fill="{TemplateBinding Foreground}" |
|
||||
Stretch="UniformToFill" /> |
|
||||
</Viewbox> |
|
||||
</Button> |
|
||||
<Button x:Name="PART_CloseButton" |
|
||||
Background="#ffe81123" |
|
||||
BorderBrush="#fff1707a" |
|
||||
Theme="{StaticResource SimpleCaptionButton}" |
|
||||
AutomationProperties.Name="Close" |
|
||||
Win32Properties.NonClientHitTestResult="Close"> |
|
||||
<Viewbox Width="11" |
|
||||
Margin="2"> |
|
||||
<Path Data="M1169 1024l879 -879l-145 -145l-879 879l-879 -879l-145 145l879 879l-879 879l145 145l879 -879l879 879l145 -145z" |
|
||||
Fill="{TemplateBinding Foreground}" |
|
||||
Stretch="UniformToFill" /> |
|
||||
</Viewbox> |
|
||||
</Button> |
|
||||
</StackPanel> |
|
||||
</ControlTemplate> |
|
||||
</Setter> |
|
||||
<Style Selector="^:maximized /template/ Path#RestoreButtonPath"> |
|
||||
<Setter Property="Data" Value="M2048 410h-410v-410h-1638v1638h410v410h1638v-1638zM1434 1434h-1229v-1229h1229v1229zM1843 1843h-1229v-205h1024v-1024h205v1229z" /> |
|
||||
</Style> |
|
||||
<Style Selector="^:fullscreen /template/ Path#FullScreenButtonPath"> |
|
||||
<Setter Property="IsVisible" Value="True" /> |
|
||||
<Setter Property="Data" Value="M205 1024h819v-819h-205v469l-674 -674l-145 145l674 674h-469v205zM1374 1229h469v-205h-819v819h205v-469l674 674l145 -145z" /> |
|
||||
</Style> |
|
||||
<Style Selector="^:fullscreen /template/ Button#PART_RestoreButton"> |
|
||||
<Setter Property="IsVisible" Value="False" /> |
|
||||
</Style> |
|
||||
<Style Selector="^:fullscreen /template/ Button#PART_MinimizeButton"> |
|
||||
<Setter Property="IsVisible" Value="False" /> |
|
||||
</Style> |
|
||||
<Style Selector="^ /template/ Button#PART_RestoreButton:disabled"> |
|
||||
<Setter Property="Opacity" Value="0.2"/> |
|
||||
</Style> |
|
||||
</ControlTheme> |
|
||||
</ResourceDictionary> |
|
||||
@ -1,69 +0,0 @@ |
|||||
<ResourceDictionary xmlns="https://github.com/avaloniaui" |
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
|
||||
x:ClassModifier="internal"> |
|
||||
<Design.PreviewWith> |
|
||||
<Border> |
|
||||
<TitleBar Width="300" |
|
||||
Height="30" |
|
||||
Background="SkyBlue" |
|
||||
Foreground="Black" /> |
|
||||
</Border> |
|
||||
</Design.PreviewWith> |
|
||||
<ControlTheme x:Key="{x:Type TitleBar}" |
|
||||
TargetType="TitleBar"> |
|
||||
<Setter Property="Foreground" Value="{DynamicResource ThemeForegroundBrush}" /> |
|
||||
<Setter Property="VerticalAlignment" Value="Top" /> |
|
||||
<Setter Property="HorizontalAlignment" Value="Stretch" /> |
|
||||
<Setter Property="Background" Value="Transparent" /> |
|
||||
<Setter Property="Template"> |
|
||||
<ControlTemplate> |
|
||||
<Panel HorizontalAlignment="{TemplateBinding HorizontalAlignment}" |
|
||||
VerticalAlignment="Stretch"> |
|
||||
<Panel x:Name="PART_MouseTracker" |
|
||||
Height="1" |
|
||||
VerticalAlignment="Top" /> |
|
||||
<Panel x:Name="PART_Container"> |
|
||||
<Border x:Name="PART_Background" |
|
||||
Background="{TemplateBinding Background}" |
|
||||
IsHitTestVisible="False" |
|
||||
Win32Properties.NonClientHitTestResult="Caption" /> |
|
||||
<CaptionButtons x:Name="PART_CaptionButtons" |
|
||||
HorizontalAlignment="Right" |
|
||||
VerticalAlignment="Top" |
|
||||
Foreground="{TemplateBinding Foreground}" |
|
||||
Win32Properties.NonClientHitTestResult="Client" /> |
|
||||
</Panel> |
|
||||
</Panel> |
|
||||
</ControlTemplate> |
|
||||
</Setter> |
|
||||
<Style Selector="^:fullscreen"> |
|
||||
<Setter Property="Background" Value="{DynamicResource ThemeAccentColor}" /> |
|
||||
</Style> |
|
||||
|
|
||||
<Style Selector="^ /template/ Border#PART_Background"> |
|
||||
<Setter Property="IsHitTestVisible" Value="False" /> |
|
||||
</Style> |
|
||||
|
|
||||
<Style Selector="^:fullscreen /template/ Border#PART_Background"> |
|
||||
<Setter Property="IsHitTestVisible" Value="True" /> |
|
||||
</Style> |
|
||||
|
|
||||
<Style Selector="^:fullscreen /template/ Panel#PART_MouseTracker"> |
|
||||
<Setter Property="Background" Value="Transparent" /> |
|
||||
</Style> |
|
||||
|
|
||||
<Style Selector="^:fullscreen /template/ Panel#PART_Container"> |
|
||||
<Setter Property="RenderTransform" Value="translateY(-30px)" /> |
|
||||
<Setter Property="Transitions"> |
|
||||
<Transitions> |
|
||||
<TransformOperationsTransition Property="RenderTransform" |
|
||||
Duration="0:0:.25" /> |
|
||||
</Transitions> |
|
||||
</Setter> |
|
||||
</Style> |
|
||||
|
|
||||
<Style Selector="^:fullscreen:pointerover /template/ Panel#PART_Container"> |
|
||||
<Setter Property="RenderTransform" Value="none" /> |
|
||||
</Style> |
|
||||
</ControlTheme> |
|
||||
</ResourceDictionary> |
|
||||
@ -0,0 +1,221 @@ |
|||||
|
<ResourceDictionary xmlns="https://github.com/avaloniaui" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
x:ClassModifier="internal"> |
||||
|
|
||||
|
<x:Double x:Key="CaptionButtonWidth">45</x:Double> |
||||
|
<x:Double x:Key="CaptionButtonHeight">30</x:Double> |
||||
|
|
||||
|
<ControlTheme x:Key="SimpleDrawnCaptionButton" TargetType="Button"> |
||||
|
<Setter Property="Background" Value="{DynamicResource CaptionButtonBackground}" /> |
||||
|
<Setter Property="BorderBrush" Value="{DynamicResource CaptionButtonBorderBrush}" /> |
||||
|
<Setter Property="Foreground" Value="{DynamicResource CaptionButtonForeground}"/> |
||||
|
<Setter Property="Width" Value="{DynamicResource CaptionButtonWidth}"/> |
||||
|
<Setter Property="Height" Value="{DynamicResource CaptionButtonHeight}"/> |
||||
|
<Setter Property="VerticalAlignment" Value="Stretch"/> |
||||
|
<Setter Property="(WindowDecorationProperties.ElementRole)" Value="DecorationsElement"/> |
||||
|
<Setter Property="Template"> |
||||
|
<ControlTemplate> |
||||
|
<ContentPresenter Name="PART_ContentPresenter" |
||||
|
Background="Transparent" |
||||
|
Content="{TemplateBinding Content}"/> |
||||
|
</ControlTemplate> |
||||
|
</Setter> |
||||
|
|
||||
|
<Style Selector="^:pointerover /template/ ContentPresenter"> |
||||
|
<Setter Property="Background" Value="{TemplateBinding Background}" /> |
||||
|
</Style> |
||||
|
|
||||
|
<Style Selector="^:pressed /template/ ContentPresenter"> |
||||
|
<Setter Property="Background" Value="{TemplateBinding BorderBrush}" /> |
||||
|
</Style> |
||||
|
</ControlTheme> |
||||
|
|
||||
|
<ControlTheme x:Key="{x:Type WindowDrawnDecorations}" TargetType="WindowDrawnDecorations"> |
||||
|
<Setter Property="DefaultTitleBarHeight" Value="30"/> |
||||
|
<Setter Property="DefaultFrameThickness" Value="1"/> |
||||
|
<Setter Property="DefaultShadowThickness" Value="8"/> |
||||
|
<Setter Property="Template"> |
||||
|
<WindowDrawnDecorationsTemplate> |
||||
|
<WindowDrawnDecorationsContent> |
||||
|
|
||||
|
<WindowDrawnDecorationsContent.Underlay> |
||||
|
<Panel x:Name="PART_UnderlayWrapper"> |
||||
|
<Border x:Name="PART_WindowBorder" |
||||
|
Background="{DynamicResource ThemeBackgroundBrush}" |
||||
|
BorderThickness="{TemplateBinding FrameThickness}" |
||||
|
BorderBrush="{DynamicResource ThemeBorderMidBrush}" |
||||
|
IsHitTestVisible="False" /> |
||||
|
<!-- Titlebar: background, title text, and drag area live in underlay --> |
||||
|
<Panel x:Name="PART_TitleBar" VerticalAlignment="Top" |
||||
|
Height="{TemplateBinding TitleBarHeight}" |
||||
|
Background="{DynamicResource ThemeBackgroundBrush}" |
||||
|
IsVisible="{TemplateBinding HasTitleBar}" |
||||
|
WindowDecorationProperties.ElementRole="TitleBar" /> |
||||
|
</Panel> |
||||
|
</WindowDrawnDecorationsContent.Underlay> |
||||
|
|
||||
|
<WindowDrawnDecorationsContent.Overlay> |
||||
|
<!-- Overlay: only interactive caption buttons --> |
||||
|
<Panel x:Name="PART_OverlayWrapper"> |
||||
|
<!-- Title text lives in overlay so it renders above client content --> |
||||
|
<Panel x:Name="PART_TitleTextPanel" VerticalAlignment="Top" |
||||
|
Height="{TemplateBinding TitleBarHeight}" |
||||
|
IsHitTestVisible="False" |
||||
|
IsVisible="{TemplateBinding HasTitleBar}"> |
||||
|
<TextBlock Text="{TemplateBinding Title}" |
||||
|
VerticalAlignment="Center" |
||||
|
Margin="12,0,0,0" |
||||
|
FontSize="12" /> |
||||
|
</Panel> |
||||
|
<StackPanel x:Name="PART_OverlayPanel" |
||||
|
VerticalAlignment="Top" |
||||
|
HorizontalAlignment="Right" |
||||
|
Height="{TemplateBinding TitleBarHeight}" |
||||
|
Orientation="Horizontal" |
||||
|
Spacing="2" |
||||
|
IsVisible="{TemplateBinding HasTitleBar}" |
||||
|
TextElement.FontSize="10"> |
||||
|
<Button x:Name="PART_FullScreenButton" |
||||
|
Theme="{StaticResource SimpleDrawnCaptionButton}" |
||||
|
WindowDecorationProperties.ElementRole="FullScreenButton"> |
||||
|
<Viewbox Width="11" Margin="2"> |
||||
|
<Path Name="FullScreenButtonPath" |
||||
|
Stretch="UniformToFill" |
||||
|
Fill="{DynamicResource CaptionButtonForeground}" |
||||
|
Data="M2048 2048v-819h-205v469l-1493 -1493h469v-205h-819v819h205v-469l1493 1493h-469v205h819z" /> |
||||
|
</Viewbox> |
||||
|
</Button> |
||||
|
<Button x:Name="PART_MinimizeButton" |
||||
|
Theme="{StaticResource SimpleDrawnCaptionButton}" |
||||
|
WindowDecorationProperties.ElementRole="MinimizeButton" |
||||
|
AutomationProperties.Name="Minimize"> |
||||
|
<Viewbox Width="11" Margin="2"> |
||||
|
<Path Stretch="UniformToFill" |
||||
|
Fill="{DynamicResource CaptionButtonForeground}" |
||||
|
Data="M2048 1229v-205h-2048v205h2048z" /> |
||||
|
</Viewbox> |
||||
|
</Button> |
||||
|
<Button x:Name="PART_MaximizeButton" |
||||
|
Theme="{StaticResource SimpleDrawnCaptionButton}" |
||||
|
WindowDecorationProperties.ElementRole="MaximizeButton" |
||||
|
AutomationProperties.Name="Maximize"> |
||||
|
<Viewbox Width="11" Margin="2"> |
||||
|
<Viewbox.RenderTransform> |
||||
|
<RotateTransform Angle="-90" /> |
||||
|
</Viewbox.RenderTransform> |
||||
|
<Path Name="RestoreButtonPath" |
||||
|
Stretch="UniformToFill" |
||||
|
Fill="{DynamicResource CaptionButtonForeground}" |
||||
|
Data="M2048 2048v-2048h-2048v2048h2048zM1843 1843h-1638v-1638h1638v1638z"/> |
||||
|
</Viewbox> |
||||
|
</Button> |
||||
|
<Button x:Name="PART_CloseButton" |
||||
|
Background="#ffe81123" |
||||
|
BorderBrush="#fff1707a" |
||||
|
Theme="{StaticResource SimpleDrawnCaptionButton}" |
||||
|
WindowDecorationProperties.ElementRole="CloseButton" |
||||
|
AutomationProperties.Name="Close"> |
||||
|
<Viewbox Width="11" Margin="2"> |
||||
|
<Path Stretch="UniformToFill" |
||||
|
Fill="{DynamicResource CaptionButtonForeground}" |
||||
|
Data="M1169 1024l879 -879l-145 -145l-879 879l-879 -879l-145 145l879 879l-879 879l145 145l879 -879l879 879l145 -145z" /> |
||||
|
</Viewbox> |
||||
|
</Button> |
||||
|
</StackPanel> |
||||
|
</Panel> |
||||
|
</WindowDrawnDecorationsContent.Overlay> |
||||
|
|
||||
|
<WindowDrawnDecorationsContent.FullscreenPopover> |
||||
|
<!-- Shown on hover at top edge in fullscreen mode --> |
||||
|
<Panel Height="30" |
||||
|
VerticalAlignment="Top" |
||||
|
Background="{DynamicResource ThemeAccentBrush}" |
||||
|
WindowDecorationProperties.ElementRole="TitleBar"> |
||||
|
<TextBlock Text="{TemplateBinding Title}" |
||||
|
VerticalAlignment="Center" |
||||
|
Foreground="White" |
||||
|
Margin="12,0,0,0" |
||||
|
FontSize="12" /> |
||||
|
<StackPanel HorizontalAlignment="Right" |
||||
|
Orientation="Horizontal" |
||||
|
Spacing="2" |
||||
|
TextElement.FontSize="10"> |
||||
|
<Button x:Name="PART_PopoverFullScreenButton" |
||||
|
Theme="{StaticResource SimpleDrawnCaptionButton}" |
||||
|
WindowDecorationProperties.ElementRole="FullScreenButton"> |
||||
|
<Viewbox Width="11" Margin="2"> |
||||
|
<Path Stretch="UniformToFill" |
||||
|
Fill="{DynamicResource CaptionButtonForeground}" |
||||
|
Data="M205 1024h819v-819h-205v469l-674 -674l-145 145l674 674h-469v205zM1374 1229h469v-205h-819v819h205v-469l674 674l145 -145z" /> |
||||
|
</Viewbox> |
||||
|
</Button> |
||||
|
<Button x:Name="PART_PopoverCloseButton" |
||||
|
Background="#ffe81123" |
||||
|
BorderBrush="#fff1707a" |
||||
|
Theme="{StaticResource SimpleDrawnCaptionButton}" |
||||
|
WindowDecorationProperties.ElementRole="CloseButton" |
||||
|
AutomationProperties.Name="Close"> |
||||
|
<Viewbox Width="11" Margin="2"> |
||||
|
<Path Stretch="UniformToFill" |
||||
|
Fill="{DynamicResource CaptionButtonForeground}" |
||||
|
Data="M1169 1024l879 -879l-145 -145l-879 879l-879 -879l-145 145l879 879l-879 879l145 145l879 -879l879 879l145 -145z" /> |
||||
|
</Viewbox> |
||||
|
</Button> |
||||
|
</StackPanel> |
||||
|
</Panel> |
||||
|
</WindowDrawnDecorationsContent.FullscreenPopover> |
||||
|
|
||||
|
</WindowDrawnDecorationsContent> |
||||
|
</WindowDrawnDecorationsTemplate> |
||||
|
</Setter> |
||||
|
|
||||
|
<!-- Shadow: inset border and add drop shadow --> |
||||
|
<Style Selector="^:has-shadow /template/ Panel#PART_UnderlayWrapper"> |
||||
|
<Setter Property="Margin" Value="{TemplateBinding ShadowThickness}"/> |
||||
|
</Style> |
||||
|
<Style Selector="^:has-shadow /template/ Border#PART_WindowBorder"> |
||||
|
<Setter Property="BoxShadow" Value="0 2 10 2 #80000000"/> |
||||
|
</Style> |
||||
|
<Style Selector="^:has-shadow /template/ Panel#PART_OverlayWrapper"> |
||||
|
<Setter Property="Margin" Value="{TemplateBinding ShadowThickness}"/> |
||||
|
</Style> |
||||
|
|
||||
|
|
||||
|
<!-- Border: inset titlebar and buttons inside frame --> |
||||
|
<Style Selector="^:has-border /template/ Panel#PART_TitleTextPanel"> |
||||
|
<Setter Property="Margin" Value="1,1,1,0"/> |
||||
|
</Style> |
||||
|
<Style Selector="^:has-border /template/ Panel#PART_TitleBar"> |
||||
|
<Setter Property="Margin" Value="1,1,1,0"/> |
||||
|
</Style> |
||||
|
<Style Selector="^:has-border /template/ StackPanel#PART_OverlayPanel"> |
||||
|
<Setter Property="Margin" Value="0,1,1,0"/> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- Maximized: restore button path changes --> |
||||
|
<Style Selector="^:maximized /template/ Path#RestoreButtonPath"> |
||||
|
<Setter Property="Data" Value="M2048 410h-410v-410h-1638v1638h410v410h1638v-1638zM1434 1434h-1229v-1229h1229v1229zM1843 1843h-1229v-205h1024v-1024h205v1229z" /> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- Fullscreen: update fullscreen button icon to "exit fullscreen" --> |
||||
|
<Style Selector="^:fullscreen /template/ Path#FullScreenButtonPath"> |
||||
|
<Setter Property="Data" Value="M205 1024h819v-819h-205v469l-674 -674l-145 145l674 674h-469v205zM1374 1229h469v-205h-819v819h205v-469l674 674l145 -145z" /> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- Disabled buttons --> |
||||
|
<Style Selector="^ /template/ Button:disabled"> |
||||
|
<Setter Property="Opacity" Value="0.2"/> |
||||
|
</Style> |
||||
|
|
||||
|
<!-- Fullscreen: hide overlay and titlebar (popover takes over) --> |
||||
|
<Style Selector="^:fullscreen /template/ Panel#PART_TitleTextPanel"> |
||||
|
<Setter Property="IsVisible" Value="False" /> |
||||
|
</Style> |
||||
|
<Style Selector="^:fullscreen /template/ StackPanel#PART_OverlayPanel"> |
||||
|
<Setter Property="IsVisible" Value="False" /> |
||||
|
</Style> |
||||
|
<Style Selector="^:fullscreen /template/ Panel#PART_TitleBar"> |
||||
|
<Setter Property="IsVisible" Value="False" /> |
||||
|
</Style> |
||||
|
</ControlTheme> |
||||
|
</ResourceDictionary> |
||||
@ -0,0 +1,21 @@ |
|||||
|
using System; |
||||
|
using Avalonia.Controls.Chrome; |
||||
|
using Avalonia.Controls.Templates; |
||||
|
using Avalonia.Metadata; |
||||
|
using Avalonia.Styling; |
||||
|
|
||||
|
namespace Avalonia.Markup.Xaml.Templates; |
||||
|
|
||||
|
[ControlTemplateScope] |
||||
|
public class WindowDrawnDecorationsTemplate : IWindowDrawnDecorationsTemplate, ITemplate |
||||
|
{ |
||||
|
[Content] |
||||
|
[TemplateContent(TemplateResultType = typeof(WindowDrawnDecorationsContent))] |
||||
|
public object? Content { get; set; } |
||||
|
|
||||
|
public TemplateResult<WindowDrawnDecorationsContent> Build() => |
||||
|
TemplateContent.Load<WindowDrawnDecorationsContent>(Content) |
||||
|
?? throw new InvalidOperationException(); |
||||
|
|
||||
|
object? ITemplate.Build() => Build().Result; |
||||
|
} |
||||
Loading…
Reference in new issue