diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj
index 76114c385b..340905a7f7 100644
--- a/samples/ControlCatalog/ControlCatalog.csproj
+++ b/samples/ControlCatalog/ControlCatalog.csproj
@@ -136,7 +136,7 @@
SliderPage.xaml
-
+
TreeViewPage.xaml
@@ -146,6 +146,7 @@
ToolTipPage.xaml
+
diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs
index a3b8708c29..0be5d25a09 100644
--- a/samples/ControlCatalog/MainView.xaml.cs
+++ b/samples/ControlCatalog/MainView.xaml.cs
@@ -13,11 +13,20 @@ namespace ControlCatalog
{
this.InitializeComponent();
if (AvaloniaLocator.Current.GetService().GetRuntimeInfo().IsDesktop)
- ((IList) this.FindControl("Sidebar").Items).Add(new TabItem()
+ {
+ IList tabItems = ((IList)this.FindControl("Sidebar").Items);
+ tabItems.Add(new TabItem()
{
Header = "Dialogs",
Content = new DialogsPage()
});
+ tabItems.Add(new TabItem()
+ {
+ Header = "Screens",
+ Content = new ScreenPage()
+ });
+
+ }
}
private void InitializeComponent()
diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs
new file mode 100644
index 0000000000..34aa85b8aa
--- /dev/null
+++ b/samples/ControlCatalog/Pages/ScreenPage.cs
@@ -0,0 +1,62 @@
+using System;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using Avalonia.Media;
+using Avalonia.Platform;
+
+namespace ControlCatalog.Pages
+{
+ public class ScreenPage : UserControl
+ {
+ private double _leftMost;
+
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToVisualTree(e);
+ Window w = (Window)VisualRoot;
+ w.PositionChanged += (sender, args) => InvalidateVisual();
+ }
+
+ public override void Render(DrawingContext context)
+ {
+ base.Render(context);
+ Window w = (Window)VisualRoot;
+ Screen[] screens = w.Screens.All;
+
+ Pen p = new Pen(Brushes.Black);
+ if (screens != null)
+ foreach (Screen screen in screens)
+ {
+ if (screen.Bounds.X / 10f < _leftMost)
+ {
+ _leftMost = screen.Bounds.X / 10f;
+ InvalidateVisual();
+ return;
+ }
+
+ Rect boundsRect = new Rect(screen.Bounds.X / 10f + Math.Abs(_leftMost), screen.Bounds.Y / 10f, screen.Bounds.Width / 10f,
+ screen.Bounds.Height / 10f);
+ Rect workingAreaRect = new Rect(screen.WorkingArea.X / 10f + Math.Abs(_leftMost), screen.WorkingArea.Y / 10f, screen.WorkingArea.Width / 10f,
+ screen.WorkingArea.Height / 10f);
+ context.DrawRectangle(p, boundsRect);
+ context.DrawRectangle(p, workingAreaRect);
+
+ FormattedText text = new FormattedText();
+ text.Text = $"Bounds: {screen.Bounds.Width}:{screen.Bounds.Height}";
+ context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height), text);
+
+ text.Text = $"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}";
+ context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 20), text);
+
+ text.Text = $"Primary: {screen.Primary}";
+ context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 40), text);
+
+ text.Text = $"Current: {screen.Equals(w.Screens.ScreenFromBounds(new Rect(w.Position, w.Bounds.Size)))}";
+ context.DrawText(Brushes.Black, boundsRect.Position.WithY(boundsRect.Size.Height + 60), text);
+ }
+
+ context.DrawRectangle(p, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10, w.Bounds.Width / 10, w.Bounds.Height / 10));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs b/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs
index efbc71b808..0b683239fb 100644
--- a/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs
+++ b/src/Android/Avalonia.Android/Platform/SkiaPlatform/PopupImpl.cs
@@ -36,6 +36,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_clientSize = value;
UpdateParams();
}
+
+ public IScreenImpl Screen { get; }
public Point Position
{
diff --git a/src/Avalonia.Controls/Platform/IScreenImpl.cs b/src/Avalonia.Controls/Platform/IScreenImpl.cs
new file mode 100644
index 0000000000..dafa1de473
--- /dev/null
+++ b/src/Avalonia.Controls/Platform/IScreenImpl.cs
@@ -0,0 +1,9 @@
+namespace Avalonia.Platform
+{
+ public interface IScreenImpl
+ {
+ int ScreenCount { get; }
+
+ Screen[] AllScreens { get; }
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
index a5c7c60843..0a01cf3df4 100644
--- a/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
+++ b/src/Avalonia.Controls/Platform/IWindowBaseImpl.cs
@@ -65,5 +65,10 @@ namespace Avalonia.Platform
/// Sets the client size of the toplevel.
///
void Resize(Size clientSize);
+
+ ///
+ /// Gets platform specific display information
+ ///
+ IScreenImpl Screen { get; }
}
}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Platform/Screen.cs b/src/Avalonia.Controls/Platform/Screen.cs
new file mode 100644
index 0000000000..5aa8c2c392
--- /dev/null
+++ b/src/Avalonia.Controls/Platform/Screen.cs
@@ -0,0 +1,18 @@
+namespace Avalonia.Platform
+{
+ public class Screen
+ {
+ public Rect Bounds { get; }
+
+ public Rect WorkingArea { get; }
+
+ public bool Primary { get; }
+
+ public Screen(Rect bounds, Rect workingArea, bool primary)
+ {
+ this.Bounds = bounds;
+ this.WorkingArea = workingArea;
+ this.Primary = primary;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Screens.cs b/src/Avalonia.Controls/Screens.cs
new file mode 100644
index 0000000000..b8ddce3aea
--- /dev/null
+++ b/src/Avalonia.Controls/Screens.cs
@@ -0,0 +1,54 @@
+using System.Linq;
+using Avalonia.Platform;
+using Avalonia.Utilities;
+using Avalonia.VisualTree;
+
+namespace Avalonia.Controls
+{
+ public class Screens
+ {
+ private readonly IScreenImpl _iScreenImpl;
+
+ public int ScreenCount => _iScreenImpl.ScreenCount;
+ public Screen[] All => _iScreenImpl?.AllScreens;
+ public Screen Primary => All.FirstOrDefault(x => x.Primary);
+
+ public Screens(IScreenImpl iScreenImpl)
+ {
+ _iScreenImpl = iScreenImpl;
+ }
+
+ public Screen ScreenFromBounds(Rect bounds){
+
+ Screen currMaxScreen = null;
+ 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 SceenFromPoint(Point point)
+ {
+ return All.FirstOrDefault(x=>x.Bounds.Contains(point));
+ }
+
+ public Screen ScreenFromVisual(IVisual visual)
+ {
+ Point tl = visual.PointToScreen(visual.Bounds.TopLeft);
+ Point br = visual.PointToScreen(visual.Bounds.BottomRight);
+ return ScreenFromBounds(new Rect(tl,br));
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Avalonia.Controls/Window.cs b/src/Avalonia.Controls/Window.cs
index 9c0245a714..64912f20df 100644
--- a/src/Avalonia.Controls/Window.cs
+++ b/src/Avalonia.Controls/Window.cs
@@ -119,6 +119,7 @@ namespace Avalonia.Controls
: base(impl)
{
_maxPlatformClientSize = PlatformImpl?.MaxClientSize ?? default(Size);
+ Screens = new Screens(PlatformImpl?.Screen);
}
///
@@ -135,6 +136,8 @@ namespace Avalonia.Controls
remove { _nameScope.Unregistered -= value; }
}
+ public Screens Screens { get; private set; }
+
///
/// Gets the platform-specific window implementation.
///
diff --git a/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj b/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj
index 6049869424..1142b32dbf 100644
--- a/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj
+++ b/src/Gtk/Avalonia.Gtk/Avalonia.Gtk.csproj
@@ -48,6 +48,7 @@
+
diff --git a/src/Gtk/Avalonia.Gtk/ScreenImpl.cs b/src/Gtk/Avalonia.Gtk/ScreenImpl.cs
new file mode 100644
index 0000000000..a5082ca59a
--- /dev/null
+++ b/src/Gtk/Avalonia.Gtk/ScreenImpl.cs
@@ -0,0 +1,42 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Platform;
+using Avalonia.Utilities;
+using Gdk;
+using Screen = Avalonia.Platform.Screen;
+using Window = Gtk.Window;
+
+namespace Avalonia.Gtk
+{
+ internal class ScreenImpl : IScreenImpl
+ {
+ private Window window;
+
+ public int ScreenCount
+ {
+ get => window.Display.DefaultScreen.NMonitors;
+ }
+ public Screen[] AllScreens {
+ get
+ {
+ Screen[] screens = new Screen[ScreenCount];
+ var screen = window.Display.DefaultScreen;
+
+ for (short i = 0; i < screens.Length; i++)
+ {
+ Rectangle geometry = screen.GetMonitorGeometry(i);
+ Rect geometryRect = new Rect(geometry.X, geometry.Y, geometry.Width, geometry.Height);
+ Screen s = new Screen(geometryRect, geometryRect, false);
+ screens[i] = s;
+ }
+
+ return screens;
+ }
+ }
+
+ public ScreenImpl(Window window)
+ {
+ this.window = window;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Gtk/Avalonia.Gtk/WindowImpl.cs b/src/Gtk/Avalonia.Gtk/WindowImpl.cs
index 2c1826c509..ef8f5f95cb 100644
--- a/src/Gtk/Avalonia.Gtk/WindowImpl.cs
+++ b/src/Gtk/Avalonia.Gtk/WindowImpl.cs
@@ -1,5 +1,6 @@
using System;
using System.Reactive.Disposables;
+using Avalonia.Controls;
using Avalonia.Platform;
using Gdk;
@@ -66,6 +67,8 @@ namespace Avalonia.Gtk
}
}
+ public IScreenImpl Screen => new ScreenImpl(Window);
+
public void Resize(Size value)
{
Window.Resize((int)value.Width, (int)value.Height);
diff --git a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj
index b58a83b3d2..59bd58916b 100644
--- a/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj
+++ b/src/Gtk/Avalonia.Gtk3/Avalonia.Gtk3.csproj
@@ -53,6 +53,8 @@
+
+
diff --git a/src/Gtk/Avalonia.Gtk3/GtkScreen.cs b/src/Gtk/Avalonia.Gtk3/GtkScreen.cs
new file mode 100644
index 0000000000..7c28dd47b9
--- /dev/null
+++ b/src/Gtk/Avalonia.Gtk3/GtkScreen.cs
@@ -0,0 +1,24 @@
+using Avalonia.Platform;
+
+namespace Avalonia.Gtk3
+{
+ public class GtkScreen : Screen
+ {
+ private readonly int _screenId;
+
+ public GtkScreen(Rect bounds, Rect workingArea, bool primary, int screenId) : base(bounds, workingArea, primary)
+ {
+ this._screenId = screenId;
+ }
+
+ public override int GetHashCode()
+ {
+ return _screenId;
+ }
+
+ public override bool Equals(object obj)
+ {
+ return (obj is GtkScreen screen) ? this._screenId == screen._screenId : base.Equals(obj);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Gtk/Avalonia.Gtk3/Interop/GObject.cs b/src/Gtk/Avalonia.Gtk3/Interop/GObject.cs
index 9766e8ca36..8d14515d28 100644
--- a/src/Gtk/Avalonia.Gtk3/Interop/GObject.cs
+++ b/src/Gtk/Avalonia.Gtk3/Interop/GObject.cs
@@ -46,7 +46,30 @@ namespace Avalonia.Gtk3.Interop
class GtkImContext : GObject
{
-
+ }
+
+ class GdkScreen : GObject
+ {
+ public GdkScreen() : base(IntPtr.Zero, false)
+ {
+ }
+
+ public GdkScreen(IntPtr handle, bool owned = true) : base(handle, owned)
+ {
+ this.handle = handle;
+ }
+ }
+
+ class UnownedGdkScreen : GdkScreen
+ {
+ public UnownedGdkScreen() : base(IntPtr.Zero, false)
+ {
+ }
+
+ public UnownedGdkScreen(IntPtr handle, bool owned = true) : base(IntPtr.Zero, false)
+ {
+ this.handle = handle;
+ }
}
class GtkDialog : GtkWindow
diff --git a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs
index ffc1d380a3..35aef7b4e5 100644
--- a/src/Gtk/Avalonia.Gtk3/Interop/Native.cs
+++ b/src/Gtk/Avalonia.Gtk3/Interop/Native.cs
@@ -20,6 +20,27 @@ namespace Avalonia.Gtk3.Interop
{
public static class D
{
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
+ public delegate gint16 gdk_display_get_n_screens(IntPtr display);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
+ public delegate UnownedGdkScreen gdk_display_get_screen(IntPtr display, gint16 num);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
+ public delegate UnownedGdkScreen gdk_display_get_default_screen (IntPtr display);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
+ public delegate gint16 gdk_screen_get_n_monitors(GdkScreen screen);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
+ public delegate gint16 gdk_screen_get_primary_monitor(GdkScreen screen);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
+ public delegate void gdk_screen_get_monitor_geometry(GdkScreen screen, gint16 num, ref GdkRectangle rect);
+
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gdk)]
+ public delegate void gdk_screen_get_monitor_workarea(GdkScreen screen, gint16 num, ref GdkRectangle rect);
+
[UnmanagedFunctionPointer(CallingConvention.Cdecl), GtkImport(GtkDll.Gtk)]
public delegate IntPtr gtk_application_new(Utf8Buffer appId, int flags);
@@ -288,6 +309,9 @@ namespace Avalonia.Gtk3.Interop
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate bool signal_onevent(IntPtr gtkWidget, IntPtr ev, IntPtr userData);
+ [UnmanagedFunctionPointer(CallingConvention.Cdecl)]
+ public delegate void monitors_changed(IntPtr screen, IntPtr userData);
+
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate bool signal_commit(IntPtr gtkWidget, IntPtr utf8string, IntPtr userData);
@@ -298,7 +322,13 @@ namespace Avalonia.Gtk3.Interop
public delegate void GtkClipboardTextReceivedFunc(IntPtr clipboard, IntPtr utf8string, IntPtr userdata);
}
-
+ public static D.gdk_display_get_n_screens GdkDisplayGetNScreens;
+ public static D.gdk_display_get_screen GdkDisplayGetScreen;
+ public static D.gdk_display_get_default_screen GdkDisplayGetDefaultScreen;
+ public static D.gdk_screen_get_n_monitors GdkScreenGetNMonitors;
+ public static D.gdk_screen_get_primary_monitor GdkScreenGetPrimaryMonitor;
+ public static D.gdk_screen_get_monitor_geometry GdkScreenGetMonitorGeometry;
+ public static D.gdk_screen_get_monitor_workarea GdkScreenGetMonitorWorkarea;
public static D.gtk_window_set_decorated GtkWindowSetDecorated;
public static D.gtk_window_set_skip_taskbar_hint GtkWindowSetSkipTaskbarHint;
public static D.gtk_window_get_skip_taskbar_hint GtkWindowGetSkipTaskbarHint;
diff --git a/src/Gtk/Avalonia.Gtk3/ScreenImpl.cs b/src/Gtk/Avalonia.Gtk3/ScreenImpl.cs
new file mode 100644
index 0000000000..0f4fbd54bd
--- /dev/null
+++ b/src/Gtk/Avalonia.Gtk3/ScreenImpl.cs
@@ -0,0 +1,57 @@
+using System;
+using Avalonia.Controls;
+using Avalonia.Gtk3.Interop;
+using Avalonia.Platform;
+using Avalonia.Utilities;
+
+namespace Avalonia.Gtk3
+{
+ internal class ScreenImpl : IScreenImpl
+ {
+ public int ScreenCount
+ {
+ get => _allScreens.Length;
+ }
+
+ private Screen[] _allScreens;
+ public Screen[] AllScreens
+ {
+ get
+ {
+ if (_allScreens == null)
+ {
+ IntPtr display = Native.GdkGetDefaultDisplay();
+ GdkScreen screen = Native.GdkDisplayGetDefaultScreen(display);
+ short primary = Native.GdkScreenGetPrimaryMonitor(screen);
+ Screen[] screens = new Screen[ScreenCount];
+ for (short i = 0; i < screens.Length; i++)
+ {
+ GdkRectangle workArea = new GdkRectangle(), geometry = new GdkRectangle();
+ Native.GdkScreenGetMonitorGeometry(screen, i, ref geometry);
+ Native.GdkScreenGetMonitorWorkarea(screen, i, ref workArea);
+ Rect workAreaRect = new Rect(workArea.X, workArea.Y, workArea.Width, workArea.Height);
+ Rect geometryRect = new Rect(geometry.X, geometry.Y, geometry.Width, geometry.Height);
+ GtkScreen s = new GtkScreen(geometryRect, workAreaRect, i == primary, i);
+ screens[i] = s;
+ }
+
+ _allScreens = screens;
+ }
+
+ return _allScreens;
+ }
+ }
+
+ public ScreenImpl()
+ {
+ IntPtr display = Native.GdkGetDefaultDisplay();
+ GdkScreen screen = Native.GdkDisplayGetDefaultScreen(display);
+ Signal.Connect(screen, "monitors-changed", MonitorsChanged);
+ }
+
+ private unsafe void MonitorsChanged(IntPtr screen, IntPtr userData)
+ {
+ _allScreens = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
index 4464059efb..5725ee2596 100644
--- a/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
+++ b/src/Gtk/Avalonia.Gtk3/WindowBaseImpl.cs
@@ -341,6 +341,8 @@ namespace Avalonia.Gtk3
return;
Native.GtkWindowResize(GtkWidget, (int)value.Width, (int)value.Height);
}
+
+ public IScreenImpl Screen { get; } = new ScreenImpl();
public Point Position
{
diff --git a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs
index e6935c4f20..a4bc45c0a1 100644
--- a/src/Gtk/Avalonia.Gtk3/WindowImpl.cs
+++ b/src/Gtk/Avalonia.Gtk3/WindowImpl.cs
@@ -1,4 +1,5 @@
using System;
+using System.Linq.Expressions;
using Avalonia.Controls;
using Avalonia.Gtk3.Interop;
using Avalonia.Platform;
diff --git a/src/OSX/Avalonia.MonoMac/Helpers.cs b/src/OSX/Avalonia.MonoMac/Helpers.cs
index b4e3f7e0b7..454b775cc0 100644
--- a/src/OSX/Avalonia.MonoMac/Helpers.cs
+++ b/src/OSX/Avalonia.MonoMac/Helpers.cs
@@ -1,6 +1,8 @@
using System;
+ using System.ComponentModel;
using MonoMac.AppKit;
using MonoMac.CoreGraphics;
+ using MonoMac.OpenGL;
namespace Avalonia.MonoMac
{
@@ -27,5 +29,14 @@ namespace Avalonia.MonoMac
return new CGPoint(pt.X, t - pt.Y);
}
+ public static Rect ConvertRectY(this Rect rect)
+ {
+ return new Rect(rect.Position.WithY(rect.Y + rect.Height).ConvertPointY(), rect.Size);
+ }
+
+ public static CGRect ConvertRectY(this CGRect rect)
+ {
+ return new CGRect(new CGPoint(rect.X, rect.Y + rect.Height).ConvertPointY(), rect.Size);
+ }
}
}
diff --git a/src/OSX/Avalonia.MonoMac/MacScreen.cs b/src/OSX/Avalonia.MonoMac/MacScreen.cs
new file mode 100644
index 0000000000..3d5ab0769f
--- /dev/null
+++ b/src/OSX/Avalonia.MonoMac/MacScreen.cs
@@ -0,0 +1,25 @@
+using System;
+using Avalonia.Platform;
+
+namespace Avalonia.MonoMac
+{
+ public class MacScreen : Screen
+ {
+ private readonly IntPtr handle;
+
+ public MacScreen(Rect bounds, Rect workingArea, bool primary, IntPtr handle) : base(bounds, workingArea, primary)
+ {
+ this.handle = handle;
+ }
+
+ public override int GetHashCode()
+ {
+ return (int)handle;
+ }
+
+ public override bool Equals(object obj)
+ {
+ return (obj is MacScreen screen) ? this.handle == screen.handle : base.Equals(obj);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/OSX/Avalonia.MonoMac/ScreenImpl.cs b/src/OSX/Avalonia.MonoMac/ScreenImpl.cs
new file mode 100644
index 0000000000..bf8545a300
--- /dev/null
+++ b/src/OSX/Avalonia.MonoMac/ScreenImpl.cs
@@ -0,0 +1,51 @@
+using Avalonia.Controls;
+using Avalonia.Platform;
+using Avalonia.Utilities;
+using MonoMac.AppKit;
+using MonoMac.Foundation;
+
+namespace Avalonia.MonoMac
+{
+ public class ScreenImpl : IScreenImpl
+ {
+ private const string NSApplicationDidChangeScreenParametersNotification = "NSApplicationDidChangeScreenParametersNotification";
+
+ public int ScreenCount
+ {
+ get => NSScreen.Screens.Length;
+ }
+
+ private Screen[] _allScreens;
+ public Screen[] AllScreens
+ {
+ get
+ {
+ if (_allScreens == null)
+ {
+ NSScreen[] screens = NSScreen.Screens;
+ Screen[] s = new Screen[screens.Length];
+ NSScreen primary = NSScreen.MainScreen;
+ for (int i = 0; i < screens.Length; i++)
+ {
+ Rect bounds = screens[i].Frame.ToAvaloniaRect().ConvertRectY();
+ Rect workArea = screens[i].VisibleFrame.ToAvaloniaRect().ConvertRectY();
+ s[i] = new MacScreen(bounds, workArea, i == 0, screens[i].Handle);
+ }
+
+ _allScreens = s;
+ }
+ return _allScreens;
+ }
+ }
+
+ public ScreenImpl()
+ {
+ NSNotificationCenter.DefaultCenter.AddObserver(NSApplicationDidChangeScreenParametersNotification, MonitorsChanged);
+ }
+
+ private void MonitorsChanged(NSNotification notification)
+ {
+ _allScreens = null;
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs b/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs
index fdb2434698..71f008e074 100644
--- a/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs
+++ b/src/OSX/Avalonia.MonoMac/WindowBaseImpl.cs
@@ -153,6 +153,8 @@ namespace Avalonia.MonoMac
Position = pos;
}
+ public IScreenImpl Screen { get; } = new ScreenImpl();
+
public override Point PointToClient(Point point)
{
var cocoaScreenPoint = point.ToMonoMacPoint().ConvertPointY();
diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.Shared.projitems b/src/Windows/Avalonia.Win32/Avalonia.Win32.Shared.projitems
index 4599a0741e..6f13747cb6 100644
--- a/src/Windows/Avalonia.Win32/Avalonia.Win32.Shared.projitems
+++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.Shared.projitems
@@ -20,10 +20,12 @@
+
+
Properties\SharedAssemblyInfo.cs
diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
index 4fed7a34f4..fb4aefbf36 100644
--- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
+++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
@@ -616,6 +616,12 @@ namespace Avalonia.Win32.Interop
public const int SizeOf_BITMAPINFOHEADER = 40;
+ [DllImport("user32.dll")]
+ public static extern bool EnumDisplayMonitors(IntPtr hdc, IntPtr lprcClip,
+ MonitorEnumDelegate lpfnEnum, IntPtr dwData);
+
+ public delegate bool MonitorEnumDelegate(IntPtr hMonitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr dwData);
+
[DllImport("user32.dll", SetLastError = true)]
public static extern IntPtr GetDC(IntPtr hWnd);
@@ -903,6 +909,9 @@ namespace Avalonia.Win32.Interop
[DllImport("user32.dll")]
public static extern IntPtr MonitorFromPoint(POINT pt, MONITOR dwFlags);
+ [DllImport("user32.dll")]
+ public static extern IntPtr MonitorFromRect(RECT rect, MONITOR dwFlags);
+
[DllImport("user32.dll")]
public static extern IntPtr MonitorFromWindow(IntPtr hwnd, MONITOR dwFlags);
@@ -1021,6 +1030,14 @@ namespace Avalonia.Win32.Interop
public int top;
public int right;
public int bottom;
+
+ public RECT(Rect rect)
+ {
+ left = (int)rect.X;
+ top = (int)rect.Y;
+ right = (int)(rect.X + rect.Width);
+ bottom = (int)(rect.Y + rect.Height);
+ }
}
public struct TRACKMOUSEEVENT
diff --git a/src/Windows/Avalonia.Win32/ScreenImpl.cs b/src/Windows/Avalonia.Win32/ScreenImpl.cs
new file mode 100644
index 0000000000..4f4331e461
--- /dev/null
+++ b/src/Windows/Avalonia.Win32/ScreenImpl.cs
@@ -0,0 +1,64 @@
+// 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 System;
+using System.Linq;
+using Avalonia.Controls;
+using Avalonia.Platform;
+using Avalonia.Utilities;
+using static Avalonia.Win32.Interop.UnmanagedMethods;
+
+#if NETSTANDARD
+using Win32Exception = Avalonia.Win32.NetStandard.AvaloniaWin32Exception;
+#endif
+
+namespace Avalonia.Win32
+{
+ public class ScreenImpl : IScreenImpl
+ {
+ public int ScreenCount
+ {
+ get => GetSystemMetrics(SystemMetric.SM_CMONITORS);
+ }
+
+ private Screen[] _allScreens;
+ public Screen[] AllScreens
+ {
+ get
+ {
+ if (_allScreens == null)
+ {
+ int index = 0;
+ Screen[] screens = new Screen[ScreenCount];
+ EnumDisplayMonitors(IntPtr.Zero, IntPtr.Zero,
+ (IntPtr monitor, IntPtr hdcMonitor, ref Rect lprcMonitor, IntPtr data) =>
+ {
+ MONITORINFO monitorInfo = new MONITORINFO();
+ if (GetMonitorInfo(monitor, monitorInfo))
+ {
+ RECT bounds = monitorInfo.rcMonitor;
+ RECT workingArea = monitorInfo.rcWork;
+ Rect avaloniaBounds = new Rect(bounds.left, bounds.top, bounds.right - bounds.left,
+ bounds.bottom - bounds.top);
+ Rect avaloniaWorkArea =
+ new Rect(workingArea.left, workingArea.top, workingArea.right - bounds.left,
+ workingArea.bottom - bounds.top);
+ screens[index] =
+ new WinScreen(avaloniaBounds, avaloniaWorkArea, monitorInfo.dwFlags == 1,
+ monitor);
+ index++;
+ }
+ return true;
+ }, IntPtr.Zero);
+ _allScreens = screens;
+ }
+ return _allScreens;
+ }
+ }
+
+ public void InvalidateScreensCache()
+ {
+ _allScreens = null;
+ }
+ }
+}
diff --git a/src/Windows/Avalonia.Win32/WinScreen.cs b/src/Windows/Avalonia.Win32/WinScreen.cs
new file mode 100644
index 0000000000..2f8dcb0f3e
--- /dev/null
+++ b/src/Windows/Avalonia.Win32/WinScreen.cs
@@ -0,0 +1,25 @@
+using System;
+using Avalonia.Platform;
+
+namespace Avalonia.Win32
+{
+ public class WinScreen : Screen
+ {
+ private readonly IntPtr _hMonitor;
+
+ public WinScreen(Rect bounds, Rect workingArea, bool primary, IntPtr hMonitor) : base(bounds, workingArea, primary)
+ {
+ this._hMonitor = hMonitor;
+ }
+
+ public override int GetHashCode()
+ {
+ return (int)_hMonitor;
+ }
+
+ public override bool Equals(object obj)
+ {
+ return (obj is WinScreen screen) ? this._hMonitor == screen._hMonitor : base.Equals(obj);
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index 9c13a6d9b8..dab7f0ba04 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.cs
@@ -103,6 +103,7 @@ namespace Avalonia.Win32
}
}
+ public IScreenImpl Screen => new ScreenImpl();
public IRenderer CreateRenderer(IRenderRoot root)
{
@@ -598,6 +599,10 @@ namespace Avalonia.Win32
case UnmanagedMethods.WindowsMessage.WM_MOVE:
PositionChanged?.Invoke(new Point((short)(ToInt32(lParam) & 0xffff), (short)(ToInt32(lParam) >> 16)));
return IntPtr.Zero;
+
+ case UnmanagedMethods.WindowsMessage.WM_DISPLAYCHANGE:
+ (Screen as ScreenImpl)?.InvalidateScreensCache();
+ return IntPtr.Zero;
}
#if USE_MANAGED_DRAG