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