Browse Source

Wired up the popup positioner

Tests are failing because they are trying create popups out of a thin air
Nikita Tsukanov 6 years ago
parent
commit
9343ba4c23

+ 17 - 2
src/Avalonia.Controls/PlacementMode.cs

@@ -23,6 +23,21 @@ namespace Avalonia.Controls
         /// <summary>
         /// <summary>
         /// The popup is placed at the top right of its target.
         /// The popup is placed at the top right of its target.
         /// </summary>
         /// </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
     }
     }
-}
+}

+ 3 - 1
src/Avalonia.Controls/Platform/IPopupImpl.cs

@@ -1,6 +1,8 @@
 // Copyright (c) The Avalonia Project. All rights reserved.
 // 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.
 // 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
 namespace Avalonia.Platform
 {
 {
     /// <summary>
     /// <summary>
@@ -8,6 +10,6 @@ namespace Avalonia.Platform
     /// </summary>
     /// </summary>
     public interface IPopupImpl : IWindowBaseImpl
     public interface IPopupImpl : IWindowBaseImpl
     {
     {
-
+        IPopupPositioner PopupPositioner { get; }
     }
     }
 }
 }

+ 1 - 23
src/Avalonia.Controls/Platform/IWindowBaseImpl.cs

@@ -15,21 +15,10 @@ namespace Avalonia.Platform
         /// </summary>
         /// </summary>
         void Hide();
         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>
         /// <summary>
         /// Gets the position of the window in device pixels.
         /// Gets the position of the window in device pixels.
         /// </summary>
         /// </summary>
-        PixelPoint Position { get; set; }
+        PixelPoint Position { get; }
         
         
         /// <summary>
         /// <summary>
         /// Gets or sets a method called when the window's position changes.
         /// Gets or sets a method called when the window's position changes.
@@ -61,17 +50,6 @@ namespace Avalonia.Platform
         /// </summary>
         /// </summary>
         Size MaxClientSize { get; }
         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>
         /// <summary>
         /// Sets whether this window appears on top of all other windows
         /// Sets whether this window appears on top of all other windows
         /// </summary>
         /// </summary>

+ 27 - 0
src/Avalonia.Controls/Platform/IWindowImpl.cs

@@ -57,5 +57,32 @@ namespace Avalonia.Platform
         /// Return true to prevent the underlying implementation from closing.
         /// Return true to prevent the underlying implementation from closing.
         /// </summary>
         /// </summary>
         Func<bool> Closing { get; set; }
         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);
     }
     }
 }
 }

+ 5 - 55
src/Avalonia.Controls/Primitives/Popup.cs

@@ -42,7 +42,7 @@ namespace Avalonia.Controls.Primitives
         /// Defines the <see cref="ObeyScreenEdges"/> property.
         /// Defines the <see cref="ObeyScreenEdges"/> property.
         /// </summary>
         /// </summary>
         public static readonly StyledProperty<bool> ObeyScreenEdgesProperty =
         public static readonly StyledProperty<bool> ObeyScreenEdgesProperty =
-            AvaloniaProperty.Register<Popup, bool>(nameof(ObeyScreenEdges));
+            AvaloniaProperty.Register<Popup, bool>(nameof(ObeyScreenEdges), true);
 
 
         /// <summary>
         /// <summary>
         /// Defines the <see cref="HorizontalOffset"/> property.
         /// Defines the <see cref="HorizontalOffset"/> property.
@@ -147,10 +147,7 @@ namespace Avalonia.Controls.Primitives
             set { SetValue(PlacementModeProperty, value); }
             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
         public bool ObeyScreenEdges
         {
         {
             get => GetValue(ObeyScreenEdgesProperty);
             get => GetValue(ObeyScreenEdgesProperty);
@@ -241,8 +238,9 @@ namespace Avalonia.Controls.Primitives
                 ((ISetLogicalParent)_popupRoot).SetParent(this);
                 ((ISetLogicalParent)_popupRoot).SetParent(this);
             }
             }
 
 
-            _popupRoot.Position = GetPosition();
-
+            _popupRoot.ConfigurePosition(PlacementTarget ?? this.GetVisualParent<Control>(),
+                PlacementMode, new Point(HorizontalOffset, VerticalOffset));
+            
             var window = _topLevel as Window;
             var window = _topLevel as Window;
             if (window != null)
             if (window != null)
             {
             {
@@ -263,11 +261,6 @@ namespace Avalonia.Controls.Primitives
 
 
             _popupRoot.Show();
             _popupRoot.Show();
 
 
-            if (ObeyScreenEdges)
-            {
-                _popupRoot.SnapInsideScreenEdges();
-            }
-
             using (BeginIgnoringIsOpen())
             using (BeginIgnoringIsOpen())
             {
             {
                 IsOpen = true;
                 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)
         private void ListenForNonClientClick(RawInputEventArgs e)
         {
         {
             var mouse = e as RawPointerEventArgs;
             var mouse = e as RawPointerEventArgs;

+ 84 - 27
src/Avalonia.Controls/Primitives/PopupRoot.cs

@@ -4,6 +4,7 @@
 using System;
 using System;
 using Avalonia.Controls.Platform;
 using Avalonia.Controls.Platform;
 using Avalonia.Controls.Presenters;
 using Avalonia.Controls.Presenters;
+using Avalonia.Controls.Primitives.PopupPositioning;
 using Avalonia.Interactivity;
 using Avalonia.Interactivity;
 using Avalonia.Media;
 using Avalonia.Media;
 using Avalonia.Platform;
 using Avalonia.Platform;
@@ -18,7 +19,9 @@ namespace Avalonia.Controls.Primitives
     /// </summary>
     /// </summary>
     public class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost
     public class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost
     {
     {
+        private readonly TopLevel _parent;
         private IDisposable _presenterSubscription;
         private IDisposable _presenterSubscription;
+        private PopupPositionerParameters _positionerParameters;
 
 
         /// <summary>
         /// <summary>
         /// Initializes static members of the <see cref="PopupRoot"/> class.
         /// Initializes static members of the <see cref="PopupRoot"/> class.
@@ -45,6 +48,7 @@ namespace Avalonia.Controls.Primitives
         public PopupRoot(TopLevel parent, IAvaloniaDependencyResolver dependencyResolver)
         public PopupRoot(TopLevel parent, IAvaloniaDependencyResolver dependencyResolver)
             : base(parent.PlatformImpl.CreatePopup(), dependencyResolver)
             : base(parent.PlatformImpl.CreatePopup(), dependencyResolver)
         {
         {
+            _parent = parent;
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -74,33 +78,6 @@ namespace Avalonia.Controls.Primitives
         /// <inheritdoc/>
         /// <inheritdoc/>
         public void Dispose() => PlatformImpl?.Dispose();
         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/>
         /// <inheritdoc/>
         protected override void OnTemplateApplied(TemplateAppliedEventArgs e)
         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));
+        }
     }
     }
 }
 }

+ 3 - 3
src/Avalonia.Controls/ToolTip.cs

@@ -237,10 +237,10 @@ namespace Avalonia.Controls
 
 
             _popup = new PopupRoot((TopLevel)control.GetVisualRoot()) {Content = this};
             _popup = new PopupRoot((TopLevel)control.GetVisualRoot()) {Content = this};
             ((ISetLogicalParent)_popup).SetParent(control);
             ((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.Show();
-            _popup.SnapInsideScreenEdges();
         }
         }
 
 
         private void Close()
         private void Close()

+ 45 - 0
src/Avalonia.Controls/Window.cs

@@ -135,6 +135,12 @@ namespace Avalonia.Controls
 
 
             WindowStateProperty.Changed.AddClassHandler<Window>(
             WindowStateProperty.Changed.AddClassHandler<Window>(
                 (w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.WindowState = (WindowState)e.NewValue; });
                 (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>
         /// <summary>
@@ -155,6 +161,7 @@ namespace Avalonia.Controls
             impl.Closing = HandleClosing;
             impl.Closing = HandleClosing;
             impl.WindowStateChanged = HandleWindowStateChanged;
             impl.WindowStateChanged = HandleWindowStateChanged;
             _maxPlatformClientSize = PlatformImpl?.MaxClientSize ?? default(Size);
             _maxPlatformClientSize = PlatformImpl?.MaxClientSize ?? default(Size);
+            this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x));
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -239,6 +246,44 @@ namespace Avalonia.Controls
             set { SetAndRaise(WindowStartupLocationProperty, ref _windowStartupLocation, value); }
             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/>
         /// <inheritdoc/>
         Size ILayoutRoot.MaxClientSize => _maxPlatformClientSize;
         Size ILayoutRoot.MaxClientSize => _maxPlatformClientSize;
 
 

+ 5 - 44
src/Avalonia.Controls/WindowBase.cs

@@ -49,10 +49,6 @@ namespace Avalonia.Controls
             IsVisibleProperty.OverrideDefaultValue<WindowBase>(false);
             IsVisibleProperty.OverrideDefaultValue<WindowBase>(false);
             IsVisibleProperty.Changed.AddClassHandler<WindowBase>(x => x.IsVisibleChanged);
             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));
             TopmostProperty.Changed.AddClassHandler<WindowBase>((w, e) => w.PlatformImpl?.SetTopmost((bool)e.NewValue));
         }
         }
@@ -67,7 +63,6 @@ namespace Avalonia.Controls
             impl.Activated = HandleActivated;
             impl.Activated = HandleActivated;
             impl.Deactivated = HandleDeactivated;
             impl.Deactivated = HandleDeactivated;
             impl.PositionChanged = HandlePositionChanged;
             impl.PositionChanged = HandlePositionChanged;
-            this.GetObservable(ClientSizeProperty).Skip(1).Subscribe(x => PlatformImpl?.Resize(x));
         }
         }
 
 
         /// <summary>
         /// <summary>
@@ -96,19 +91,6 @@ namespace Avalonia.Controls
             get { return _isActive; }
             get { return _isActive; }
             private set { SetAndRaise(IsActiveProperty, ref _isActive, value); }
             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; }
         public Screens Screens { get; private set; }
 
 
@@ -193,6 +175,11 @@ namespace Avalonia.Controls
             }
             }
         }
         }
 
 
+        protected internal virtual void OnBeforeShow()
+        {
+            
+        }
+
         /// <summary>
         /// <summary>
         /// Begins an auto-resize operation.
         /// Begins an auto-resize operation.
         /// </summary>
         /// </summary>
@@ -208,21 +195,6 @@ namespace Avalonia.Controls
             return Disposable.Create(() => AutoSizing = false);
             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>
         /// <summary>
         /// Ensures that the window is initialized.
         /// Ensures that the window is initialized.
         /// </summary>
         /// </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 - 0
src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs

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

+ 0 - 2
src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs

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

+ 21 - 2
src/Avalonia.DesignerSupport/Remote/Stubs.cs

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

+ 14 - 2
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.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 
 using System;
 using System;
+using Avalonia.Controls.Primitives.PopupPositioning;
 using Avalonia.Native.Interop;
 using Avalonia.Native.Interop;
 using Avalonia.Platform;
 using Avalonia.Platform;
 
 
@@ -11,7 +12,9 @@ namespace Avalonia.Native
     {
     {
         private readonly IAvaloniaNativeFactory _factory;
         private readonly IAvaloniaNativeFactory _factory;
         private readonly AvaloniaNativePlatformOptions _opts;
         private readonly AvaloniaNativePlatformOptions _opts;
-        public PopupImpl(IAvaloniaNativeFactory factory, AvaloniaNativePlatformOptions opts) : base(opts)
+        public PopupImpl(IAvaloniaNativeFactory factory,
+            AvaloniaNativePlatformOptions opts,
+            IWindowBaseImpl parent) : base(opts)
         {
         {
             _factory = factory;
             _factory = factory;
             _opts = opts;
             _opts = opts;
@@ -19,6 +22,14 @@ namespace Avalonia.Native
             {
             {
                 Init(factory.CreatePopup(e), factory.CreateScreens());
                 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
         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; }
     }
     }
 }
 }

+ 3 - 1
src/Avalonia.Native/WindowImpl.cs

@@ -104,6 +104,8 @@ namespace Avalonia.Native
         }
         }
 
 
         public Func<bool> Closing { get; set; }
         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);
     }
     }
 }
 }

+ 28 - 6
src/Avalonia.X11/X11Window.cs

@@ -6,6 +6,7 @@ using System.Linq;
 using System.Reactive.Disposables;
 using System.Reactive.Disposables;
 using System.Text;
 using System.Text;
 using Avalonia.Controls;
 using Avalonia.Controls;
+using Avalonia.Controls.Primitives.PopupPositioning;
 using Avalonia.Input;
 using Avalonia.Input;
 using Avalonia.Input.Raw;
 using Avalonia.Input.Raw;
 using Avalonia.OpenGL;
 using Avalonia.OpenGL;
@@ -39,6 +40,7 @@ namespace Avalonia.X11
         private bool _mapped;
         private bool _mapped;
         private HashSet<X11Window> _transientChildren = new HashSet<X11Window>();
         private HashSet<X11Window> _transientChildren = new HashSet<X11Window>();
         private X11Window _transientParent;
         private X11Window _transientParent;
+        private double? _scalingOverride;
         public object SyncRoot { get; } = new object();
         public object SyncRoot { get; } = new object();
 
 
         class InputEventContainer
         class InputEventContainer
@@ -151,6 +153,8 @@ namespace Avalonia.X11
             _xic = XCreateIC(_x11.Xim, XNames.XNInputStyle, XIMProperties.XIMPreeditNothing | XIMProperties.XIMStatusNothing,
             _xic = XCreateIC(_x11.Xim, XNames.XNInputStyle, XIMProperties.XIMPreeditNothing | XIMProperties.XIMStatusNothing,
                 XNames.XNClientWindow, _handle, IntPtr.Zero);
                 XNames.XNClientWindow, _handle, IntPtr.Zero);
             XFlush(_x11.Display);
             XFlush(_x11.Display);
+            if(_popup)
+                PopupPositioner = new ManagedPopupPositioner(new ManagedPopupPositionerPopupImplHelper(popupParent, MoveResize));
         }
         }
 
 
         class SurfaceInfo  : EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo
         class SurfaceInfo  : EglGlPlatformSurface.IEglWindowGlPlatformSurfaceInfo
@@ -454,13 +458,20 @@ namespace Avalonia.X11
             }
             }
         }
         }
 
 
-        private bool UpdateScaling()
+        private bool UpdateScaling(bool skipResize = false)
         {
         {
             lock (SyncRoot)
             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)
                 if (Scaling != newScaling)
                 {
                 {
                     Console.WriteLine(
                     Console.WriteLine(
@@ -469,7 +480,8 @@ namespace Avalonia.X11
                     Scaling = newScaling;
                     Scaling = newScaling;
                     ScalingChanged?.Invoke(Scaling);
                     ScalingChanged?.Invoke(Scaling);
                     SetMinMaxSize(_scaledMinMaxSize.minSize, _scaledMinMaxSize.maxSize);
                     SetMinMaxSize(_scaledMinMaxSize.minSize, _scaledMinMaxSize.maxSize);
-                    Resize(oldScaledSize, true);
+                    if(!skipResize)
+                        Resize(oldScaledSize, true);
                     return true;
                     return true;
                 }
                 }
 
 
@@ -731,6 +743,14 @@ namespace Avalonia.X11
 
 
 
 
         public void Resize(Size clientSize) => Resize(clientSize, false);
         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));
         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,
             SendNetWMMessage(_x11.Atoms._NET_WM_STATE,
                 (IntPtr)(value ? 0 : 1), _x11.Atoms._NET_WM_STATE_SKIP_TASKBAR, IntPtr.Zero);
                 (IntPtr)(value ? 0 : 1), _x11.Atoms._NET_WM_STATE_SKIP_TASKBAR, IntPtr.Zero);
-        }        
+        }
+
+        public IPopupPositioner PopupPositioner { get; }
     }
     }
 }
 }

+ 15 - 0
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.
 // Licensed under the MIT license. See licence.md file in the project root for full license information.
 
 
 using System;
 using System;
+using Avalonia.Controls.Primitives.PopupPositioning;
 using Avalonia.Platform;
 using Avalonia.Platform;
 using Avalonia.Win32.Interop;
 using Avalonia.Win32.Interop;
 
 
@@ -57,5 +58,19 @@ namespace Avalonia.Win32
                     return base.WndProc(hWnd, msg, wParam, lParam);
                     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; }
     }
     }
 }
 }

+ 3 - 1
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)
         public void SetMinMaxSize(Size minSize, Size maxSize)
         {
         {
             _minSize = minSize;
             _minSize = minSize;
@@ -250,7 +252,7 @@ namespace Avalonia.Win32
 
 
         public IPopupImpl CreatePopup()
         public IPopupImpl CreatePopup()
         {
         {
-            return new PopupImpl();
+            return new PopupImpl(this);
         }
         }
 
 
         public void Dispose()
         public void Dispose()

+ 0 - 27
tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs

@@ -20,33 +20,6 @@ namespace Avalonia.Controls.UnitTests
 {
 {
     public class WindowBaseTests
     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]
         [Fact]
         public void Activate_Should_Call_Impl_Activate()
         public void Activate_Should_Call_Impl_Activate()
         {
         {

+ 25 - 0
tests/Avalonia.Controls.UnitTests/WindowTests.cs

@@ -17,6 +17,31 @@ namespace Avalonia.Controls.UnitTests
 {
 {
     public class WindowTests
     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]
         [Fact]
         public void Setting_Title_Should_Set_Impl_Title()
         public void Setting_Title_Should_Set_Impl_Title()
         {
         {