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 + + + +