diff --git a/api/Avalonia.nupkg.xml b/api/Avalonia.nupkg.xml
index a47a716d3d..409cfa9e75 100644
--- a/api/Avalonia.nupkg.xml
+++ b/api/Avalonia.nupkg.xml
@@ -1093,6 +1093,12 @@
baseline/netstandard2.0/Avalonia.Base.dll
target/netstandard2.0/Avalonia.Base.dll
+
+ CP0002
+ M:Avalonia.Controls.Screens.#ctor(Avalonia.Platform.IScreenImpl)
+ baseline/netstandard2.0/Avalonia.Controls.dll
+ target/netstandard2.0/Avalonia.Controls.dll
+
CP0006
M:Avalonia.Platform.IAssetLoader.InvalidateAssemblyCache
@@ -1141,4 +1147,10 @@
baseline/netstandard2.0/Avalonia.Controls.dll
target/netstandard2.0/Avalonia.Controls.dll
+
+ CP0009
+ T:Avalonia.Controls.Screens
+ baseline/netstandard2.0/Avalonia.Controls.dll
+ target/netstandard2.0/Avalonia.Controls.dll
+
\ No newline at end of file
diff --git a/native/Avalonia.Native/src/OSX/Screens.mm b/native/Avalonia.Native/src/OSX/Screens.mm
index 85f4b7c50a..2279dae7c8 100644
--- a/native/Avalonia.Native/src/OSX/Screens.mm
+++ b/native/Avalonia.Native/src/OSX/Screens.mm
@@ -1,36 +1,62 @@
#include "common.h"
+#include "AvnString.h"
class Screens : public ComSingleObject
{
- public:
- FORWARD_IUNKNOWN()
-
+private:
+ ComPtr _events;
public:
- virtual HRESULT GetScreenCount (int* ret) override
+ FORWARD_IUNKNOWN()
+
+ Screens(IAvnScreenEvents* events) {
+ _events = events;
+ CGDisplayRegisterReconfigurationCallback(CGDisplayReconfigurationCallBack, this);
+ }
+
+ virtual HRESULT GetScreenIds (
+ unsigned int* ptrFirstResult,
+ int* screenCound) override
{
START_COM_CALL;
@autoreleasepool
{
- *ret = (int)[NSScreen screens].count;
-
+ auto screens = [NSScreen screens];
+ *screenCound = (int)screens.count;
+
+ if (ptrFirstResult == nil)
+ return S_OK;
+
+ for (int i = 0; i < screens.count; i++) {
+ ptrFirstResult[i] = [[screens objectAtIndex:i] av_displayId];
+ }
+
return S_OK;
}
}
-
- virtual HRESULT GetScreen (int index, AvnScreen* ret) override
- {
+
+ virtual HRESULT GetScreen (
+ CGDirectDisplayID displayId,
+ void** localizedName,
+ AvnScreen* ret
+ ) override {
START_COM_CALL;
@autoreleasepool
{
- if(index < 0 || index >= [NSScreen screens].count)
- {
- return E_INVALIDARG;
+ NSScreen* screen;
+ for (NSScreen *s in NSScreen.screens) {
+ if (s.av_displayId == displayId)
+ {
+ screen = s;
+ break;
+ }
}
- auto screen = [[NSScreen screens] objectAtIndex:index];
-
+ if (screen == nil) {
+ return E_INVALIDARG;
+ }
+
ret->Bounds.Height = [screen frame].size.height;
ret->Bounds.Width = [screen frame].size.width;
ret->Bounds.X = [screen frame].origin.x;
@@ -43,14 +69,45 @@ public:
ret->Scaling = 1;
- ret->IsPrimary = index == 0;
-
+ ret->IsPrimary = CGDisplayIsMain(displayId);
+
+ // Compute natural orientation:
+ auto naturalScreenSize = CGDisplayScreenSize(displayId);
+ auto isNaturalLandscape = naturalScreenSize.width > naturalScreenSize.height;
+ // Normalize rotation:
+ auto rotation = (int)CGDisplayRotation(displayId) % 360;
+ if (rotation < 0) rotation = 360 - rotation;
+ // Get current orientation relative to the natural
+ if (rotation >= 0 && rotation < 90) {
+ ret->Orientation = isNaturalLandscape ? AvnScreenOrientation::Landscape : AvnScreenOrientation::Portrait;
+ } else if (rotation >= 90 && rotation < 180) {
+ ret->Orientation = isNaturalLandscape ? AvnScreenOrientation::Portrait : AvnScreenOrientation::Landscape;
+ } else if (rotation >= 180 && rotation < 270) {
+ ret->Orientation = isNaturalLandscape ? AvnScreenOrientation::LandscapeFlipped : AvnScreenOrientation::PortraitFlipped;
+ } else {
+ ret->Orientation = isNaturalLandscape ? AvnScreenOrientation::PortraitFlipped : AvnScreenOrientation::LandscapeFlipped;
+ }
+
+ if (@available(macOS 10.15, *)) {
+ *localizedName = CreateAvnString([screen localizedName]);
+ }
+
return S_OK;
}
}
+
+private:
+ static void CGDisplayReconfigurationCallBack(CGDirectDisplayID display, CGDisplayChangeSummaryFlags flags, void *screens)
+ {
+ auto object = (Screens *)screens;
+ auto events = object->_events;
+ if (events != nil) {
+ events->OnChanged();
+ }
+ }
};
-extern IAvnScreens* CreateScreens()
+extern IAvnScreens* CreateScreens(IAvnScreenEvents* events)
{
- return new Screens();
+ return new Screens(events);
}
diff --git a/native/Avalonia.Native/src/OSX/TopLevelImpl.h b/native/Avalonia.Native/src/OSX/TopLevelImpl.h
index d6243b6268..dd494ab761 100644
--- a/native/Avalonia.Native/src/OSX/TopLevelImpl.h
+++ b/native/Avalonia.Native/src/OSX/TopLevelImpl.h
@@ -58,7 +58,8 @@ public:
virtual HRESULT PointToScreen(AvnPoint point, AvnPoint *ret) override;
virtual HRESULT SetTransparencyMode(AvnWindowTransparencyMode mode) override;
-
+
+ virtual HRESULT GetCurrentDisplayId (CGDirectDisplayID* ret) override;
protected:
NSCursor *cursor;
virtual void UpdateAppearance();
diff --git a/native/Avalonia.Native/src/OSX/TopLevelImpl.mm b/native/Avalonia.Native/src/OSX/TopLevelImpl.mm
index 8915dd0e77..6200f096d3 100644
--- a/native/Avalonia.Native/src/OSX/TopLevelImpl.mm
+++ b/native/Avalonia.Native/src/OSX/TopLevelImpl.mm
@@ -258,6 +258,15 @@ HRESULT TopLevelImpl::SetTransparencyMode(AvnWindowTransparencyMode mode) {
return S_OK;
}
+HRESULT TopLevelImpl::GetCurrentDisplayId (CGDirectDisplayID* ret) {
+ START_COM_CALL;
+
+ auto window = [View window];
+ *ret = [window.screen av_displayId];
+
+ return S_OK;
+}
+
void TopLevelImpl::UpdateAppearance() {
}
diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h
index 38b702d775..fab11d6e4f 100644
--- a/native/Avalonia.Native/src/OSX/common.h
+++ b/native/Avalonia.Native/src/OSX/common.h
@@ -15,7 +15,7 @@ extern IAvnTopLevel* CreateAvnTopLevel(IAvnTopLevelEvents* events);
extern IAvnWindow* CreateAvnWindow(IAvnWindowEvents*events);
extern IAvnPopup* CreateAvnPopup(IAvnWindowEvents*events);
extern IAvnSystemDialogs* CreateSystemDialogs();
-extern IAvnScreens* CreateScreens();
+extern IAvnScreens* CreateScreens(IAvnScreenEvents* cb);
extern IAvnClipboard* CreateClipboard(NSPasteboard*, NSPasteboardItem*);
extern NSPasteboardItem* TryGetPasteboardItem(IAvnClipboard*);
extern NSObject* CreateDraggingSource(NSDragOperation op, IAvnDndResultCallback* cb, void* handle);
@@ -89,6 +89,13 @@ public:
- (void) action;
@end
+@implementation NSScreen (AvNSScreen)
+- (CGDirectDisplayID)av_displayId
+{
+ return [self.deviceDescription[@"NSScreenNumber"] unsignedIntValue];
+}
+@end
+
class AvnInsidePotentialDeadlock
{
public:
diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm
index a36170fddc..47a5ba73c5 100644
--- a/native/Avalonia.Native/src/OSX/main.mm
+++ b/native/Avalonia.Native/src/OSX/main.mm
@@ -287,13 +287,13 @@ public:
}
}
- virtual HRESULT CreateScreens (IAvnScreens** ppv) override
+ virtual HRESULT CreateScreens (IAvnScreenEvents* cb, IAvnScreens** ppv) override
{
START_COM_CALL;
@autoreleasepool
{
- *ppv = ::CreateScreens ();
+ *ppv = ::CreateScreens (cb);
return S_OK;
}
}
diff --git a/native/Avalonia.Native/src/OSX/menu.mm b/native/Avalonia.Native/src/OSX/menu.mm
index 7a7edcb1cb..1235979cb2 100644
--- a/native/Avalonia.Native/src/OSX/menu.mm
+++ b/native/Avalonia.Native/src/OSX/menu.mm
@@ -1,4 +1,5 @@
+
#include "common.h"
#include "menu.h"
#include "KeyTransform.h"
diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index 5e47f36c0b..6246c73ce2 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -196,6 +196,9 @@
+
+
+
diff --git a/samples/ControlCatalog/MainView.xaml.cs b/samples/ControlCatalog/MainView.xaml.cs
index 31fa54a23a..89bccb4475 100644
--- a/samples/ControlCatalog/MainView.xaml.cs
+++ b/samples/ControlCatalog/MainView.xaml.cs
@@ -25,16 +25,6 @@ namespace ControlCatalog
var sideBar = this.Get("Sidebar");
- if (Application.Current?.ApplicationLifetime is IClassicDesktopStyleApplicationLifetime)
- {
- var tabItems = (sideBar.Items as IList);
- tabItems?.Add(new TabItem()
- {
- Header = "Screens",
- Content = new ScreenPage()
- });
- }
-
var themes = this.Get("Themes");
themes.SelectedItem = App.CurrentTheme;
themes.SelectionChanged += (sender, e) =>
diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs
index 2cdd031693..66e90c2b34 100644
--- a/samples/ControlCatalog/Pages/ScreenPage.cs
+++ b/samples/ControlCatalog/Pages/ScreenPage.cs
@@ -20,33 +20,62 @@ namespace ControlCatalog.Pages
private IPen _activePen = new Pen(Brushes.Black);
private IPen _defaultPen = new Pen(Brushes.DarkGray);
+ public ScreenPage()
+ {
+ var button = new Button();
+ button.Content = "Request ScreenDetails";
+ button.VerticalAlignment = Avalonia.Layout.VerticalAlignment.Top;
+ button.Click += async (sender, args) =>
+ {
+ var success = TopLevel.GetTopLevel(this)!.Screens is { } screens ?
+ await screens.RequestScreenDetails() :
+ false;
+ button.Content = "Request ScreenDetails: " + (success ? "Granted" : "Denied");
+ };
+ Content = button;
+ }
+
protected override bool BypassFlowDirectionPolicies => true;
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
{
base.OnAttachedToVisualTree(e);
- if(VisualRoot is Window w)
+
+ var topLevel = TopLevel.GetTopLevel(this);
+ if (topLevel is Window w)
{
w.PositionChanged += (_, _) => InvalidateVisual();
}
+
+ if (topLevel?.Screens is { } screens)
+ {
+ screens.Changed += (_, _) =>
+ {
+ Console.WriteLine("Screens Changed");
+ InvalidateVisual();
+ };
+ }
}
public override void Render(DrawingContext context)
{
base.Render(context);
- if (!(VisualRoot is Window w))
+ double beginOffset = (Content as Visual)?.Bounds.Height + 10 ?? 0;
+
+ var topLevel = TopLevel.GetTopLevel(this)!;
+ if (topLevel.Screens is not { } screens)
{
+ var formattedText = CreateFormattedText("Current platform doesn't support Screens API.");
+ context.DrawText(formattedText, new Point(15, 15 + beginOffset));
return;
}
- var screens = w.Screens.All;
- var scaling = ((IRenderRoot)w).RenderScaling;
- var activeScreen = w.Screens.ScreenFromBounds(new PixelRect(w.Position, PixelSize.FromSize(w.Bounds.Size, scaling)));
+ var activeScreen = screens.ScreenFromTopLevel(topLevel);
double maxBottom = 0;
- for (int i = 0; i
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs
index 89126b3b99..deced2f97d 100644
--- a/samples/IntegrationTestApp/MainWindow.axaml.cs
+++ b/samples/IntegrationTestApp/MainWindow.axaml.cs
@@ -1,5 +1,6 @@
using System;
using System.Collections.Generic;
+using System.Globalization;
using System.Linq;
using Avalonia;
using Avalonia.Automation;
@@ -303,6 +304,8 @@ namespace IntegrationTestApp
OnShowNewWindowDecorations();
if (source?.Name == nameof(ToggleTrayIconVisible))
OnToggleTrayIconVisible();
+ if (source?.Name == nameof(ScreenRefresh))
+ OnScreenRefresh();
}
private void OnApplyWindowDecorations(Window window)
@@ -376,5 +379,19 @@ namespace IntegrationTestApp
dialog.ShowDialog(this);
}
+
+ private Screen? _lastScreen;
+ private void OnScreenRefresh()
+ {
+ var lastScreen = _lastScreen;
+ var screen = _lastScreen = Screens.ScreenFromWindow(this);
+ ScreenName.Text = screen?.DisplayName;
+ ScreenHandle.Text = screen?.TryGetPlatformHandle()?.ToString();
+ ScreenBounds.Text = screen?.Bounds.ToString();
+ ScreenWorkArea.Text = screen?.WorkingArea.ToString();
+ ScreenScaling.Text = screen?.Scaling.ToString(CultureInfo.InvariantCulture);
+ ScreenOrientation.Text = screen?.CurrentOrientation.ToString();
+ ScreenSameReference.Text = ReferenceEquals(lastScreen, screen).ToString();
+ }
}
}
diff --git a/src/Avalonia.Base/Platform/PlatformHandle.cs b/src/Avalonia.Base/Platform/PlatformHandle.cs
index 9cc2741a12..79dd4f54dc 100644
--- a/src/Avalonia.Base/Platform/PlatformHandle.cs
+++ b/src/Avalonia.Base/Platform/PlatformHandle.cs
@@ -5,7 +5,7 @@ namespace Avalonia.Platform
///
/// Represents a platform-specific handle.
///
- public class PlatformHandle : IPlatformHandle
+ public class PlatformHandle : IPlatformHandle, IEquatable
{
///
/// Initializes a new instance of the class.
@@ -29,5 +29,44 @@ namespace Avalonia.Platform
/// Gets an optional string that describes what represents.
///
public string? HandleDescriptor { get; }
+
+ ///
+ public override string ToString()
+ {
+ return $"PlatformHandle {{ {HandleDescriptor} = {Handle} }}";
+ }
+
+ ///
+ public bool Equals(PlatformHandle? other)
+ {
+ if (other is null) return false;
+ if (ReferenceEquals(this, other)) return true;
+ return Handle == other.Handle && HandleDescriptor == other.HandleDescriptor;
+ }
+
+ ///
+ public override bool Equals(object? obj)
+ {
+ if (obj is null) return false;
+ if (ReferenceEquals(this, obj)) return true;
+ if (obj.GetType() != GetType()) return false;
+ return Equals((PlatformHandle)obj);
+ }
+
+ ///
+ public override int GetHashCode()
+ {
+ return (Handle, HandleDescriptor).GetHashCode();
+ }
+
+ public static bool operator ==(PlatformHandle? left, PlatformHandle? right)
+ {
+ return Equals(left, right);
+ }
+
+ public static bool operator !=(PlatformHandle? left, PlatformHandle? right)
+ {
+ return !Equals(left, right);
+ }
}
}
diff --git a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
index f631b18c04..14bb65f0df 100644
--- a/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
+++ b/src/Avalonia.Controls/Embedding/Offscreen/OffscreenTopLevelImpl.cs
@@ -30,7 +30,6 @@ namespace Avalonia.Controls.Embedding.Offscreen
public abstract IEnumerable