Browse Source

Wired up the popup positioner

Tests are failing because they are trying create popups out of a thin air
pull/2778/head
Nikita Tsukanov 7 years ago
parent
commit
9343ba4c23
  1. 19
      src/Avalonia.Controls/PlacementMode.cs
  2. 4
      src/Avalonia.Controls/Platform/IPopupImpl.cs
  3. 24
      src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
  4. 27
      src/Avalonia.Controls/Platform/IWindowImpl.cs
  5. 60
      src/Avalonia.Controls/Primitives/Popup.cs
  6. 111
      src/Avalonia.Controls/Primitives/PopupRoot.cs
  7. 6
      src/Avalonia.Controls/ToolTip.cs
  8. 45
      src/Avalonia.Controls/Window.cs
  9. 49
      src/Avalonia.Controls/WindowBase.cs
  10. 5
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
  11. 2
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
  12. 23
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  13. 16
      src/Avalonia.Native/PopupImpl.cs
  14. 4
      src/Avalonia.Native/WindowImpl.cs
  15. 34
      src/Avalonia.X11/X11Window.cs
  16. 15
      src/Windows/Avalonia.Win32/PopupImpl.cs
  17. 4
      src/Windows/Avalonia.Win32/WindowImpl.cs
  18. 27
      tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
  19. 25
      tests/Avalonia.Controls.UnitTests/WindowTests.cs

19
src/Avalonia.Controls/PlacementMode.cs

@ -23,6 +23,21 @@ namespace Avalonia.Controls
/// <summary>
/// The popup is placed at the top right of its target.
/// </summary>
Right
Right,
/// <summary>
/// The popup is placed at the top left of its target.
/// </summary>
Left,
/// <summary>
/// The popup is placed at the top left of its target.
/// </summary>
Top,
/// <summary>
/// The popup is placed according to anchor and gravity rules
/// </summary>
AnchorAndGravity
}
}
}

4
src/Avalonia.Controls/Platform/IPopupImpl.cs

@ -1,6 +1,8 @@
// Copyright (c) The Avalonia Project. All rights reserved.
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using Avalonia.Controls.Primitives.PopupPositioning;
namespace Avalonia.Platform
{
/// <summary>
@ -8,6 +10,6 @@ namespace Avalonia.Platform
/// </summary>
public interface IPopupImpl : IWindowBaseImpl
{
IPopupPositioner PopupPositioner { get; }
}
}

24
src/Avalonia.Controls/Platform/IWindowBaseImpl.cs

@ -15,21 +15,10 @@ namespace Avalonia.Platform
/// </summary>
void Hide();
/// <summary>
/// Starts moving a window with left button being held. Should be called from left mouse button press event handler.
/// </summary>
void BeginMoveDrag();
/// <summary>
/// Starts resizing a window. This function is used if an application has window resizing controls.
/// Should be called from left mouse button press event handler
/// </summary>
void BeginResizeDrag(WindowEdge edge);
/// <summary>
/// Gets the position of the window in device pixels.
/// </summary>
PixelPoint Position { get; set; }
PixelPoint Position { get; }
/// <summary>
/// Gets or sets a method called when the window's position changes.
@ -61,17 +50,6 @@ namespace Avalonia.Platform
/// </summary>
Size MaxClientSize { get; }
/// <summary>
/// Sets the client size of the top level.
/// </summary>
void Resize(Size clientSize);
/// <summary>
/// Minimum width of the window.
/// </summary>
///
void SetMinMaxSize(Size minSize, Size maxSize);
/// <summary>
/// Sets whether this window appears on top of all other windows
/// </summary>

27
src/Avalonia.Controls/Platform/IWindowImpl.cs

@ -57,5 +57,32 @@ namespace Avalonia.Platform
/// Return true to prevent the underlying implementation from closing.
/// </summary>
Func<bool> Closing { get; set; }
/// <summary>
/// Starts moving a window with left button being held. Should be called from left mouse button press event handler.
/// </summary>
void BeginMoveDrag();
/// <summary>
/// Starts resizing a window. This function is used if an application has window resizing controls.
/// Should be called from left mouse button press event handler
/// </summary>
void BeginResizeDrag(WindowEdge edge);
/// <summary>
/// Sets the client size of the top level.
/// </summary>
void Resize(Size clientSize);
/// <summary>
/// Sets the client size of the top level.
/// </summary>
void Move(PixelPoint point);
/// <summary>
/// Minimum width of the window.
/// </summary>
///
void SetMinMaxSize(Size minSize, Size maxSize);
}
}

60
src/Avalonia.Controls/Primitives/Popup.cs

@ -42,7 +42,7 @@ namespace Avalonia.Controls.Primitives
/// Defines the <see cref="ObeyScreenEdges"/> property.
/// </summary>
public static readonly StyledProperty<bool> ObeyScreenEdgesProperty =
AvaloniaProperty.Register<Popup, bool>(nameof(ObeyScreenEdges));
AvaloniaProperty.Register<Popup, bool>(nameof(ObeyScreenEdges), true);
/// <summary>
/// Defines the <see cref="HorizontalOffset"/> property.
@ -147,10 +147,7 @@ namespace Avalonia.Controls.Primitives
set { SetValue(PlacementModeProperty, value); }
}
/// <summary>
/// Gets or sets a value indicating whether the popup positions itself within the nearest screen boundary
/// when its opened at a position where it would otherwise overlap the screen edge.
/// </summary>
[Obsolete("This property has no effect")]
public bool ObeyScreenEdges
{
get => GetValue(ObeyScreenEdgesProperty);
@ -241,8 +238,9 @@ namespace Avalonia.Controls.Primitives
((ISetLogicalParent)_popupRoot).SetParent(this);
}
_popupRoot.Position = GetPosition();
_popupRoot.ConfigurePosition(PlacementTarget ?? this.GetVisualParent<Control>(),
PlacementMode, new Point(HorizontalOffset, VerticalOffset));
var window = _topLevel as Window;
if (window != null)
{
@ -263,11 +261,6 @@ namespace Avalonia.Controls.Primitives
_popupRoot.Show();
if (ObeyScreenEdges)
{
_popupRoot.SnapInsideScreenEdges();
}
using (BeginIgnoringIsOpen())
{
IsOpen = true;
@ -379,49 +372,6 @@ namespace Avalonia.Controls.Primitives
}
}
/// <summary>
/// Gets the position for the popup based on the placement properties.
/// </summary>
/// <returns>The popup's position in screen coordinates.</returns>
protected virtual PixelPoint GetPosition()
{
var result = GetPosition(PlacementTarget ?? this.GetVisualParent<Control>(), PlacementMode, PopupRoot,
HorizontalOffset, VerticalOffset);
return result;
}
internal static PixelPoint GetPosition(Control target, PlacementMode placement, PopupRoot popupRoot, double horizontalOffset, double verticalOffset)
{
var root = target?.GetVisualRoot();
var mode = root != null ? placement : PlacementMode.Pointer;
var scaling = root?.RenderScaling ?? 1;
switch (mode)
{
case PlacementMode.Pointer:
if (popupRoot != null)
{
var screenOffset = PixelPoint.FromPoint(new Point(horizontalOffset, verticalOffset), scaling);
var mouseOffset = ((IInputRoot)popupRoot)?.MouseDevice?.Position ?? default;
return new PixelPoint(
screenOffset.X + mouseOffset.X,
screenOffset.Y + mouseOffset.Y);
}
return default;
case PlacementMode.Bottom:
return target?.PointToScreen(new Point(0 + horizontalOffset, target.Bounds.Height + verticalOffset)) ?? default;
case PlacementMode.Right:
return target?.PointToScreen(new Point(target.Bounds.Width + horizontalOffset, 0 + verticalOffset)) ?? default;
default:
throw new InvalidOperationException("Invalid value for Popup.PlacementMode");
}
}
private void ListenForNonClientClick(RawInputEventArgs e)
{
var mouse = e as RawPointerEventArgs;

111
src/Avalonia.Controls/Primitives/PopupRoot.cs

@ -4,6 +4,7 @@
using System;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Presenters;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Interactivity;
using Avalonia.Media;
using Avalonia.Platform;
@ -18,7 +19,9 @@ namespace Avalonia.Controls.Primitives
/// </summary>
public class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost
{
private readonly TopLevel _parent;
private IDisposable _presenterSubscription;
private PopupPositionerParameters _positionerParameters;
/// <summary>
/// Initializes static members of the <see cref="PopupRoot"/> class.
@ -45,6 +48,7 @@ namespace Avalonia.Controls.Primitives
public PopupRoot(TopLevel parent, IAvaloniaDependencyResolver dependencyResolver)
: base(parent.PlatformImpl.CreatePopup(), dependencyResolver)
{
_parent = parent;
}
/// <summary>
@ -74,33 +78,6 @@ namespace Avalonia.Controls.Primitives
/// <inheritdoc/>
public void Dispose() => PlatformImpl?.Dispose();
/// <summary>
/// Moves the Popups position so that it doesnt overlap screen edges.
/// This method can be called immediately after Show has been called.
/// </summary>
public void SnapInsideScreenEdges()
{
var screen = (VisualRoot as WindowBase)?.Screens?.ScreenFromPoint(Position);
if (screen != null)
{
var scaling = VisualRoot.RenderScaling;
var bounds = PixelRect.FromRect(Bounds, scaling);
var screenX = Position.X + bounds.Width - screen.Bounds.X;
var screenY = Position.Y + bounds.Height - screen.Bounds.Y;
if (screenX > screen.Bounds.Width)
{
Position = Position.WithX(Position.X - (screenX - screen.Bounds.Width));
}
if (screenY > screen.Bounds.Height)
{
Position = Position.WithY(Position.Y - (screenY - screen.Bounds.Height));
}
}
}
/// <inheritdoc/>
protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
{
@ -142,5 +119,85 @@ namespace Avalonia.Controls.Primitives
}
}
}
void UpdatePosition()
{
PlatformImpl?.PopupPositioner.Update(_positionerParameters);
}
public void ConfigurePosition(Control target, PlacementMode placement, Point offset,
PopupPositioningEdge anchor = PopupPositioningEdge.None,
PopupPositioningEdge gravity = PopupPositioningEdge.None)
{
// We need a better way for tracking the last pointer position
var pointer = _parent.PointToClient(_parent.PlatformImpl.MouseDevice.Position);
_positionerParameters.Offset = offset;
_positionerParameters.ConstraintAdjustment = PopupPositionerConstraintAdjustment.All;
if (placement == PlacementMode.Pointer)
{
_positionerParameters.AnchorRectangle = new Rect(pointer, new Size(1, 1));
_positionerParameters.Anchor = PopupPositioningEdge.BottomRight;
_positionerParameters.Gravity = PopupPositioningEdge.BottomRight;
}
else
{
if (target == null)
throw new InvalidOperationException("Placement mode is not Pointer and PlacementTarget is null");
var matrix = target.TransformToVisual(_parent);
if (matrix == null)
throw new InvalidCastException("Target control is not in the same tree as the popup parent");
_positionerParameters.AnchorRectangle = new Rect(default, target.Bounds.Size)
.TransformToAABB(matrix.Value);
if (placement == PlacementMode.Right)
{
_positionerParameters.Anchor = PopupPositioningEdge.TopRight;
_positionerParameters.Gravity = PopupPositioningEdge.BottomRight;
}
else if (placement == PlacementMode.Bottom)
{
_positionerParameters.Anchor = PopupPositioningEdge.BottomLeft;
_positionerParameters.Gravity = PopupPositioningEdge.BottomRight;
}
else if (placement == PlacementMode.Left)
{
_positionerParameters.Anchor = PopupPositioningEdge.TopLeft;
_positionerParameters.Gravity = PopupPositioningEdge.BottomLeft;
}
else if (placement == PlacementMode.Top)
{
_positionerParameters.Anchor = PopupPositioningEdge.TopLeft;
_positionerParameters.Gravity = PopupPositioningEdge.TopRight;
}
else if (placement == PlacementMode.AnchorAndGravity)
{
_positionerParameters.Anchor = anchor;
_positionerParameters.Gravity = gravity;
}
else
throw new InvalidOperationException("Invalid value for Popup.PlacementMode");
}
if (_positionerParameters.Size != default)
UpdatePosition();
}
/// <summary>
/// Carries out the arrange pass of the window.
/// </summary>
/// <param name="finalSize">The final window size.</param>
/// <returns>The <paramref name="finalSize"/> parameter unchanged.</returns>
protected override Size ArrangeOverride(Size finalSize)
{
using (BeginAutoSizing())
{
_positionerParameters.Size = finalSize;
UpdatePosition();
}
return base.ArrangeOverride(PlatformImpl?.ClientSize ?? default(Size));
}
}
}

6
src/Avalonia.Controls/ToolTip.cs

@ -237,10 +237,10 @@ namespace Avalonia.Controls
_popup = new PopupRoot((TopLevel)control.GetVisualRoot()) {Content = this};
((ISetLogicalParent)_popup).SetParent(control);
_popup.Position = Popup.GetPosition(control, GetPlacement(control), _popup,
GetHorizontalOffset(control), GetVerticalOffset(control));
_popup.ConfigurePosition(control, GetPlacement(control),
new Point(GetHorizontalOffset(control), GetVerticalOffset(control)));
_popup.Show();
_popup.SnapInsideScreenEdges();
}
private void Close()

45
src/Avalonia.Controls/Window.cs

@ -135,6 +135,12 @@ namespace Avalonia.Controls
WindowStateProperty.Changed.AddClassHandler<Window>(
(w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.WindowState = (WindowState)e.NewValue; });
MinWidthProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size((double)e.NewValue, w.MinHeight), new Size(w.MaxWidth, w.MaxHeight)));
MinHeightProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, (double)e.NewValue), new Size(w.MaxWidth, w.MaxHeight)));
MaxWidthProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size((double)e.NewValue, w.MaxHeight)));
MaxHeightProperty.Changed.AddClassHandler<Window>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size(w.MaxWidth, (double)e.NewValue)));
}
/// <summary>
@ -155,6 +161,7 @@ namespace Avalonia.Controls
impl.Closing = HandleClosing;
impl.WindowStateChanged = HandleWindowStateChanged;
_maxPlatformClientSize = PlatformImpl?.MaxClientSize ?? default(Size);
this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x));
}
/// <summary>
@ -239,6 +246,44 @@ namespace Avalonia.Controls
set { SetAndRaise(WindowStartupLocationProperty, ref _windowStartupLocation, value); }
}
/// <summary>
/// Gets or sets the window position in screen coordinates.
/// </summary>
public PixelPoint Position
{
get { return PlatformImpl?.Position ?? PixelPoint.Origin; }
set
{
PlatformImpl?.Move(value);
}
}
/// <summary>
/// Starts moving a window with left button being held. Should be called from left mouse button press event handler
/// </summary>
public void BeginMoveDrag() => PlatformImpl?.BeginMoveDrag();
/// <summary>
/// Starts resizing a window. This function is used if an application has window resizing controls.
/// Should be called from left mouse button press event handler
/// </summary>
public void BeginResizeDrag(WindowEdge edge) => PlatformImpl?.BeginResizeDrag(edge);
/// <summary>
/// Carries out the arrange pass of the window.
/// </summary>
/// <param name="finalSize">The final window size.</param>
/// <returns>The <paramref name="finalSize"/> parameter unchanged.</returns>
protected override Size ArrangeOverride(Size finalSize)
{
using (BeginAutoSizing())
{
PlatformImpl?.Resize(finalSize);
}
return base.ArrangeOverride(PlatformImpl?.ClientSize ?? default(Size));
}
/// <inheritdoc/>
Size ILayoutRoot.MaxClientSize => _maxPlatformClientSize;

49
src/Avalonia.Controls/WindowBase.cs

@ -49,10 +49,6 @@ namespace Avalonia.Controls
IsVisibleProperty.OverrideDefaultValue<WindowBase>(false);
IsVisibleProperty.Changed.AddClassHandler<WindowBase>(x => x.IsVisibleChanged);
MinWidthProperty.Changed.AddClassHandler<WindowBase>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size((double)e.NewValue, w.MinHeight), new Size(w.MaxWidth, w.MaxHeight)));
MinHeightProperty.Changed.AddClassHandler<WindowBase>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, (double)e.NewValue), new Size(w.MaxWidth, w.MaxHeight)));
MaxWidthProperty.Changed.AddClassHandler<WindowBase>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size((double)e.NewValue, w.MaxHeight)));
MaxHeightProperty.Changed.AddClassHandler<WindowBase>((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size(w.MaxWidth, (double)e.NewValue)));
TopmostProperty.Changed.AddClassHandler<WindowBase>((w, e) => w.PlatformImpl?.SetTopmost((bool)e.NewValue));
}
@ -67,7 +63,6 @@ namespace Avalonia.Controls
impl.Activated = HandleActivated;
impl.Deactivated = HandleDeactivated;
impl.PositionChanged = HandlePositionChanged;
this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x));
}
/// <summary>
@ -96,19 +91,6 @@ namespace Avalonia.Controls
get { return _isActive; }
private set { SetAndRaise(IsActiveProperty, ref _isActive, value); }
}
/// <summary>
/// Gets or sets the window position in screen coordinates.
/// </summary>
public PixelPoint Position
{
get { return PlatformImpl?.Position ?? PixelPoint.Origin; }
set
{
if (PlatformImpl is IWindowBaseImpl impl)
impl.Position = value;
}
}
public Screens Screens { get; private set; }
@ -193,6 +175,11 @@ namespace Avalonia.Controls
}
}
protected internal virtual void OnBeforeShow()
{
}
/// <summary>
/// Begins an auto-resize operation.
/// </summary>
@ -208,21 +195,6 @@ namespace Avalonia.Controls
return Disposable.Create(() => AutoSizing = false);
}
/// <summary>
/// Carries out the arrange pass of the window.
/// </summary>
/// <param name="finalSize">The final window size.</param>
/// <returns>The <paramref name="finalSize"/> parameter unchanged.</returns>
protected override Size ArrangeOverride(Size finalSize)
{
using (BeginAutoSizing())
{
PlatformImpl?.Resize(finalSize);
}
return base.ArrangeOverride(PlatformImpl?.ClientSize ?? default(Size));
}
/// <summary>
/// Ensures that the window is initialized.
/// </summary>
@ -318,16 +290,5 @@ namespace Avalonia.Controls
}
}
}
/// <summary>
/// Starts moving a window with left button being held. Should be called from left mouse button press event handler
/// </summary>
public void BeginMoveDrag() => PlatformImpl?.BeginMoveDrag();
/// <summary>
/// Starts resizing a window. This function is used if an application has window resizing controls.
/// Should be called from left mouse button press event handler
/// </summary>
public void BeginResizeDrag(WindowEdge edge) => PlatformImpl?.BeginResizeDrag(edge);
}
}

5
src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs

@ -72,6 +72,11 @@ namespace Avalonia.DesignerSupport.Remote
RenderIfNeeded();
}
public void Move(PixelPoint point)
{
}
public void SetMinMaxSize(Size minSize, Size maxSize)
{
}

2
src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs

@ -40,8 +40,6 @@ namespace Avalonia.DesignerSupport.Remote
return s_lastWindow;
}
public IPopupImpl CreatePopup() => new WindowStub();
public static void Initialize(IAvaloniaRemoteTransportConnection transport)
{
s_transport = transport;

23
src/Avalonia.DesignerSupport/Remote/Stubs.cs

@ -5,6 +5,7 @@ using System.Reactive.Disposables;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Input.Raw;
@ -13,7 +14,7 @@ using Avalonia.Rendering;
namespace Avalonia.DesignerSupport.Remote
{
class WindowStub : IPopupImpl, IWindowImpl
class WindowStub : IWindowImpl, IPopupImpl
{
public Action Deactivated { get; set; }
public Action Activated { get; set; }
@ -29,12 +30,23 @@ namespace Avalonia.DesignerSupport.Remote
public Func<bool> Closing { get; set; }
public Action Closed { get; set; }
public IMouseDevice MouseDevice { get; } = new MouseDevice();
public IPopupImpl CreatePopup() => null;
public IPopupImpl CreatePopup() => new WindowStub(this);
public PixelPoint Position { get; set; }
public Action<PixelPoint> PositionChanged { get; set; }
public WindowState WindowState { get; set; }
public Action<WindowState> WindowStateChanged { get; set; }
public WindowStub(IWindowImpl parent = null)
{
if (parent != null)
PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent,
(_, size, __) =>
{
Resize(size);
}));
}
public IRenderer CreateRenderer(IRenderRoot root) => new ImmediateRenderer(root);
public void Dispose()
{
@ -79,6 +91,11 @@ namespace Avalonia.DesignerSupport.Remote
{
}
public void Move(PixelPoint point)
{
}
public IScreenImpl Screen { get; } = new ScreenStub();
public void SetMinMaxSize(Size minSize, Size maxSize)
@ -112,6 +129,8 @@ namespace Avalonia.DesignerSupport.Remote
public void SetTopmost(bool value)
{
}
public IPopupPositioner PopupPositioner { get; }
}
class ClipboardStub : IClipboard

16
src/Avalonia.Native/PopupImpl.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Native.Interop;
using Avalonia.Platform;
@ -11,7 +12,9 @@ namespace Avalonia.Native
{
private readonly IAvaloniaNativeFactory _factory;
private readonly AvaloniaNativePlatformOptions _opts;
public PopupImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts) : base(opts)
public PopupImpl(IAvaloniaNativeFactory factory,
AvaloniaNativePlatformOptions opts,
IWindowBaseImpl parent) : base(opts)
{
_factory = factory;
_opts = opts;
@ -19,6 +22,14 @@ namespace Avalonia.Native
{
Init(factory.CreatePopup(e), factory.CreateScreens());
}
PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, MoveResize));
}
private void MoveResize(PixelPoint position, Size size, double scaling)
{
Position = position;
Resize(size);
//TODO: We ignore the scaling override for now
}
class PopupEvents : WindowBaseEvents, IAvnWindowEvents
@ -40,6 +51,7 @@ namespace Avalonia.Native
}
}
public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts);
public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts, this);
public IPopupPositioner PopupPositioner { get; }
}
}

4
src/Avalonia.Native/WindowImpl.cs

@ -104,6 +104,8 @@ namespace Avalonia.Native
}
public Func<bool> Closing { get; set; }
public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts);
public void Move(PixelPoint point) => Position = point;
public override IPopupImpl CreatePopup() => new PopupImpl(_factory, _opts, this);
}
}

34
src/Avalonia.X11/X11Window.cs

@ -6,6 +6,7 @@ using System.Linq;
using System.Reactive.Disposables;
using System.Text;
using Avalonia.Controls;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.OpenGL;
@ -39,6 +40,7 @@ namespace Avalonia.X11
private bool _mapped;
private HashSet<X11Window> _transientChildren = new HashSet<X11Window>();
private X11Window _transientParent;
private double? _scalingOverride;
public object SyncRoot { get; } = new object();
class InputEventContainer
@ -151,6 +153,8 @@ namespace Avalonia.X11
_xic = XCreateIC(_x11.Xim, XNames.XNInputStyle, XIMProperties.XIMPreeditNothing | XIMProperties.XIMStatusNothing,
XNames.XNClientWindow, _handle, IntPtr.Zero);
XFlush(_x11.Display);
if(_popup)
PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(popupParent, MoveResize));
}
class SurfaceInfo : EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo
@ -454,13 +458,20 @@ namespace Avalonia.X11
}
}
private bool UpdateScaling()
private bool UpdateScaling(bool skipResize = false)
{
lock (SyncRoot)
{
var monitor = _platform.X11Screens.Screens.OrderBy(x => x.PixelDensity)
.FirstOrDefault(m => m.Bounds.Contains(Position));
var newScaling = monitor?.PixelDensity ?? Scaling;
double newScaling;
if (_scalingOverride.HasValue)
newScaling = _scalingOverride.Value;
else
{
var monitor = _platform.X11Screens.Screens.OrderBy(x => x.PixelDensity)
.FirstOrDefault(m => m.Bounds.Contains(Position));
newScaling = monitor?.PixelDensity ?? Scaling;
}
if (Scaling != newScaling)
{
Console.WriteLine(
@ -469,7 +480,8 @@ namespace Avalonia.X11
Scaling = newScaling;
ScalingChanged?.Invoke(Scaling);
SetMinMaxSize(_scaledMinMaxSize.minSize, _scaledMinMaxSize.maxSize);
Resize(oldScaledSize, true);
if(!skipResize)
Resize(oldScaledSize, true);
return true;
}
@ -731,6 +743,14 @@ namespace Avalonia.X11
public void Resize(Size clientSize) => Resize(clientSize, false);
public void Move(PixelPoint point) => Position = point;
private void MoveResize(PixelPoint position, Size size, double scaling)
{
Move(position);
_scalingOverride = scaling;
UpdateScaling(true);
Resize(size, true);
}
PixelSize ToPixelSize(Size size) => new PixelSize((int)(size.Width * Scaling), (int)(size.Height * Scaling));
@ -939,6 +959,8 @@ namespace Avalonia.X11
{
SendNetWMMessage(_x11.Atoms._NET_WM_STATE,
(IntPtr)(value ? 0 : 1), _x11.Atoms._NET_WM_STATE_SKIP_TASKBAR, IntPtr.Zero);
}
}
public IPopupPositioner PopupPositioner { get; }
}
}

15
src/Windows/Avalonia.Win32/PopupImpl.cs

@ -2,6 +2,7 @@
// Licensed under the MIT license. See licence.md file in the project root for full license information.
using System;
using Avalonia.Controls.Primitives.PopupPositioning;
using Avalonia.Platform;
using Avalonia.Win32.Interop;
@ -57,5 +58,19 @@ namespace Avalonia.Win32
return base.WndProc(hWnd, msg, wParam, lParam);
}
}
public PopupImpl(IWindowBaseImpl parent)
{
PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(parent, MoveResize));
}
private void MoveResize(PixelPoint position, Size size, double scaling)
{
Move(position);
Resize(size);
//TODO: We ignore the scaling override for now
}
public IPopupPositioner PopupPositioner { get; }
}
}

4
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -131,6 +131,8 @@ namespace Avalonia.Win32
}
}
public void Move(PixelPoint point) => Position = point;
public void SetMinMaxSize(Size minSize, Size maxSize)
{
_minSize = minSize;
@ -250,7 +252,7 @@ namespace Avalonia.Win32
public IPopupImpl CreatePopup()
{
return new PopupImpl();
return new PopupImpl(this);
}
public void Dispose()

27
tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs

@ -20,33 +20,6 @@ namespace Avalonia.Controls.UnitTests
{
public class WindowBaseTests
{
[Fact]
public void Impl_ClientSize_Should_Be_Set_After_Layout_Pass()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var impl = Mock.Of<IWindowBaseImpl>(x => x.Scaling == 1);
Mock.Get(impl).Setup(x => x.Resize(It.IsAny<Size>())).Callback(() => { });
var target = new TestWindowBase(impl)
{
Template = CreateTemplate(),
Content = new TextBlock
{
Width = 321,
Height = 432,
},
IsVisible = true,
};
target.LayoutManager.ExecuteInitialLayoutPass(target);
Mock.Get(impl).Verify(x => x.Resize(new Size(321, 432)));
}
}
[Fact]
public void Activate_Should_Call_Impl_Activate()
{

25
tests/Avalonia.Controls.UnitTests/WindowTests.cs

@ -17,6 +17,31 @@ namespace Avalonia.Controls.UnitTests
{
public class WindowTests
{
[Fact]
public void Impl_ClientSize_Should_Be_Set_After_Layout_Pass()
{
using (UnitTestApplication.Start(TestServices.StyledWindow))
{
var impl = Mock.Of<IWindowImpl>(x => x.Scaling == 1);
Mock.Get(impl).Setup(x => x.Resize(It.IsAny<Size>())).Callback(() => { });
var target = new Window(impl)
{
Content = new TextBlock
{
Width = 321,
Height = 432,
},
IsVisible = true,
};
target.LayoutManager.ExecuteInitialLayoutPass(target);
Mock.Get(impl).Verify(x => x.Resize(new Size(321, 432)));
}
}
[Fact]
public void Setting_Title_Should_Set_Impl_Title()
{

Loading…
Cancel
Save