diff --git a/src/Avalonia.Controls/PlacementMode.cs b/src/Avalonia.Controls/PlacementMode.cs
index db77b6a365..99958c4c9e 100644
--- a/src/Avalonia.Controls/PlacementMode.cs
+++ b/src/Avalonia.Controls/PlacementMode.cs
@@ -23,6 +23,21 @@ namespace Avalonia.Controls
///
/// The popup is placed at the top right of its target.
///
- Right
+ Right,
+
+ ///
+ /// The popup is placed at the top left of its target.
+ ///
+ Left,
+
+ ///
+ /// The popup is placed at the top left of its target.
+ ///
+ Top,
+
+ ///
+ /// The popup is placed according to anchor and gravity rules
+ ///
+ AnchorAndGravity
}
-}
\ No newline at end of file
+}
diff --git a/src/Avalonia.Controls/Platform/IPopupImpl.cs b/src/Avalonia.Controls/Platform/IPopupImpl.cs
index 1b606f550b..2978016519 100644
--- a/src/Avalonia.Controls/Platform/IPopupImpl.cs
+++ b/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
{
///
@@ -8,6 +10,6 @@ namespace Avalonia.Platform
///
public interface IPopupImpl : IWindowBaseImpl
{
-
+ IPopupPositioner PopupPositioner { get; }
}
}
diff --git a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
index b37521de30..8c99dffc28 100644
--- a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
+++ b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
@@ -15,21 +15,10 @@ namespace Avalonia.Platform
///
void Hide();
- ///
- /// Starts moving a window with left button being held. Should be called from left mouse button press event handler.
- ///
- void BeginMoveDrag();
-
- ///
- /// 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
- ///
- void BeginResizeDrag(WindowEdge edge);
-
///
/// Gets the position of the window in device pixels.
///
- PixelPoint Position { get; set; }
+ PixelPoint Position { get; }
///
/// Gets or sets a method called when the window's position changes.
@@ -61,17 +50,6 @@ namespace Avalonia.Platform
///
Size MaxClientSize { get; }
- ///
- /// Sets the client size of the top level.
- ///
- void Resize(Size clientSize);
-
- ///
- /// Minimum width of the window.
- ///
- ///
- void SetMinMaxSize(Size minSize, Size maxSize);
-
///
/// Sets whether this window appears on top of all other windows
///
diff --git a/src/Avalonia.Controls/Platform/IWindowImpl.cs b/src/Avalonia.Controls/Platform/IWindowImpl.cs
index 2ddc5a5c85..bc5d38c845 100644
--- a/src/Avalonia.Controls/Platform/IWindowImpl.cs
+++ b/src/Avalonia.Controls/Platform/IWindowImpl.cs
@@ -57,5 +57,32 @@ namespace Avalonia.Platform
/// Return true to prevent the underlying implementation from closing.
///
Func Closing { get; set; }
+
+ ///
+ /// Starts moving a window with left button being held. Should be called from left mouse button press event handler.
+ ///
+ void BeginMoveDrag();
+
+ ///
+ /// 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
+ ///
+ void BeginResizeDrag(WindowEdge edge);
+
+ ///
+ /// Sets the client size of the top level.
+ ///
+ void Resize(Size clientSize);
+
+ ///
+ /// Sets the client size of the top level.
+ ///
+ void Move(PixelPoint point);
+
+ ///
+ /// Minimum width of the window.
+ ///
+ ///
+ void SetMinMaxSize(Size minSize, Size maxSize);
}
}
diff --git a/src/Avalonia.Controls/Primitives/Popup.cs b/src/Avalonia.Controls/Primitives/Popup.cs
index 895094eded..f9ec9796fb 100644
--- a/src/Avalonia.Controls/Primitives/Popup.cs
+++ b/src/Avalonia.Controls/Primitives/Popup.cs
@@ -42,7 +42,7 @@ namespace Avalonia.Controls.Primitives
/// Defines the property.
///
public static readonly StyledProperty ObeyScreenEdgesProperty =
- AvaloniaProperty.Register(nameof(ObeyScreenEdges));
+ AvaloniaProperty.Register(nameof(ObeyScreenEdges), true);
///
/// Defines the property.
@@ -147,10 +147,7 @@ namespace Avalonia.Controls.Primitives
set { SetValue(PlacementModeProperty, value); }
}
- ///
- /// 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.
- ///
+ [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(),
+ 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
}
}
- ///
- /// Gets the position for the popup based on the placement properties.
- ///
- /// The popup's position in screen coordinates.
- protected virtual PixelPoint GetPosition()
- {
- var result = GetPosition(PlacementTarget ?? this.GetVisualParent(), 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;
diff --git a/src/Avalonia.Controls/Primitives/PopupRoot.cs b/src/Avalonia.Controls/Primitives/PopupRoot.cs
index 47863932d1..efe4d09b3d 100644
--- a/src/Avalonia.Controls/Primitives/PopupRoot.cs
+++ b/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
///
public class PopupRoot : WindowBase, IInteractive, IHostedVisualTreeRoot, IDisposable, IStyleHost
{
+ private readonly TopLevel _parent;
private IDisposable _presenterSubscription;
+ private PopupPositionerParameters _positionerParameters;
///
/// Initializes static members of the class.
@@ -45,6 +48,7 @@ namespace Avalonia.Controls.Primitives
public PopupRoot(TopLevel parent, IAvaloniaDependencyResolver dependencyResolver)
: base(parent.PlatformImpl.CreatePopup(), dependencyResolver)
{
+ _parent = parent;
}
///
@@ -74,33 +78,6 @@ namespace Avalonia.Controls.Primitives
///
public void Dispose() => PlatformImpl?.Dispose();
- ///
- /// Moves the Popups position so that it doesnt overlap screen edges.
- /// This method can be called immediately after Show has been called.
- ///
- 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));
- }
- }
- }
-
///
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();
+ }
+
+ ///
+ /// Carries out the arrange pass of the window.
+ ///
+ /// The final window size.
+ /// The parameter unchanged.
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ using (BeginAutoSizing())
+ {
+ _positionerParameters.Size = finalSize;
+ UpdatePosition();
+ }
+
+ return base.ArrangeOverride(PlatformImpl?.ClientSize ?? default(Size));
+ }
}
}
diff --git a/src/Avalonia.Controls/ToolTip.cs b/src/Avalonia.Controls/ToolTip.cs
index 8c23f4abdc..da537a2e65 100644
--- a/src/Avalonia.Controls/ToolTip.cs
+++ b/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()
diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs
index d2793fe0dd..ef43746665 100644
--- a/src/Avalonia.Controls/Window.cs
+++ b/src/Avalonia.Controls/Window.cs
@@ -135,6 +135,12 @@ namespace Avalonia.Controls
WindowStateProperty.Changed.AddClassHandler(
(w, e) => { if (w.PlatformImpl != null) w.PlatformImpl.WindowState = (WindowState)e.NewValue; });
+
+ MinWidthProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size((double)e.NewValue, w.MinHeight), new Size(w.MaxWidth, w.MaxHeight)));
+ MinHeightProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, (double)e.NewValue), new Size(w.MaxWidth, w.MaxHeight)));
+ MaxWidthProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size((double)e.NewValue, w.MaxHeight)));
+ MaxHeightProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size(w.MaxWidth, (double)e.NewValue)));
+
}
///
@@ -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));
}
///
@@ -239,6 +246,44 @@ namespace Avalonia.Controls
set { SetAndRaise(WindowStartupLocationProperty, ref _windowStartupLocation, value); }
}
+ ///
+ /// Gets or sets the window position in screen coordinates.
+ ///
+ public PixelPoint Position
+ {
+ get { return PlatformImpl?.Position ?? PixelPoint.Origin; }
+ set
+ {
+ PlatformImpl?.Move(value);
+ }
+ }
+
+ ///
+ /// Starts moving a window with left button being held. Should be called from left mouse button press event handler
+ ///
+ public void BeginMoveDrag() => PlatformImpl?.BeginMoveDrag();
+
+ ///
+ /// 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
+ ///
+ public void BeginResizeDrag(WindowEdge edge) => PlatformImpl?.BeginResizeDrag(edge);
+
+ ///
+ /// Carries out the arrange pass of the window.
+ ///
+ /// The final window size.
+ /// The parameter unchanged.
+ protected override Size ArrangeOverride(Size finalSize)
+ {
+ using (BeginAutoSizing())
+ {
+ PlatformImpl?.Resize(finalSize);
+ }
+
+ return base.ArrangeOverride(PlatformImpl?.ClientSize ?? default(Size));
+ }
+
///
Size ILayoutRoot.MaxClientSize => _maxPlatformClientSize;
diff --git a/src/Avalonia.Controls/WindowBase.cs b/src/Avalonia.Controls/WindowBase.cs
index 40c9fc94d2..53e43e4ec4 100644
--- a/src/Avalonia.Controls/WindowBase.cs
+++ b/src/Avalonia.Controls/WindowBase.cs
@@ -49,10 +49,6 @@ namespace Avalonia.Controls
IsVisibleProperty.OverrideDefaultValue(false);
IsVisibleProperty.Changed.AddClassHandler(x => x.IsVisibleChanged);
- MinWidthProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size((double)e.NewValue, w.MinHeight), new Size(w.MaxWidth, w.MaxHeight)));
- MinHeightProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, (double)e.NewValue), new Size(w.MaxWidth, w.MaxHeight)));
- MaxWidthProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size((double)e.NewValue, w.MaxHeight)));
- MaxHeightProperty.Changed.AddClassHandler((w, e) => w.PlatformImpl?.SetMinMaxSize(new Size(w.MinWidth, w.MinHeight), new Size(w.MaxWidth, (double)e.NewValue)));
TopmostProperty.Changed.AddClassHandler((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));
}
///
@@ -96,19 +91,6 @@ namespace Avalonia.Controls
get { return _isActive; }
private set { SetAndRaise(IsActiveProperty, ref _isActive, value); }
}
-
- ///
- /// Gets or sets the window position in screen coordinates.
- ///
- 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()
+ {
+
+ }
+
///
/// Begins an auto-resize operation.
///
@@ -208,21 +195,6 @@ namespace Avalonia.Controls
return Disposable.Create(() => AutoSizing = false);
}
- ///
- /// Carries out the arrange pass of the window.
- ///
- /// The final window size.
- /// The parameter unchanged.
- protected override Size ArrangeOverride(Size finalSize)
- {
- using (BeginAutoSizing())
- {
- PlatformImpl?.Resize(finalSize);
- }
-
- return base.ArrangeOverride(PlatformImpl?.ClientSize ?? default(Size));
- }
-
///
/// Ensures that the window is initialized.
///
@@ -318,16 +290,5 @@ namespace Avalonia.Controls
}
}
}
-
- ///
- /// Starts moving a window with left button being held. Should be called from left mouse button press event handler
- ///
- public void BeginMoveDrag() => PlatformImpl?.BeginMoveDrag();
-
- ///
- /// 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
- ///
- public void BeginResizeDrag(WindowEdge edge) => PlatformImpl?.BeginResizeDrag(edge);
}
}
diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
index dc01bcb07e..40524ad4b7 100644
--- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowImpl.cs
+++ b/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)
{
}
diff --git a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs b/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
index a7a94130ea..dcfcd42c04 100644
--- a/src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
+++ b/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;
diff --git a/src/Avalonia.DesignerSupport/Remote/Stubs.cs b/src/Avalonia.DesignerSupport/Remote/Stubs.cs
index ddb8b62b6a..4ce0da60a2 100644
--- a/src/Avalonia.DesignerSupport/Remote/Stubs.cs
+++ b/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 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 PositionChanged { get; set; }
public WindowState WindowState { get; set; }
public Action 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
diff --git a/src/Avalonia.Native/PopupImpl.cs b/src/Avalonia.Native/PopupImpl.cs
index 976208b058..f776ee0132 100644
--- a/src/Avalonia.Native/PopupImpl.cs
+++ b/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; }
}
}
diff --git a/src/Avalonia.Native/WindowImpl.cs b/src/Avalonia.Native/WindowImpl.cs
index c7857898d2..e4c158eeb3 100644
--- a/src/Avalonia.Native/WindowImpl.cs
+++ b/src/Avalonia.Native/WindowImpl.cs
@@ -104,6 +104,8 @@ namespace Avalonia.Native
}
public Func 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);
}
}
diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs
index a1e386892b..7ca2672d2b 100644
--- a/src/Avalonia.X11/X11Window.cs
+++ b/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 _transientChildren = new HashSet();
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; }
}
}
diff --git a/src/Windows/Avalonia.Win32/PopupImpl.cs b/src/Windows/Avalonia.Win32/PopupImpl.cs
index 39f1a95466..c9aa1ce4e7 100644
--- a/src/Windows/Avalonia.Win32/PopupImpl.cs
+++ b/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; }
}
}
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index 2f7805884d..21625af84a 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/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()
diff --git a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs b/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
index 3ee6a50e69..55e8ae0115 100644
--- a/tests/Avalonia.Controls.UnitTests/WindowBaseTests.cs
+++ b/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(x => x.Scaling == 1);
-
- Mock.Get(impl).Setup(x => x.Resize(It.IsAny())).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()
{
diff --git a/tests/Avalonia.Controls.UnitTests/WindowTests.cs b/tests/Avalonia.Controls.UnitTests/WindowTests.cs
index f4d9a91d0c..cbcf08049e 100644
--- a/tests/Avalonia.Controls.UnitTests/WindowTests.cs
+++ b/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(x => x.Scaling == 1);
+
+ Mock.Get(impl).Setup(x => x.Resize(It.IsAny())).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()
{