@ -98,6 +98,7 @@ namespace Avalonia.Controls
private Thickness _ offScreenMargin ;
private bool _ canHandleResized = false ;
private Size _ arrangeBounds ;
private bool _ isForcedDecorationMode ;
/// <summary>
/// Defines the <see cref="SizeToContent"/> property.
@ -249,7 +250,11 @@ namespace Avalonia.Controls
impl . WindowStateChanged = HandleWindowStateChanged ;
_ maxPlatformClientSize = PlatformImpl ? . MaxAutoSizeHint ? ? default ( Size ) ;
impl . ExtendClientAreaToDecorationsChanged = ExtendClientAreaToDecorationsChanged ;
this . GetObservable ( ClientSizeProperty ) . Skip ( 1 ) . Subscribe ( x = > PlatformImpl ? . Resize ( x , WindowResizeReason . Application ) ) ;
this . GetObservable ( ClientSizeProperty ) . Skip ( 1 ) . Subscribe ( x = >
{
ResizePlatformImpl ( x , WindowResizeReason . Application ) ;
} ) ;
ScalingChanged + = OnScalingChangedUpdateDecorations ;
CreatePlatformImplBinding ( TitleProperty , title = > PlatformImpl ! . SetTitle ( title ) ) ;
CreatePlatformImplBinding ( IconProperty , SetEffectiveIcon ) ;
@ -668,7 +673,7 @@ namespace Avalonia.Controls
UpdateDrawnDecorationParts ( ) ;
}
protected virtual void ExtendClientAreaToDecorationsChanged ( bool isExtended )
private void ExtendClientAreaToDecorationsChanged ( bool isExtended )
{
IsExtendedIntoWindowDecorations = isExtended ;
OffScreenMargin = PlatformImpl ? . OffScreenMargin ? ? default ;
@ -679,6 +684,10 @@ namespace Avalonia.Controls
private void UpdateDrawnDecorations ( )
{
var parts = ComputeDecorationParts ( ) ;
// Detect forced mode: platform needs managed decorations but app hasn't opted in
_ isForcedDecorationMode = parts ! = null & & ! IsExtendedIntoWindowDecorations ;
TopLevelHost . UpdateDrawnDecorations ( parts , WindowState ) ;
if ( parts ! = null )
@ -687,6 +696,8 @@ namespace Avalonia.Controls
var decorations = TopLevelHost . Decorations ;
if ( decorations ! = null )
{
decorations . RenderScaling = RenderScaling ;
var hint = ExtendClientAreaTitleBarHeightHint ;
if ( hint > = 0 )
decorations . TitleBarHeightOverride = hint ;
@ -696,6 +707,13 @@ namespace Avalonia.Controls
UpdateDrawnDecorationMargins ( ) ;
}
private void OnScalingChangedUpdateDecorations ( object? sender , EventArgs e )
{
var decorations = TopLevelHost . Decorations ;
if ( decorations ! = null )
decorations . RenderScaling = RenderScaling ;
}
/// <summary>
/// Updates decoration parts based on current window state without
/// re-creating the decorations instance.
@ -753,7 +771,9 @@ namespace Avalonia.Controls
var decorations = TopLevelHost . Decorations ;
if ( decorations = = null )
{
// Only use platform margins if drawn decorations are not active
WindowDecorationMargin = PlatformImpl ? . ExtendedMargins ? ? default ;
TopLevelHost . DecorationInset = default ;
return ;
}
@ -764,11 +784,25 @@ namespace Avalonia.Controls
? decorations . FrameThickness : default ;
var shadow = parts . HasFlag ( Chrome . DrawnWindowDecorationParts . Shadow )
? decorations . ShadowThickness : default ;
WindowDecorationM argin = new Thickness (
var m argin = new Thickness (
frame . Left + shadow . Left ,
titleBarHeight + frame . Top + shadow . Top ,
frame . Right + shadow . Right ,
frame . Bottom + shadow . Bottom ) ;
if ( _ isForcedDecorationMode )
{
// In forced mode, app is unaware of decorations.
// TopLevelHost insets the Window child; WindowDecorationMargin stays zero.
WindowDecorationMargin = default ;
TopLevelHost . DecorationInset = margin ;
}
else
{
// In extended mode, app handles the margin itself.
WindowDecorationMargin = margin ;
TopLevelHost . DecorationInset = default ;
}
}
private void OnTitleBarHeightHintChanged ( )
@ -933,6 +967,15 @@ namespace Avalonia.Controls
// Enable drawn decorations before layout so margins are computed
UpdateDrawnDecorations ( ) ;
// In forced mode, adjust ClientSize to reflect usable content area
if ( _ isForcedDecorationMode )
{
var inset = TopLevelHost . DecorationInset ;
ClientSize = new Size (
Math . Max ( 0 , ClientSize . Width - inset . Left - inset . Right ) ,
Math . Max ( 0 , ClientSize . Height - inset . Top - inset . Bottom ) ) ;
}
_ shown = true ;
IsVisible = true ;
@ -978,10 +1021,18 @@ namespace Avalonia.Controls
DesktopScalingOverride = null ;
if ( clientSizeChanged | | ClientSize ! = PlatformImpl ? . ClientSize )
// In forced mode, compare against adjusted platform size
var platformClientSize = PlatformImpl ? . ClientSize ? ? default ;
var comparableClientSize = _ isForcedDecorationMode
? new Size (
Math . Max ( 0 , platformClientSize . Width - TopLevelHost . DecorationInset . Left - TopLevelHost . DecorationInset . Right ) ,
Math . Max ( 0 , platformClientSize . Height - TopLevelHost . DecorationInset . Top - TopLevelHost . DecorationInset . Bottom ) )
: platformClientSize ;
if ( clientSizeChanged | | ClientSize ! = comparableClientSize )
{
// Previously it was called before ExecuteInitialLayoutPass
PlatformImpl ? . Resize ( ClientSize , WindowResizeReason . Layout ) ;
Resize PlatformImpl( ClientSize , WindowResizeReason . Layout ) ;
// we do not want PlatformImpl?.Resize to trigger HandleResized yet because it will set Width and Height.
// So perform some important actions from HandleResized
@ -1037,6 +1088,22 @@ namespace Avalonia.Controls
}
}
private void ResizePlatformImpl ( Size size , WindowResizeReason reason )
{
// In forced mode, add decoration inset so platform gets full frame size
if ( _ isForcedDecorationMode )
{
var inset = TopLevelHost . DecorationInset ;
size = new Size (
size . Width + inset . Left + inset . Right ,
size . Height + inset . Top + inset . Bottom ) ;
if ( PlatformImpl ? . ClientSize ! = size )
PlatformImpl ? . Resize ( size , reason ) ;
}
else
PlatformImpl ? . Resize ( size , reason ) ;
}
/// <summary>
/// Shows the window as a dialog.
/// </summary>
@ -1272,6 +1339,14 @@ namespace Avalonia.Controls
{
var sizeToContent = SizeToContent ;
var clientSize = ClientSize ;
if ( _ isForcedDecorationMode )
{
clientSize = PlatformImpl ? . ClientSize ? ? clientSize ;
var inset = TopLevelHost . DecorationInset ;
clientSize = new Size (
Math . Max ( 0 , clientSize . Width - inset . Left - inset . Right ) ,
Math . Max ( 0 , clientSize . Height - inset . Top - inset . Bottom ) ) ;
}
var maxAutoSize = PlatformImpl ? . MaxAutoSizeHint ? ? Size . Infinity ;
var useAutoWidth = sizeToContent . HasAllFlags ( SizeToContent . Width ) ;
var useAutoHeight = sizeToContent . HasAllFlags ( SizeToContent . Height ) ;
@ -1332,7 +1407,9 @@ namespace Avalonia.Controls
{
_ arrangeBounds = size ;
if ( _ canHandleResized )
PlatformImpl ? . Resize ( size , WindowResizeReason . Layout ) ;
{
ResizePlatformImpl ( size , WindowResizeReason . Layout ) ;
}
return ClientSize ;
}
@ -1350,6 +1427,16 @@ namespace Avalonia.Controls
/// <inheritdoc/>
internal override void HandleResized ( Size clientSize , WindowResizeReason reason )
{
// In forced decoration mode, the platform's clientSize includes decoration area.
// Subtract the decoration inset so Window.ClientSize reflects the usable content area.
if ( _ isForcedDecorationMode )
{
var inset = TopLevelHost . DecorationInset ;
clientSize = new Size (
Math . Max ( 0 , clientSize . Width - inset . Left - inset . Right ) ,
Math . Max ( 0 , clientSize . Height - inset . Top - inset . Bottom ) ) ;
}
if ( _ canHandleResized & & ( ClientSize ! = clientSize | | double . IsNaN ( Width ) | | double . IsNaN ( Height ) ) )
{
var sizeToContent = SizeToContent ;