Browse Source

Merge pull request #7165 from MarchingCube/platform-screen-api

Add more platform specific screen from Window/Rect/Point methods.
# Conflicts:
#	src/Avalonia.Controls/ApiCompatBaseline.txt
#	src/Avalonia.Controls/Screens.cs
pull/7930/head
Dan Walmsley 4 years ago
parent
commit
e84a13f3ac
  1. 5
      src/Avalonia.Controls/ApiCompatBaseline.txt
  2. 8
      src/Avalonia.Controls/Platform/IScreenImpl.cs
  3. 54
      src/Avalonia.Controls/Platform/ScreenHelper.cs
  4. 4
      src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs
  5. 35
      src/Avalonia.Controls/Screens.cs
  6. 14
      src/Avalonia.Controls/Window.cs
  7. 2
      src/Avalonia.Controls/WindowBase.cs
  8. 15
      src/Avalonia.DesignerSupport/Remote/Stubs.cs
  9. 15
      src/Avalonia.Headless/HeadlessPlatformStubs.cs
  10. 15
      src/Avalonia.Native/ScreenImpl.cs
  11. 12
      src/Avalonia.Visuals/Media/PixelRect.cs
  12. 12
      src/Avalonia.Visuals/Rect.cs
  13. 15
      src/Avalonia.X11/X11Screens.cs
  14. 15
      src/Web/Avalonia.Web.Blazor/WinStubs.cs
  15. 39
      src/Windows/Avalonia.Win32/ScreenImpl.cs
  16. 6
      src/Windows/Avalonia.Win32/WinScreen.cs
  17. 2
      tests/Avalonia.Controls.UnitTests/WindowTests.cs

5
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -16,6 +16,9 @@ MembersMustExist : Member 'public Avalonia.AvaloniaProperty Avalonia.AvaloniaPro
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.Platform.ITopLevelNativeMenuExporter.SetNativeMenu(Avalonia.Controls.NativeMenu)' is present in the contract but not in the implementation. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Controls.Platform.ITopLevelNativeMenuExporter.SetNativeMenu(Avalonia.Controls.NativeMenu)' is present in the contract but not in the implementation.
CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.Primitives.PopupRoot' does not implement interface 'Avalonia.Utilities.IWeakSubscriber<Avalonia.Controls.ResourcesChangedEventArgs>' in the implementation but it does in the contract. CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.Primitives.PopupRoot' does not implement interface 'Avalonia.Utilities.IWeakSubscriber<Avalonia.Controls.ResourcesChangedEventArgs>' in the implementation but it does in the contract.
EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract. EnumValuesMustMatch : Enum value 'Avalonia.Platform.ExtendClientAreaChromeHints Avalonia.Platform.ExtendClientAreaChromeHints.Default' is (System.Int32)2 in the implementation but (System.Int32)1 in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.Screen Avalonia.Platform.IScreenImpl.ScreenFromPoint(Avalonia.PixelPoint)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.Screen Avalonia.Platform.IScreenImpl.ScreenFromRect(Avalonia.PixelRect)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.Screen Avalonia.Platform.IScreenImpl.ScreenFromWindow(Avalonia.Platform.IWindowBaseImpl)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize.get()' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Nullable<Avalonia.Size> Avalonia.Platform.ITopLevelImpl.FrameSize.get()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.Action<Avalonia.Size, Avalonia.Platform.PlatformResizeReason> Avalonia.Platform.ITopLevelImpl.Resized.get()' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public System.Action<Avalonia.Size, Avalonia.Platform.PlatformResizeReason> Avalonia.Platform.ITopLevelImpl.Resized.get()' is present in the implementation but not in the contract.
@ -34,4 +37,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platfor
MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract. MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.ITrayIconImpl Avalonia.Platform.IWindowingPlatform.CreateTrayIcon()' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.ITrayIconImpl Avalonia.Platform.IWindowingPlatform.CreateTrayIcon()' is present in the implementation but not in the contract.
Total Issues: 35 Total Issues: 73

8
src/Avalonia.Controls/Platform/IScreenImpl.cs

@ -1,5 +1,7 @@
using System.Collections.Generic; using System.Collections.Generic;
#nullable enable
namespace Avalonia.Platform namespace Avalonia.Platform
{ {
public interface IScreenImpl public interface IScreenImpl
@ -7,5 +9,11 @@ namespace Avalonia.Platform
int ScreenCount { get; } int ScreenCount { get; }
IReadOnlyList<Screen> AllScreens { get; } IReadOnlyList<Screen> AllScreens { get; }
Screen? ScreenFromWindow(IWindowBaseImpl window);
Screen? ScreenFromPoint(PixelPoint point);
Screen? ScreenFromRect(PixelRect rect);
} }
} }

54
src/Avalonia.Controls/Platform/ScreenHelper.cs

@ -0,0 +1,54 @@
using System.Collections.Generic;
using Avalonia.Utilities;
#nullable enable
namespace Avalonia.Platform
{
public static class ScreenHelper
{
public static Screen? ScreenFromPoint(PixelPoint point, IReadOnlyList<Screen> screens)
{
foreach (Screen screen in screens)
{
if (screen.Bounds.ContainsExclusive(point))
{
return screen;
}
}
return null;
}
public static Screen? ScreenFromRect(PixelRect bounds, IReadOnlyList<Screen> screens)
{
Screen? currMaxScreen = null;
double maxAreaSize = 0;
foreach (Screen screen in screens)
{
double left = MathUtilities.Clamp(bounds.X, screen.Bounds.X, screen.Bounds.X + screen.Bounds.Width);
double top = MathUtilities.Clamp(bounds.Y, screen.Bounds.Y, screen.Bounds.Y + screen.Bounds.Height);
double right = MathUtilities.Clamp(bounds.X + bounds.Width, screen.Bounds.X, screen.Bounds.X + screen.Bounds.Width);
double bottom = MathUtilities.Clamp(bounds.Y + bounds.Height, screen.Bounds.Y, screen.Bounds.Y + screen.Bounds.Height);
double area = (right - left) * (bottom - top);
if (area > maxAreaSize)
{
maxAreaSize = area;
currMaxScreen = screen;
}
}
return currMaxScreen;
}
public static Screen? ScreenFromWindow(IWindowBaseImpl window, IReadOnlyList<Screen> screens)
{
var rect = new PixelRect(
window.Position,
PixelSize.FromSize(window.FrameSize ?? window.ClientSize, window.DesktopScaling));
return ScreenFromRect(rect, screens);
}
}
}

4
src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs

@ -106,9 +106,9 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
{ {
var screens = _popup.Screens; var screens = _popup.Screens;
var targetScreen = screens.FirstOrDefault(s => s.Bounds.Contains(anchorRect.TopLeft)) var targetScreen = screens.FirstOrDefault(s => s.Bounds.ContainsExclusive(anchorRect.TopLeft))
?? screens.FirstOrDefault(s => s.Bounds.Intersects(anchorRect)) ?? screens.FirstOrDefault(s => s.Bounds.Intersects(anchorRect))
?? screens.FirstOrDefault(s => s.Bounds.Contains(parentGeometry.TopLeft)) ?? screens.FirstOrDefault(s => s.Bounds.ContainsExclusive(parentGeometry.TopLeft))
?? screens.FirstOrDefault(s => s.Bounds.Intersects(parentGeometry)) ?? screens.FirstOrDefault(s => s.Bounds.Intersects(parentGeometry))
?? screens.FirstOrDefault(); ?? screens.FirstOrDefault();

35
src/Avalonia.Controls/Screens.cs

@ -2,9 +2,10 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Utilities;
using Avalonia.VisualTree; using Avalonia.VisualTree;
#nullable enable
namespace Avalonia.Controls namespace Avalonia.Controls
{ {
public class Screens public class Screens
@ -20,36 +21,26 @@ namespace Avalonia.Controls
_iScreenImpl = iScreenImpl; _iScreenImpl = iScreenImpl;
} }
public Screen ScreenFromBounds(PixelRect bounds){ public Screen? ScreenFromBounds(PixelRect bounds)
{
Screen currMaxScreen = null; return _iScreenImpl.ScreenFromRect(bounds);
double maxAreaSize = 0;
foreach (Screen screen in All)
{
double left = MathUtilities.Clamp(bounds.X, screen.Bounds.X, screen.Bounds.X + screen.Bounds.Width);
double top = MathUtilities.Clamp(bounds.Y, screen.Bounds.Y, screen.Bounds.Y + screen.Bounds.Height);
double right = MathUtilities.Clamp(bounds.X + bounds.Width, screen.Bounds.X, screen.Bounds.X + screen.Bounds.Width);
double bottom = MathUtilities.Clamp(bounds.Y + bounds.Height, screen.Bounds.Y, screen.Bounds.Y + screen.Bounds.Height);
double area = (right - left) * (bottom - top);
if (area > maxAreaSize)
{
maxAreaSize = area;
currMaxScreen = screen;
}
}
return currMaxScreen;
} }
public Screen ScreenFromPoint(PixelPoint point) public Screen? ScreenFromWindow(IWindowBaseImpl window)
{ {
return All.FirstOrDefault(x => x.Bounds.Contains(point)); return _iScreenImpl.ScreenFromWindow(window);
}
public Screen? ScreenFromPoint(PixelPoint point)
{
return _iScreenImpl.ScreenFromPoint(point);
} }
public Screen ScreenFromVisual(IVisual visual) public Screen ScreenFromVisual(IVisual visual)
{ {
var tl = visual.PointToScreen(visual.Bounds.TopLeft); var tl = visual.PointToScreen(visual.Bounds.TopLeft);
var br = visual.PointToScreen(visual.Bounds.BottomRight); var br = visual.PointToScreen(visual.Bounds.BottomRight);
return ScreenFromBounds(new PixelRect(tl, br)); return ScreenFromBounds(new PixelRect(tl, br));
} }
} }

14
src/Avalonia.Controls/Window.cs

@ -879,7 +879,19 @@ namespace Avalonia.Controls
if (startupLocation == WindowStartupLocation.CenterScreen) if (startupLocation == WindowStartupLocation.CenterScreen)
{ {
var screen = Screens.ScreenFromPoint(owner?.Position ?? Position); Screen? screen = null;
if (owner is not null)
{
screen = Screens.ScreenFromWindow(owner);
screen ??= Screens.ScreenFromPoint(owner.Position);
}
if (screen is null)
{
screen = Screens.ScreenFromPoint(Position);
}
if (screen != null) if (screen != null)
{ {

2
src/Avalonia.Controls/WindowBase.cs

@ -59,7 +59,7 @@ namespace Avalonia.Controls
public WindowBase(IWindowBaseImpl impl, IAvaloniaDependencyResolver dependencyResolver) : base(impl, dependencyResolver) public WindowBase(IWindowBaseImpl impl, IAvaloniaDependencyResolver dependencyResolver) : base(impl, dependencyResolver)
{ {
Screens = new Screens(PlatformImpl?.Screen); Screens = new Screens(impl.Screen);
impl.Activated = HandleActivated; impl.Activated = HandleActivated;
impl.Deactivated = HandleDeactivated; impl.Deactivated = HandleDeactivated;
impl.PositionChanged = HandlePositionChanged; impl.PositionChanged = HandlePositionChanged;

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

@ -236,5 +236,20 @@ namespace Avalonia.DesignerSupport.Remote
public IReadOnlyList<Screen> AllScreens { get; } = public IReadOnlyList<Screen> AllScreens { get; } =
new Screen[] { new Screen(1, new PixelRect(0, 0, 4000, 4000), new PixelRect(0, 0, 4000, 4000), true) }; new Screen[] { new Screen(1, new PixelRect(0, 0, 4000, 4000), new PixelRect(0, 0, 4000, 4000), true) };
public Screen ScreenFromPoint(PixelPoint point)
{
return ScreenHelper.ScreenFromPoint(point, AllScreens);
}
public Screen ScreenFromRect(PixelRect rect)
{
return ScreenHelper.ScreenFromRect(rect, AllScreens);
}
public Screen ScreenFromWindow(IWindowBaseImpl window)
{
return ScreenHelper.ScreenFromWindow(window, AllScreens);
}
} }
} }

15
src/Avalonia.Headless/HeadlessPlatformStubs.cs

@ -199,5 +199,20 @@ namespace Avalonia.Headless
new Screen(1, new PixelRect(0, 0, 1920, 1280), new Screen(1, new PixelRect(0, 0, 1920, 1280),
new PixelRect(0, 0, 1920, 1280), true), new PixelRect(0, 0, 1920, 1280), true),
}; };
public Screen ScreenFromPoint(PixelPoint point)
{
return ScreenHelper.ScreenFromPoint(point, AllScreens);
}
public Screen ScreenFromRect(PixelRect rect)
{
return ScreenHelper.ScreenFromRect(rect, AllScreens);
}
public Screen ScreenFromWindow(IWindowBaseImpl window)
{
return ScreenHelper.ScreenFromWindow(window, AllScreens);
}
} }
} }

15
src/Avalonia.Native/ScreenImpl.cs

@ -48,5 +48,20 @@ namespace Avalonia.Native
_native?.Dispose(); _native?.Dispose();
_native = null; _native = null;
} }
public Screen ScreenFromPoint(PixelPoint point)
{
return ScreenHelper.ScreenFromPoint(point, AllScreens);
}
public Screen ScreenFromRect(PixelRect rect)
{
return ScreenHelper.ScreenFromRect(rect, AllScreens);
}
public Screen ScreenFromWindow(IWindowBaseImpl window)
{
return ScreenHelper.ScreenFromWindow(window, AllScreens);
}
} }
} }

12
src/Avalonia.Visuals/Media/PixelRect.cs

@ -168,6 +168,18 @@ namespace Avalonia
{ {
return p.X >= X && p.X <= Right && p.Y >= Y && p.Y <= Bottom; return p.X >= X && p.X <= Right && p.Y >= Y && p.Y <= Bottom;
} }
/// <summary>
/// Determines whether a point is in the bounds of the rectangle, exclusive of the
/// rectangle's bottom/right edge.
/// </summary>
/// <param name="p">The point.</param>
/// <returns>true if the point is in the bounds of the rectangle; otherwise false.</returns>
public bool ContainsExclusive(PixelPoint p)
{
return p.X >= X && p.X < X + Width &&
p.Y >= Y && p.Y < Y + Height;
}
/// <summary> /// <summary>
/// Determines whether the rectangle fully contains another rectangle. /// Determines whether the rectangle fully contains another rectangle.

12
src/Avalonia.Visuals/Rect.cs

@ -252,6 +252,18 @@ namespace Avalonia
return p.X >= _x && p.X <= _x + _width && return p.X >= _x && p.X <= _x + _width &&
p.Y >= _y && p.Y <= _y + _height; p.Y >= _y && p.Y <= _y + _height;
} }
/// <summary>
/// Determines whether a point is in the bounds of the rectangle, exclusive of the
/// rectangle's bottom/right edge.
/// </summary>
/// <param name="p">The point.</param>
/// <returns>true if the point is in the bounds of the rectangle; otherwise false.</returns>
public bool ContainsExclusive(Point p)
{
return p.X >= _x && p.X < _x + _width &&
p.Y >= _y && p.Y < _y + _height;
}
/// <summary> /// <summary>
/// Determines whether the rectangle fully contains another rectangle. /// Determines whether the rectangle fully contains another rectangle.

15
src/Avalonia.X11/X11Screens.cs

@ -200,6 +200,21 @@ namespace Avalonia.X11
} }
public Screen ScreenFromPoint(PixelPoint point)
{
return ScreenHelper.ScreenFromPoint(point, AllScreens);
}
public Screen ScreenFromRect(PixelRect rect)
{
return ScreenHelper.ScreenFromRect(rect, AllScreens);
}
public Screen ScreenFromWindow(IWindowBaseImpl window)
{
return ScreenHelper.ScreenFromWindow(window, AllScreens);
}
public int ScreenCount => _impl.Screens.Length; public int ScreenCount => _impl.Screens.Length;
public IReadOnlyList<Screen> AllScreens => public IReadOnlyList<Screen> AllScreens =>

15
src/Web/Avalonia.Web.Blazor/WinStubs.cs

@ -55,5 +55,20 @@ namespace Avalonia.Web.Blazor
public IReadOnlyList<Screen> AllScreens { get; } = public IReadOnlyList<Screen> AllScreens { get; } =
new[] { new Screen(96, new PixelRect(0, 0, 4000, 4000), new PixelRect(0, 0, 4000, 4000), true) }; new[] { new Screen(96, new PixelRect(0, 0, 4000, 4000), new PixelRect(0, 0, 4000, 4000), true) };
public Screen? ScreenFromPoint(PixelPoint point)
{
return ScreenHelper.ScreenFromPoint(point, AllScreens);
}
public Screen? ScreenFromRect(PixelRect rect)
{
return ScreenHelper.ScreenFromRect(rect, AllScreens);
}
public Screen? ScreenFromWindow(IWindowBaseImpl window)
{
return ScreenHelper.ScreenFromWindow(window, AllScreens);
}
} }
} }

39
src/Windows/Avalonia.Win32/ScreenImpl.cs

@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Win32.Interop; using Avalonia.Win32.Interop;
using static Avalonia.Win32.Interop.UnmanagedMethods; using static Avalonia.Win32.Interop.UnmanagedMethods;
@ -70,5 +71,43 @@ namespace Avalonia.Win32
{ {
_allScreens = null; _allScreens = null;
} }
public Screen ScreenFromWindow(IWindowBaseImpl window)
{
var handle = window.Handle.Handle;
var monitor = MonitorFromWindow(handle, MONITOR.MONITOR_DEFAULTTONULL);
return FindScreenByHandle(monitor);
}
public Screen ScreenFromPoint(PixelPoint point)
{
var monitor = MonitorFromPoint(new POINT
{
X = point.X,
Y = point.Y
}, MONITOR.MONITOR_DEFAULTTONULL);
return FindScreenByHandle(monitor);
}
public Screen ScreenFromRect(PixelRect rect)
{
var monitor = MonitorFromRect(new RECT
{
left = rect.TopLeft.X,
top = rect.TopLeft.Y,
right = rect.TopRight.X,
bottom = rect.BottomRight.Y
}, MONITOR.MONITOR_DEFAULTTONULL);
return FindScreenByHandle(monitor);
}
private Screen FindScreenByHandle(IntPtr handle)
{
return AllScreens.Cast<WinScreen>().FirstOrDefault(m => m.Handle == handle);
}
} }
} }

6
src/Windows/Avalonia.Win32/WinScreen.cs

@ -9,9 +9,11 @@ namespace Avalonia.Win32
public WinScreen(double pixelDensity, PixelRect bounds, PixelRect workingArea, bool primary, IntPtr hMonitor) : base(pixelDensity, bounds, workingArea, primary) public WinScreen(double pixelDensity, PixelRect bounds, PixelRect workingArea, bool primary, IntPtr hMonitor) : base(pixelDensity, bounds, workingArea, primary)
{ {
this._hMonitor = hMonitor; _hMonitor = hMonitor;
} }
public IntPtr Handle => _hMonitor;
public override int GetHashCode() public override int GetHashCode()
{ {
return (int)_hMonitor; return (int)_hMonitor;
@ -19,7 +21,7 @@ namespace Avalonia.Win32
public override bool Equals(object obj) public override bool Equals(object obj)
{ {
return (obj is WinScreen screen) ? this._hMonitor == screen._hMonitor : base.Equals(obj); return (obj is WinScreen screen) ? _hMonitor == screen._hMonitor : base.Equals(obj);
} }
} }
} }

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

@ -516,6 +516,8 @@ namespace Avalonia.Controls.UnitTests
var screens = new Mock<IScreenImpl>(); var screens = new Mock<IScreenImpl>();
screens.Setup(x => x.AllScreens).Returns(new Screen[] { screen1.Object, screen2.Object }); screens.Setup(x => x.AllScreens).Returns(new Screen[] { screen1.Object, screen2.Object });
screens.Setup(x => x.ScreenFromPoint(It.IsAny<PixelPoint>())).Returns(screen1.Object);
var windowImpl = MockWindowingPlatform.CreateWindowMock(); var windowImpl = MockWindowingPlatform.CreateWindowMock();
windowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480)); windowImpl.Setup(x => x.ClientSize).Returns(new Size(800, 480));

Loading…
Cancel
Save