Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Screens API refactor #16295

Merged
merged 20 commits into from
Jul 16, 2024
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
a260fb4
Draft new API
maxkatz6 Jul 12, 2024
0f20b9e
Push reusable ScreensBaseImpl implementation
maxkatz6 Jul 12, 2024
2457e5f
Fix tests and stubs
maxkatz6 Jul 12, 2024
441857b
Update ScreensPage sample to work on mobile + show new APIs
maxkatz6 Jul 12, 2024
11cab6b
Reimplement Windows ScreensImpl, reuse existing screens in other plac…
maxkatz6 Jul 12, 2024
8a1f555
Make X11 project buildable, don't utilize new APIs yet
maxkatz6 Jul 12, 2024
de8e877
Reimplement macOS Screens API, differenciate screens by CGDirectDispl…
maxkatz6 Jul 12, 2024
a14f375
Fix build
maxkatz6 Jul 12, 2024
62d5058
Adjust breaking changes file (none affect users)
maxkatz6 Jul 12, 2024
557ca54
Merge branch 'master' into screens-api-refactor
maxkatz6 Jul 12, 2024
4f7b8d8
Fix missing macOS Screen.DisplayName
maxkatz6 Jul 12, 2024
fd1b5c2
Add more tests + fix screen removal
maxkatz6 Jul 13, 2024
c16f5ef
Add screens integration tests
maxkatz6 Jul 13, 2024
4b11e53
Use hash set with comparer when removing screens
maxkatz6 Jul 13, 2024
c683f10
Merge branch 'master' into screens-api-refactor
maxkatz6 Jul 13, 2024
5e4d96d
Make screenimpl safer on macOS as per review
maxkatz6 Jul 16, 2024
cf1c184
Replace UnmanagedCallersOnly usage with source generated EnumDisplayM…
maxkatz6 Jul 16, 2024
4e9e8ff
Remove unused dllimport
maxkatz6 Jul 16, 2024
c04c15b
Only implement GetHashCode and Equals on PlatformScreen subclass, wit…
maxkatz6 Jul 16, 2024
ebe1e5c
Merge branch 'master' into screens-api-refactor
maxkatz6 Jul 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions api/Avalonia.nupkg.xml
Original file line number Diff line number Diff line change
Expand Up @@ -1093,6 +1093,12 @@
<Left>baseline/netstandard2.0/Avalonia.Base.dll</Left>
<Right>target/netstandard2.0/Avalonia.Base.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0002</DiagnosticId>
<Target>M:Avalonia.Controls.Screens.#ctor(Avalonia.Platform.IScreenImpl)</Target>
<Left>baseline/netstandard2.0/Avalonia.Controls.dll</Left>
<Right>target/netstandard2.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0006</DiagnosticId>
<Target>M:Avalonia.Platform.IAssetLoader.InvalidateAssemblyCache</Target>
Expand Down Expand Up @@ -1141,4 +1147,10 @@
<Left>baseline/netstandard2.0/Avalonia.Controls.dll</Left>
<Right>target/netstandard2.0/Avalonia.Controls.dll</Right>
</Suppression>
<Suppression>
<DiagnosticId>CP0009</DiagnosticId>
<Target>T:Avalonia.Controls.Screens</Target>
<Left>baseline/netstandard2.0/Avalonia.Controls.dll</Left>
<Right>target/netstandard2.0/Avalonia.Controls.dll</Right>
</Suppression>
</Suppressions>
87 changes: 70 additions & 17 deletions native/Avalonia.Native/src/OSX/Screens.mm
Original file line number Diff line number Diff line change
@@ -1,36 +1,59 @@
#include "common.h"
#include "AvnString.h"

class Screens : public ComSingleObject<IAvnScreens, &IID_IAvnScreens>
{
public:
public:
FORWARD_IUNKNOWN()

Screens(IAvnScreenEvents* events) {
CGDisplayRegisterReconfigurationCallback(CGDisplayReconfigurationCallBack, events);
maxkatz6 marked this conversation as resolved.
Show resolved Hide resolved
}

public:
virtual HRESULT GetScreenCount (int* ret) override
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;
Expand All @@ -43,14 +66,44 @@ virtual HRESULT GetScreen (int index, AvnScreen* ret) override

ret->Scaling = 1;
Copy link
Member Author

@maxkatz6 maxkatz6 Jul 13, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does anybody remember why we set scaling to 1? Instead of using an actual value (more likely between 2 and 3 for most main displays). MacOS.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our Screen API reports layout scaling, not DPI. macOS always uses logical coordinates for window positioning, so the reported value is 1.
We could extend the API to report both.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Our Screen API reports layout scaling

Is it used anywhere like that? It's also not intuitive.
Normally people would use Window.DesktopScaling for that.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Other implementations like qtbase and chromium just return dpi scaling, not layout.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will keep it as is for now


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 *userInfo)
{
auto events = (IAvnScreenEvents*)userInfo;
maxkatz6 marked this conversation as resolved.
Show resolved Hide resolved
if (events != nil) {
events->OnChanged();
}
}
};

extern IAvnScreens* CreateScreens()
extern IAvnScreens* CreateScreens(IAvnScreenEvents* events)
{
return new Screens();
return new Screens(events);
}
3 changes: 2 additions & 1 deletion native/Avalonia.Native/src/OSX/TopLevelImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ class TopLevelImpl : public virtual ComObject,
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();
Expand Down
9 changes: 9 additions & 0 deletions native/Avalonia.Native/src/OSX/TopLevelImpl.mm
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,15 @@
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() {

}
Expand Down
9 changes: 8 additions & 1 deletion native/Avalonia.Native/src/OSX/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<NSDraggingSource>* CreateDraggingSource(NSDragOperation op, IAvnDndResultCallback* cb, void* handle);
Expand Down Expand Up @@ -89,6 +89,13 @@ template<typename T> class ObjCWrapper {
- (void) action;
@end

@implementation NSScreen (AvNSScreen)
- (CGDirectDisplayID)av_displayId
{
return [self.deviceDescription[@"NSScreenNumber"] unsignedIntValue];
kekekeks marked this conversation as resolved.
Show resolved Hide resolved
}
@end

class AvnInsidePotentialDeadlock
{
public:
Expand Down
4 changes: 2 additions & 2 deletions native/Avalonia.Native/src/OSX/main.mm
Original file line number Diff line number Diff line change
Expand Up @@ -287,13 +287,13 @@ virtual HRESULT CreateSystemDialogs(IAvnSystemDialogs** ppv) override
}
}

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;
}
}
Expand Down
1 change: 1 addition & 0 deletions native/Avalonia.Native/src/OSX/menu.mm
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@


#include "common.h"
#include "menu.h"
#include "KeyTransform.h"
Expand Down
3 changes: 3 additions & 0 deletions samples/ControlCatalog/MainView.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,9 @@
<TabItem Header="HeaderedContentControl">
<pages:HeaderedContentPage />
</TabItem>
<TabItem Header="Screens">
<pages:ScreenPage />
</TabItem>
<FlyoutBase.AttachedFlyout>
<Flyout>
<StackPanel Width="152" Spacing="8">
Expand Down
10 changes: 0 additions & 10 deletions samples/ControlCatalog/MainView.xaml.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,16 +25,6 @@ public MainView()

var sideBar = this.Get<TabControl>("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<ComboBox>("Themes");
themes.SelectedItem = App.CurrentTheme;
themes.SelectionChanged += (sender, e) =>
Expand Down
78 changes: 62 additions & 16 deletions samples/ControlCatalog/Pages/ScreenPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,33 +20,62 @@ public class ScreenPage : UserControl
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<screens.Count; i++ )
for (int i = 0; i<screens.ScreenCount; i++ )
{
var screen = screens[i];
var screen = screens.All[i];

if (screen.Bounds.X / 10f < _leftMost)
{
Expand All @@ -63,31 +92,39 @@ public override void Render(DrawingContext context)
bool primary = screen.IsPrimary;
bool active = screen.Equals(activeScreen);

Rect boundsRect = new Rect(screen.Bounds.X / 10f + Math.Abs(_leftMost), screen.Bounds.Y / 10f+Math.Abs(_topMost), screen.Bounds.Width / 10f,
Rect boundsRect = new Rect(screen.Bounds.X / 10f + Math.Abs(_leftMost), screen.Bounds.Y / 10f+Math.Abs(_topMost) + beginOffset, screen.Bounds.Width / 10f,
screen.Bounds.Height / 10f);
Rect workingAreaRect = new Rect(screen.WorkingArea.X / 10f + Math.Abs(_leftMost), screen.WorkingArea.Y / 10f+Math.Abs(_topMost), screen.WorkingArea.Width / 10f,
Rect workingAreaRect = new Rect(screen.WorkingArea.X / 10f + Math.Abs(_leftMost), screen.WorkingArea.Y / 10f+Math.Abs(_topMost) + beginOffset, screen.WorkingArea.Width / 10f,
screen.WorkingArea.Height / 10f);

context.DrawRectangle(primary ? _primaryBrush : _defaultBrush, active ? _activePen : _defaultPen, boundsRect);
context.DrawRectangle(primary ? _primaryBrush : _defaultBrush, active ? _activePen : _defaultPen, workingAreaRect);

var identifier = CreateScreenIdentifier((i+1).ToString(), primary);
var center = boundsRect.Center - new Point(identifier.Width / 2.0f, identifier.Height / 2.0f);
var center = boundsRect.Center - new Point(identifier.Width / 2.0f, identifier.Height / 2.0f + beginOffset);

context.DrawText(identifier, center);
maxBottom = Math.Max(maxBottom, boundsRect.Bottom);
}

double currentHeight = maxBottom;

for(int i = 0; i< screens.Count; i++)
for(int i = 0; i< screens.ScreenCount; i++)
{
var screen = screens[i];
var screen = screens.All[i];

var formattedText = CreateFormattedText($"Screen {i+1}", 18);
context.DrawText(formattedText, new Point(0, currentHeight));
currentHeight += 25;

formattedText = CreateFormattedText($"DisplayName: {screen.DisplayName}");
context.DrawText(formattedText, new Point(15, currentHeight));
currentHeight += 20;

formattedText = CreateFormattedText($"Handle: {screen.TryGetPlatformHandle()}");
context.DrawText(formattedText, new Point(15, currentHeight));
currentHeight += 20;

formattedText = CreateFormattedText($"Bounds: {screen.Bounds.Width}:{screen.Bounds.Height}");
context.DrawText(formattedText, new Point(15, currentHeight));
currentHeight += 20;
Expand All @@ -101,17 +138,26 @@ public override void Render(DrawingContext context)
currentHeight += 20;

formattedText = CreateFormattedText($"IsPrimary: {screen.IsPrimary}");

context.DrawText(formattedText, new Point(15, currentHeight));
currentHeight += 20;

formattedText = CreateFormattedText($"CurrentOrientation: {screen.CurrentOrientation}");
context.DrawText(formattedText, new Point(15, currentHeight));
currentHeight += 20;

formattedText = CreateFormattedText( $"Current: {screen.Equals(activeScreen)}");
context.DrawText(formattedText, new Point(15, currentHeight));
currentHeight += 30;

}

context.DrawRectangle(_activePen, new Rect(w.Position.X / 10f + Math.Abs(_leftMost), w.Position.Y / 10f+Math.Abs(_topMost), w.Bounds.Width / 10, w.Bounds.Height / 10));
if (topLevel is Window w)
{
var wPos = w.Position;
var wSize = PixelSize.FromSize(w.FrameSize ?? w.ClientSize, w.DesktopScaling);
context.DrawRectangle(_activePen,
new Rect(wPos.X / 10f + Math.Abs(_leftMost), wPos.Y / 10f + Math.Abs(_topMost) + beginOffset,
wSize.Width / 10d, wSize.Height / 10d));
}
}

private static FormattedText CreateFormattedText(string textToFormat, double size = 12)
Expand Down
Loading
Loading