@ -94,6 +94,7 @@ namespace Avalonia.Controls
private Thickness _ windowDecorationMargin ;
private Thickness _ offScreenMargin ;
private bool _ canHandleResized = false ;
private Size _ arrangeBounds ;
/// <summary>
/// Defines the <see cref="SizeToContent"/> property.
@ -198,6 +199,7 @@ namespace Avalonia.Controls
private readonly Size _ maxPlatformClientSize ;
private bool _ shown ;
private bool _ showingAsDialog ;
private bool _ positionWasSet ;
private bool _ wasShownBefore ;
/// <summary>
@ -428,7 +430,11 @@ namespace Avalonia.Controls
public PixelPoint Position
{
get = > PlatformImpl ? . Position ? ? PixelPoint . Origin ;
set = > PlatformImpl ? . Move ( value ) ;
set
{
PlatformImpl ? . Move ( value ) ;
_ positionWasSet = true ;
}
}
/// <summary>
@ -622,7 +628,7 @@ namespace Avalonia.Controls
/// </exception>
public override void Show ( )
{
ShowCore ( null ) ;
ShowCore < object > ( null , false ) ;
}
protected override void IsVisibleChanged ( AvaloniaPropertyChangedEventArgs e )
@ -666,7 +672,7 @@ namespace Avalonia.Controls
throw new ArgumentNullException ( nameof ( owner ) , "Showing a child window requires valid parent." ) ;
}
ShowCore ( owner ) ;
ShowCore < object > ( owner , false ) ;
}
private void EnsureStateBeforeShow ( )
@ -695,12 +701,16 @@ namespace Avalonia.Controls
}
}
private void ShowCore ( Window ? owner )
private Task < TResult > ? ShowCore < TResult > ( Window ? owner , bool modal )
{
using ( FreezeVisibilityChangeHandling ( ) )
{
EnsureStateBeforeShow ( ) ;
if ( modal & & owner = = null )
{
throw new ArgumentNullException ( nameof ( owner ) ) ;
}
if ( owner ! = null )
{
EnsureParentStateBeforeShow ( owner ) ;
@ -708,7 +718,9 @@ namespace Avalonia.Controls
if ( _ shown )
{
return ;
if ( modal )
throw new InvalidOperationException ( "The window is already being shown." ) ;
return null ;
}
RaiseEvent ( new RoutedEventArgs ( WindowOpenedEvent ) ) ;
@ -716,35 +728,93 @@ namespace Avalonia.Controls
EnsureInitialized ( ) ;
ApplyStyling ( ) ;
_ shown = true ;
_ showingAsDialog = modal ;
IsVisible = true ;
// We need to set position first because it is required for getting correct display scale. If position is not manual then it can be
// determined only by calling this method. But here it will calculate not precise location because scaling may not yet be applied (see i.e. X11Window),
// thus we ought to call it again later to center window correctly if needed, when scaling will be already applied
SetWindowStartupLocation ( owner ) ;
// If window position was not set before then platform may provide incorrect scaling at this time,
// but we need it for proper calculation of position and in some cases size (size to content)
SetExpectedScaling ( owner ) ;
_ canHandleResized = true ;
var initialSize = new Size (
double . IsNaN ( Width ) ? Math . Max ( MinWidth , ClientSize . Width ) : Width ,
double . IsNaN ( Height ) ? Math . Max ( MinHeight , ClientSize . Height ) : Height ) ;
double . IsNaN ( Width ) ? ClientSize . Width : Width ,
double . IsNaN ( Height ) ? ClientSize . Height : Height ) ;
initialSize = new Size (
MathUtilities . Clamp ( initialSize . Width , MinWidth , MaxWidth ) ,
MathUtilities . Clamp ( initialSize . Height , MinHeight , MaxHeight ) ) ;
if ( initialSize ! = ClientSize )
var clientSizeChanged = initialSize ! = ClientSize ;
ClientSize = initialSize ; // ClientSize is required for Measure and Arrange
// this will call ArrangeSetBounds
LayoutManager . ExecuteInitialLayoutPass ( ) ;
if ( SizeToContent . HasFlag ( SizeToContent . Width ) )
{
PlatformImpl ? . Resize ( initialSize , WindowResizeReason . Layout ) ;
initialSize = initialSize . WithWidth ( MathUtilities . Clamp ( _ arrangeBounds . Width , MinWidth , MaxWidth ) ) ;
clientSizeChanged | = initialSize ! = ClientSize ;
ClientSize = initialSize ;
}
LayoutManager . ExecuteInitialLayoutPass ( ) ;
if ( SizeToContent . HasFlag ( SizeToContent . Height ) )
{
initialSize = initialSize . WithHeight ( MathUtilities . Clamp ( _ arrangeBounds . Height , MinHeight , MaxHeight ) ) ;
clientSizeChanged | = initialSize ! = ClientSize ;
ClientSize = initialSize ;
}
Owner = owner ;
// Second call will calculate correct position because both current and owner windows have correct scaling.
SetWindowStartupLocation ( owner ) ;
DesktopScalingOverride = null ;
if ( clientSizeChanged | | ClientSize ! = PlatformImpl ? . ClientSize )
{
// Previously it was called before ExecuteInitialLayoutPass
PlatformImpl ? . Resize ( 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
Renderer . Resized ( ClientSize ) ;
OnResized ( new WindowResizedEventArgs ( ClientSize , WindowResizeReason . Layout ) ) ;
if ( ! double . IsNaN ( Width ) )
Width = ClientSize . Width ;
if ( ! double . IsNaN ( Height ) )
Height = ClientSize . Height ;
}
FrameSize = PlatformImpl ? . FrameSize ;
_ canHandleResized = true ;
StartRendering ( ) ;
PlatformImpl ? . Show ( ShowActivated , false ) ;
PlatformImpl ? . Show ( ShowActivated , modal ) ;
Task < TResult > ? result = null ;
if ( modal )
{
var tcs = new TaskCompletionSource < TResult > ( ) ;
Observable . FromEventPattern (
x = > Closed + = x ,
x = > Closed - = x )
. Take ( 1 )
. Subscribe ( _ = >
{
owner ! . Activate ( ) ;
tcs . SetResult ( ( TResult ) ( _d ialogResult ? ? default ( TResult ) ! ) ) ;
} ) ;
result = tcs . Task ;
}
OnOpened ( EventArgs . Empty ) ;
_ wasShownBefore = true ;
if ( ! modal )
_ wasShownBefore = true ;
return result ;
}
}
@ -773,74 +843,7 @@ namespace Avalonia.Controls
/// <returns>.
/// A task that can be used to retrieve the result of the dialog when it closes.
/// </returns>
public Task < TResult > ShowDialog < TResult > ( Window owner )
{
using ( FreezeVisibilityChangeHandling ( ) )
{
EnsureStateBeforeShow ( ) ;
if ( owner = = null )
{
throw new ArgumentNullException ( nameof ( owner ) ) ;
}
EnsureParentStateBeforeShow ( owner ) ;
if ( _ shown )
{
throw new InvalidOperationException ( "The window is already being shown." ) ;
}
RaiseEvent ( new RoutedEventArgs ( WindowOpenedEvent ) ) ;
EnsureInitialized ( ) ;
ApplyStyling ( ) ;
_ shown = true ;
_ showingAsDialog = true ;
IsVisible = true ;
// We need to set position first because it is required for getting correct display scale. If position is not manual then it can be
// determined only by calling this method. But here it will calculate not precise location because scaling may not yet be applied (see i.e. X11Window),
// thus we ought to call it again later to center window correctly if needed, when scaling will be already applied
SetWindowStartupLocation ( owner ) ;
_ canHandleResized = true ;
var initialSize = new Size (
double . IsNaN ( Width ) ? ClientSize . Width : Width ,
double . IsNaN ( Height ) ? ClientSize . Height : Height ) ;
if ( initialSize ! = ClientSize )
{
PlatformImpl ? . Resize ( initialSize , WindowResizeReason . Layout ) ;
}
LayoutManager . ExecuteInitialLayoutPass ( ) ;
var result = new TaskCompletionSource < TResult > ( ) ;
Owner = owner ;
// Second call will calculate correct position because both current and owner windows have correct scaling.
SetWindowStartupLocation ( owner ) ;
StartRendering ( ) ;
PlatformImpl ? . Show ( ShowActivated , true ) ;
Observable . FromEventPattern (
x = > Closed + = x ,
x = > Closed - = x )
. Take ( 1 )
. Subscribe ( _ = >
{
owner . Activate ( ) ;
result . SetResult ( ( TResult ) ( _d ialogResult ? ? default ( TResult ) ! ) ) ;
} ) ;
OnOpened ( EventArgs . Empty ) ;
return result . Task ;
}
}
public Task < TResult > ShowDialog < TResult > ( Window owner ) = > ShowCore < TResult > ( owner , true ) ! ;
/// <summary>
/// Sorts the windows ascending by their Z order - the topmost window will be the last in the list.
@ -925,31 +928,67 @@ namespace Avalonia.Controls
}
}
private void SetWindowStartupLocation ( Window ? owner = null )
private void SetExpectedScaling ( WindowBase ? owner )
{
if ( _ wasShownBefore = = true )
if ( _ wasShownBefore )
{
return ;
}
var location = GetEffectiveWindowStartupLocation ( owner ) ;
switch ( location )
{
case WindowStartupLocation . CenterOwner :
DesktopScalingOverride = owner ? . DesktopScaling ;
break ;
case WindowStartupLocation . CenterScreen :
DesktopScalingOverride = owner ? . DesktopScaling ? ? Screens . ScreenFromPoint ( Position ) ? . Scaling ? ? Screens . Primary ? . Scaling ;
break ;
case WindowStartupLocation . Manual :
DesktopScalingOverride = Screens . ScreenFromPoint ( Position ) ? . Scaling ;
break ;
}
}
private WindowStartupLocation GetEffectiveWindowStartupLocation ( WindowBase ? owner )
{
var startupLocation = WindowStartupLocation ;
if ( startupLocation = = WindowStartupLocation . CenterOwner & &
( owner is null | |
( Owner is Window ownerWindow & & ownerWindow . WindowState = = WindowState . Minimized ) )
)
( o wner is Window ownerWindow & & ownerWindow . WindowState = = WindowState . Minimized ) )
)
{
// If startup location is CenterOwner, but owner is null or minimized then fall back
// to CenterScreen. This behavior is consistent with WPF.
startupLocation = WindowStartupLocation . CenterScreen ;
}
var scaling = owner ? . DesktopScaling ? ? PlatformImpl ? . DesktopScaling ? ? 1 ;
return startupLocation ;
}
private void SetWindowStartupLocation ( Window ? owner = null )
{
if ( _ wasShownBefore )
{
return ;
}
var startupLocation = GetEffectiveWindowStartupLocation ( owner ) ;
PixelRect rect ;
// Use frame size, falling back to client size if the platform can't give it to us.
var rect = FrameSize . HasValue ?
new PixelRect ( PixelSize . FromSize ( FrameSize . Value , scaling ) ) :
new PixelRect ( PixelSize . FromSize ( ClientSize , scaling ) ) ;
if ( PlatformImpl ? . FrameSize . HasValue = = true )
{
// Platform may calculate FrameSize with incorrect scaling, so do not trust the value.
var diff = PlatformImpl . FrameSize . Value - PlatformImpl . ClientSize ;
rect = new PixelRect ( PixelSize . FromSize ( ClientSize + diff , DesktopScaling ) ) ;
}
else
{
rect = new PixelRect ( PixelSize . FromSize ( ClientSize , DesktopScaling ) ) ;
}
if ( startupLocation = = WindowStartupLocation . CenterScreen )
{
@ -962,10 +1001,16 @@ namespace Avalonia.Controls
}
screen ? ? = Screens . ScreenFromPoint ( Position ) ;
screen ? ? = Screens . Primary ;
if ( screen is not null )
{
Position = screen . WorkingArea . CenterRect ( rect ) . Position ;
var childRect = screen . WorkingArea . CenterRect ( rect ) ;
if ( Screens . ScreenFromPoint ( childRect . Position ) = = null )
childRect = ApplyScreenConstraint ( screen , childRect ) ;
Position = childRect . Position ;
}
}
else if ( startupLocation = = WindowStartupLocation . CenterOwner )
@ -973,10 +1018,22 @@ namespace Avalonia.Controls
var ownerSize = owner ! . FrameSize ? ? owner . ClientSize ;
var ownerRect = new PixelRect (
owner . Position ,
PixelSize . FromSize ( ownerSize , s caling) ) ;
PixelSize . FromSize ( ownerSize , owner . DesktopS caling) ) ;
var childRect = ownerRect . CenterRect ( rect ) ;
if ( Screens . ScreenFromWindow ( owner ) ? . WorkingArea is { } constraint )
var screen = Screens . ScreenFromWindow ( owner ) ;
childRect = ApplyScreenConstraint ( screen , childRect ) ;
Position = childRect . Position ;
}
if ( ! _ positionWasSet & & DesktopScaling ! = PlatformImpl ? . DesktopScaling ) // Platform returns incorrect scaling, forcing setting position may fix it
PlatformImpl ? . Move ( Position ) ;
PixelRect ApplyScreenConstraint ( Screen ? screen , PixelRect childRect )
{
if ( screen ? . WorkingArea is { } constraint )
{
var maxX = constraint . Right - rect . Width ;
var maxY = constraint . Bottom - rect . Height ;
@ -987,7 +1044,7 @@ namespace Avalonia.Controls
childRect = childRect . WithY ( MathUtilities . Clamp ( childRect . Y , constraint . Y , maxY ) ) ;
}
Position = childRect . Position ;
return childRect ;
}
}
@ -1048,7 +1105,9 @@ namespace Avalonia.Controls
protected sealed override Size ArrangeSetBounds ( Size size )
{
PlatformImpl ? . Resize ( size , WindowResizeReason . Layout ) ;
_ arrangeBounds = size ;
if ( _ canHandleResized )
PlatformImpl ? . Resize ( size , WindowResizeReason . Layout ) ;
return ClientSize ;
}