Browse Source
* Android screen implementation, using Display API (with some fallbacks) * Browser screen implementation, aditionally add JSObjectPlatformHandle * iOS screens API implementation with UIScreenPlatformHandle * Tizen screens implementation, pretty limited but should be better than nothing * Reduce some API changes to make them more portable in the future * Update api suppressionpull/16701/head
committed by
GitHub
25 changed files with 697 additions and 96 deletions
@ -0,0 +1,16 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<!-- https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/diagnostic-ids --> |
|||
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.Android.AndroidViewControlHandle.get_HandleDescriptor</Target> |
|||
<Left>baseline/net8.0-android34.0/Avalonia.Android.dll</Left> |
|||
<Right>target/net8.0-android34.0/Avalonia.Android.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0007</DiagnosticId> |
|||
<Target>T:Avalonia.Android.AndroidViewControlHandle</Target> |
|||
<Left>baseline/net8.0-android34.0/Avalonia.Android.dll</Left> |
|||
<Right>target/net8.0-android34.0/Avalonia.Android.dll</Right> |
|||
</Suppression> |
|||
</Suppressions> |
|||
@ -0,0 +1,22 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<!-- https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/diagnostic-ids --> |
|||
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.Browser.JSObjectControlHandle.get_Handle</Target> |
|||
<Left>baseline/net8.0-browser1.0/Avalonia.Browser.dll</Left> |
|||
<Right>target/net8.0-browser1.0/Avalonia.Browser.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.Browser.JSObjectControlHandle.get_HandleDescriptor</Target> |
|||
<Left>baseline/net8.0-browser1.0/Avalonia.Browser.dll</Left> |
|||
<Right>target/net8.0-browser1.0/Avalonia.Browser.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0007</DiagnosticId> |
|||
<Target>T:Avalonia.Browser.JSObjectControlHandle</Target> |
|||
<Left>baseline/net8.0-browser1.0/Avalonia.Browser.dll</Left> |
|||
<Right>target/net8.0-browser1.0/Avalonia.Browser.dll</Right> |
|||
</Suppression> |
|||
</Suppressions> |
|||
@ -0,0 +1,16 @@ |
|||
<?xml version="1.0" encoding="utf-8"?> |
|||
<!-- https://learn.microsoft.com/en-us/dotnet/fundamentals/package-validation/diagnostic-ids --> |
|||
<Suppressions xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema"> |
|||
<Suppression> |
|||
<DiagnosticId>CP0002</DiagnosticId> |
|||
<Target>M:Avalonia.iOS.UIViewControlHandle.get_HandleDescriptor</Target> |
|||
<Left>baseline/net8.0-tvos17.0/Avalonia.iOS.dll</Left> |
|||
<Right>target/net8.0-tvos17.0/Avalonia.iOS.dll</Right> |
|||
</Suppression> |
|||
<Suppression> |
|||
<DiagnosticId>CP0007</DiagnosticId> |
|||
<Target>T:Avalonia.iOS.UIViewControlHandle</Target> |
|||
<Left>baseline/net8.0-tvos17.0/Avalonia.iOS.dll</Left> |
|||
<Right>target/net8.0-tvos17.0/Avalonia.iOS.dll</Right> |
|||
</Suppression> |
|||
</Suppressions> |
|||
@ -0,0 +1,140 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Android.Content; |
|||
using Android.Hardware.Display; |
|||
using Android.Runtime; |
|||
using Android.Util; |
|||
using Android.Views; |
|||
using Avalonia.Android.Platform.SkiaPlatform; |
|||
using Avalonia.Platform; |
|||
using AndroidOrientation = global::Android.Content.Res.Orientation; |
|||
|
|||
namespace Avalonia.Android.Platform; |
|||
|
|||
internal class AndroidScreen(Display display) : PlatformScreen(new PlatformHandle(new IntPtr(display.DisplayId), "DisplayId")) |
|||
{ |
|||
public void Refresh(Context context) |
|||
{ |
|||
DisplayName = display.Name; |
|||
|
|||
var naturalOrientation = ScreenOrientation.Portrait; |
|||
var rotation = display.Rotation; |
|||
|
|||
if (OperatingSystem.IsAndroidVersionAtLeast(30) |
|||
&& display.DisplayId == context.Display?.DisplayId |
|||
&& context.Resources?.DisplayMetrics is { } primaryMetrics) |
|||
{ |
|||
IsPrimary = true; |
|||
Scaling = primaryMetrics.Density; |
|||
Bounds = WorkingArea = new(0, 0, primaryMetrics.WidthPixels, primaryMetrics.HeightPixels); |
|||
|
|||
var orientation = context.Resources.Configuration?.Orientation; |
|||
if (orientation == AndroidOrientation.Square) |
|||
naturalOrientation = ScreenOrientation.None; |
|||
else if (rotation is SurfaceOrientation.Rotation0 or SurfaceOrientation.Rotation180) |
|||
naturalOrientation = orientation == AndroidOrientation.Landscape ? |
|||
ScreenOrientation.Landscape : |
|||
ScreenOrientation.Portrait; |
|||
else |
|||
naturalOrientation = orientation == AndroidOrientation.Portrait ? |
|||
ScreenOrientation.Landscape : |
|||
ScreenOrientation.Portrait; |
|||
} |
|||
else |
|||
{ |
|||
IsPrimary = false; |
|||
// These Display methods are deprecated since 31 SDK,
|
|||
// But Android doesn't have any replacement, except for the primary screen.
|
|||
#pragma warning disable CS0618 // Type or member is obsolete
|
|||
#pragma warning disable CA1422 // Validate platform compatibility
|
|||
#pragma warning disable CA1416 // Validate platform compatibility
|
|||
var displayMetrics = new DisplayMetrics(); |
|||
display.GetRealMetrics(displayMetrics); |
|||
#pragma warning restore CA1416 // Validate platform compatibility
|
|||
#pragma warning restore CA1422 // Validate platform compatibility
|
|||
#pragma warning restore CS0618 // Type or member is obsolete
|
|||
Scaling = displayMetrics.Density; |
|||
Bounds = WorkingArea = new(0, 0, displayMetrics.WidthPixels, displayMetrics.HeightPixels); |
|||
} |
|||
|
|||
CurrentOrientation = (display.Rotation, naturalOrientation) switch |
|||
{ |
|||
(_, ScreenOrientation.None) => ScreenOrientation.None, |
|||
(SurfaceOrientation.Rotation0, ScreenOrientation.Landscape) => ScreenOrientation.Landscape, |
|||
(SurfaceOrientation.Rotation90, ScreenOrientation.Landscape) => ScreenOrientation.Portrait, |
|||
(SurfaceOrientation.Rotation180, ScreenOrientation.Landscape) => ScreenOrientation.LandscapeFlipped, |
|||
(SurfaceOrientation.Rotation270, ScreenOrientation.Landscape) => ScreenOrientation.PortraitFlipped, |
|||
(SurfaceOrientation.Rotation0, _) => ScreenOrientation.Portrait, |
|||
(SurfaceOrientation.Rotation90, _) => ScreenOrientation.Landscape, |
|||
(SurfaceOrientation.Rotation180, _) => ScreenOrientation.PortraitFlipped, |
|||
(SurfaceOrientation.Rotation270, _) => ScreenOrientation.LandscapeFlipped, |
|||
_ => ScreenOrientation.Portrait |
|||
}; |
|||
} |
|||
} |
|||
|
|||
internal sealed class AndroidScreens : ScreensBase<Display, AndroidScreen>, IDisposable |
|||
{ |
|||
private readonly Context _context; |
|||
private readonly DisplayManager? _displayService; |
|||
private readonly DisplayListener? _listener; |
|||
|
|||
public AndroidScreens(Context context) : base(new DisplayComparer()) |
|||
{ |
|||
_context = context; |
|||
_displayService = context.GetSystemService(Context.DisplayService).JavaCast<DisplayManager>(); |
|||
if (_displayService is not null) |
|||
{ |
|||
_listener = new DisplayListener(this); |
|||
_displayService.RegisterDisplayListener(_listener, null); |
|||
} |
|||
} |
|||
|
|||
protected override IReadOnlyList<Display> GetAllScreenKeys() |
|||
{ |
|||
if (_displayService?.GetDisplays() is { } displays) |
|||
{ |
|||
return displays; |
|||
} |
|||
|
|||
if (OperatingSystem.IsAndroidVersionAtLeast(30) && _context.Display is { } defaultDisplay) |
|||
{ |
|||
return [defaultDisplay]; |
|||
} |
|||
|
|||
return Array.Empty<Display>(); |
|||
} |
|||
|
|||
protected override AndroidScreen CreateScreenFromKey(Display display) => new(display); |
|||
|
|||
protected override void ScreenChanged(AndroidScreen screen) => screen.Refresh(_context); |
|||
|
|||
protected override Screen? ScreenFromTopLevelCore(ITopLevelImpl topLevel) |
|||
{ |
|||
var display = ((TopLevelImpl)topLevel).View.Display; |
|||
return display is not null && TryGetScreen(display, out var screen) ? screen : null; |
|||
} |
|||
|
|||
protected override Screen? ScreenFromPointCore(PixelPoint point) => null; |
|||
protected override Screen? ScreenFromRectCore(PixelRect rect) => null; |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_displayService?.UnregisterDisplayListener(_listener); |
|||
_displayService?.Dispose(); |
|||
_listener?.Dispose(); |
|||
} |
|||
|
|||
private class DisplayListener(AndroidScreens screens) : Java.Lang.Object, DisplayManager.IDisplayListener |
|||
{ |
|||
public void OnDisplayAdded(int displayId) => screens.OnChanged(); |
|||
public void OnDisplayChanged(int displayId) => screens.OnChanged(); |
|||
public void OnDisplayRemoved(int displayId) => screens.OnChanged(); |
|||
} |
|||
|
|||
private class DisplayComparer : IEqualityComparer<Display> |
|||
{ |
|||
public bool Equals(Display? x, Display? y) => x?.DisplayId == y?.DisplayId; |
|||
public int GetHashCode(Display obj) => obj.DisplayId; |
|||
} |
|||
} |
|||
@ -0,0 +1,71 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Runtime.InteropServices.JavaScript; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Browser.Interop; |
|||
using Avalonia.Logging; |
|||
using Avalonia.Platform; |
|||
using BrowserScreenHelper = Avalonia.Browser.Interop.ScreenHelper; |
|||
|
|||
namespace Avalonia.Browser; |
|||
|
|||
internal sealed class BrowserScreen(JSObject screen) : PlatformScreen(new JSObjectPlatformHandle(screen)) |
|||
{ |
|||
internal bool IsCurrent { get; set; } |
|||
|
|||
public void Refresh() |
|||
{ |
|||
IsCurrent = BrowserScreenHelper.IsCurrent(screen); |
|||
DisplayName = BrowserScreenHelper.GetDisplayName(screen); |
|||
Scaling = BrowserScreenHelper.GetScaling(screen); |
|||
IsPrimary = BrowserScreenHelper.IsPrimary(screen); |
|||
CurrentOrientation = (ScreenOrientation)BrowserScreenHelper.GetCurrentOrientation(screen); |
|||
Bounds = BrowserScreenHelper.GetBounds(screen) is { } boundsArr ? |
|||
new PixelRect((int)boundsArr[0], (int)boundsArr[1], (int)boundsArr[2], (int)boundsArr[3]) : |
|||
new PixelRect(); |
|||
WorkingArea = BrowserScreenHelper.GetWorkingArea(screen) is { } workingAreaArr ? |
|||
new PixelRect((int)workingAreaArr[0], (int)workingAreaArr[1], (int)workingAreaArr[2], |
|||
(int)workingAreaArr[3]) : |
|||
new PixelRect(); |
|||
} |
|||
} |
|||
|
|||
internal sealed class BrowserScreens : ScreensBase<JSObject, BrowserScreen> |
|||
{ |
|||
private bool _isExtended; |
|||
|
|||
public BrowserScreens() |
|||
{ |
|||
BrowserScreenHelper.SubscribeOnChanged(BrowserWindowingPlatform.GlobalThis); |
|||
BrowserScreenHelper.CheckPermissions(BrowserWindowingPlatform.GlobalThis); |
|||
} |
|||
|
|||
protected override IReadOnlyList<JSObject> GetAllScreenKeys() => |
|||
BrowserScreenHelper.GetAllScreens(BrowserWindowingPlatform.GlobalThis); |
|||
|
|||
protected override BrowserScreen CreateScreenFromKey(JSObject key) => new(key); |
|||
protected override void ScreenChanged(BrowserScreen screen) => screen.Refresh(); |
|||
|
|||
protected override Screen? ScreenFromTopLevelCore(ITopLevelImpl topLevel) => |
|||
AllScreens.OfType<BrowserScreen>().FirstOrDefault(s => s.IsCurrent); |
|||
|
|||
protected override Screen? ScreenFromPointCore(PixelPoint point) => |
|||
_isExtended ? base.ScreenFromPointCore(point) : null; |
|||
|
|||
protected override Screen? ScreenFromRectCore(PixelRect rect) => _isExtended ? base.ScreenFromRectCore(rect) : null; |
|||
|
|||
protected override async Task<bool> RequestScreenDetailsCore() |
|||
{ |
|||
try |
|||
{ |
|||
return _isExtended = await BrowserScreenHelper.RequestDetailedScreens(BrowserWindowingPlatform.GlobalThis); |
|||
} |
|||
catch (Exception e) |
|||
{ |
|||
Logger.TryGet(LogEventLevel.Warning, LogArea.BrowserPlatform)? |
|||
.Log(this, "Failed to get extended screen details: {Exception}", e); |
|||
return false; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,41 @@ |
|||
using System.Runtime.InteropServices.JavaScript; |
|||
using System.Threading.Tasks; |
|||
|
|||
namespace Avalonia.Browser.Interop; |
|||
|
|||
internal static partial class ScreenHelper |
|||
{ |
|||
[JSImport("ScreenHelper.subscribeOnChanged", AvaloniaModule.MainModuleName)] |
|||
public static partial void SubscribeOnChanged(JSObject globalThis); |
|||
|
|||
[JSImport("ScreenHelper.checkPermissions", AvaloniaModule.MainModuleName)] |
|||
public static partial void CheckPermissions(JSObject globalThis); |
|||
|
|||
[JSImport("ScreenHelper.getAllScreens", AvaloniaModule.MainModuleName)] |
|||
public static partial JSObject[] GetAllScreens(JSObject globalThis); |
|||
|
|||
[JSImport("ScreenHelper.requestDetailedScreens", AvaloniaModule.MainModuleName)] |
|||
[return: JSMarshalAs<JSType.Promise<JSType.Boolean>>] |
|||
public static partial Task<bool> RequestDetailedScreens(JSObject globalThis); |
|||
|
|||
[JSImport("ScreenHelper.getDisplayName", AvaloniaModule.MainModuleName)] |
|||
public static partial string GetDisplayName(JSObject screen); |
|||
|
|||
[JSImport("ScreenHelper.getScaling", AvaloniaModule.MainModuleName)] |
|||
public static partial double GetScaling(JSObject screen); |
|||
|
|||
[JSImport("ScreenHelper.getBounds", AvaloniaModule.MainModuleName)] |
|||
public static partial double[] GetBounds(JSObject screen); |
|||
|
|||
[JSImport("ScreenHelper.getWorkingArea", AvaloniaModule.MainModuleName)] |
|||
public static partial double[] GetWorkingArea(JSObject screen); |
|||
|
|||
[JSImport("ScreenHelper.isCurrent", AvaloniaModule.MainModuleName)] |
|||
public static partial bool IsCurrent(JSObject screen); |
|||
|
|||
[JSImport("ScreenHelper.isPrimary", AvaloniaModule.MainModuleName)] |
|||
public static partial bool IsPrimary(JSObject screen); |
|||
|
|||
[JSImport("ScreenHelper.getCurrentOrientation", AvaloniaModule.MainModuleName)] |
|||
public static partial int GetCurrentOrientation(JSObject screen); |
|||
} |
|||
@ -0,0 +1,138 @@ |
|||
import { JsExports } from "./jsExports"; |
|||
|
|||
type SingleScreen = Screen & { window: Window; availLeft: number; availTop: number }; |
|||
type ScreenDetailedEx = ScreenDetailed & { availLeft: number; availTop: number }; |
|||
type BrowserScreen = ScreenDetailedEx | SingleScreen; |
|||
enum ScreenOrientation { |
|||
None, |
|||
Landscape = 1, |
|||
Portrait = 2, |
|||
LandscapeFlipped = 4, |
|||
PortraitFlipped = 8 |
|||
} |
|||
|
|||
export class ScreenHelper { |
|||
static detailedScreens?: ScreenDetails; |
|||
|
|||
private static raiseOnChanged() { |
|||
JsExports.DomHelper.ScreensChanged(); |
|||
} |
|||
|
|||
public static async checkPermissions(globalThis: Window): Promise<void> { |
|||
// If previous session already granted "window-management" permissions, just re-request details, before they are used.
|
|||
const { state } = await globalThis.navigator.permissions.query({ name: "window-management" } as any); |
|||
if (state === "granted") { |
|||
await this.requestDetailedScreens(globalThis); |
|||
} |
|||
} |
|||
|
|||
public static subscribeOnChanged(globalThis: Window) { |
|||
if (this.detailedScreens) { |
|||
globalThis.screen.removeEventListener("change", this.raiseOnChanged); |
|||
this.detailedScreens.addEventListener("screenschange", this.raiseOnChanged); |
|||
|
|||
// When any screen was added, we re-subscribe on all of them to keep it simpler.
|
|||
// Just like in C#, it's safer to re-subscribe if handler is the same function - it will trigger it once.
|
|||
for (const screen of this.detailedScreens.screens) { |
|||
screen.addEventListener("change", this.raiseOnChanged); |
|||
} |
|||
} else { |
|||
globalThis.screen.addEventListener("change", this.raiseOnChanged); |
|||
} |
|||
} |
|||
|
|||
public static getAllScreens(globalThis: Window): BrowserScreen[] { |
|||
if (this.detailedScreens) { |
|||
return this.detailedScreens.screens as ScreenDetailedEx[]; |
|||
} else { |
|||
const singleScreen = Object.assign(globalThis.screen, { window: globalThis }) as SingleScreen; |
|||
return [singleScreen]; |
|||
} |
|||
} |
|||
|
|||
public static async requestDetailedScreens(globalThis: Window): Promise<boolean> { |
|||
if (this.detailedScreens) { |
|||
return true; |
|||
} |
|||
if ("getScreenDetails" in globalThis) { |
|||
this.detailedScreens = await globalThis.getScreenDetails(); |
|||
if (this.detailedScreens) { |
|||
this.subscribeOnChanged(globalThis); |
|||
globalThis.setTimeout(this.raiseOnChanged, 1); |
|||
return true; |
|||
} |
|||
} |
|||
return false; |
|||
} |
|||
|
|||
static getDisplayName(screen: BrowserScreen) { |
|||
return (screen as ScreenDetailed)?.label; |
|||
} |
|||
|
|||
static getScaling(screen: BrowserScreen) { |
|||
if ("devicePixelRatio" in screen) { |
|||
return screen.devicePixelRatio; |
|||
} |
|||
if ("window" in screen) { |
|||
return screen.window.devicePixelRatio; |
|||
} |
|||
return 1; |
|||
} |
|||
|
|||
static getBounds(screen: BrowserScreen): number[] { |
|||
const width = screen.width; |
|||
const height = screen.height; |
|||
|
|||
if ("left" in screen && "top" in screen) { |
|||
return [screen.left, screen.top, width, height]; |
|||
} else if ("availLeft" in screen && "availTop" in screen) { |
|||
// If webapp doesn't have "window-management" perms, "left" and "top" will be undefined.
|
|||
// To keep getBounds consistent getWorkingArea, while still usable, fallback to availLeft and availTop values.
|
|||
return [screen.availLeft, screen.availTop, width, height]; |
|||
} |
|||
|
|||
return [0, 0, width, height]; |
|||
} |
|||
|
|||
static getWorkingArea(screen: BrowserScreen): number[] { |
|||
const width = screen.availWidth; |
|||
const height = screen.availHeight; |
|||
|
|||
if ("availLeft" in screen && "availTop" in screen) { |
|||
return [screen.availLeft, screen.availTop, width, height]; |
|||
} |
|||
return [0, 0, width, height]; |
|||
} |
|||
|
|||
static isCurrent(screen: BrowserScreen): boolean { |
|||
if (this.detailedScreens) { |
|||
return this.detailedScreens.currentScreen === screen; |
|||
} |
|||
|
|||
// If detailedScreens were not requested - we have a single screen which always is a current one.
|
|||
return true; |
|||
} |
|||
|
|||
static isPrimary(screen: BrowserScreen): boolean { |
|||
if ("isPrimary" in screen) { |
|||
return screen.isPrimary; |
|||
} |
|||
|
|||
// If detailedScreens were not requested - we have a single screen which always is a current one, and we assume it's a primary one as well.
|
|||
return true; |
|||
} |
|||
|
|||
/* eslint-disable indent */ |
|||
static getCurrentOrientation(screen: BrowserScreen): ScreenOrientation { |
|||
switch (screen.orientation.type) { |
|||
case "landscape-primary": |
|||
return ScreenOrientation.Landscape; |
|||
case "landscape-secondary": |
|||
return ScreenOrientation.LandscapeFlipped; |
|||
case "portrait-primary": |
|||
return ScreenOrientation.Portrait; |
|||
case "portrait-secondary": |
|||
return ScreenOrientation.PortraitFlipped; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,73 @@ |
|||
using Avalonia.Platform; |
|||
using Tizen.Applications; |
|||
using Tizen.Multimedia; |
|||
using Tizen.NUI; |
|||
using Tizen.System; |
|||
|
|||
namespace Avalonia.Tizen; |
|||
|
|||
internal class NuiScreens : ScreensBase<int, SingleTizenScreen> |
|||
{ |
|||
// See https://github.com/dotnet/maui/blob/8.0.70/src/Essentials/src/DeviceDisplay/DeviceDisplay.tizen.cs
|
|||
internal const float BaseLogicalDpi = 160.0f; |
|||
|
|||
internal static DeviceOrientation LastDeviceOrientation { get; private set; } |
|||
|
|||
internal static int DisplayWidth => |
|||
Information.TryGetValue<int>("http://tizen.org/feature/screen.width", out var value) ? value : 0; |
|||
|
|||
internal static int DisplayHeight => |
|||
Information.TryGetValue<int>("http://tizen.org/feature/screen.height", out var value) ? value : 0; |
|||
|
|||
internal static int DisplayDpi => TizenRuntimePlatform.Info.Value.IsTV ? 72 : |
|||
Information.TryGetValue<int>("http://tizen.org/feature/screen.dpi", out var value) ? value : 72; |
|||
|
|||
public NuiScreens() |
|||
{ |
|||
((CoreApplication)global::Tizen.Applications.Application.Current).DeviceOrientationChanged += (sender, args) => |
|||
{ |
|||
LastDeviceOrientation = args.DeviceOrientation; |
|||
OnChanged(); |
|||
}; |
|||
} |
|||
|
|||
protected override int GetScreenCount() => 1; |
|||
|
|||
protected override IReadOnlyList<int> GetAllScreenKeys() => [1]; |
|||
|
|||
protected override SingleTizenScreen CreateScreenFromKey(int key) |
|||
{ |
|||
var screen = new SingleTizenScreen(key); |
|||
screen.Refresh(); |
|||
return screen; |
|||
} |
|||
|
|||
protected override void ScreenChanged(SingleTizenScreen screen) => screen.Refresh(); |
|||
} |
|||
|
|||
internal class SingleTizenScreen(int index) : PlatformScreen(new PlatformHandle(new IntPtr(index), nameof(SingleTizenScreen))) |
|||
{ |
|||
public void Refresh() |
|||
{ |
|||
IsPrimary = index == 1; |
|||
if (IsPrimary) |
|||
{ |
|||
Bounds = WorkingArea = new PixelRect(0, 0, NuiScreens.DisplayWidth, NuiScreens.DisplayHeight); |
|||
Scaling = NuiScreens.DisplayDpi / NuiScreens.BaseLogicalDpi; |
|||
|
|||
var isNaturalLandscape = Bounds.Width > Bounds.Height; |
|||
CurrentOrientation = (isNaturalLandscape, NuiScreens.LastDeviceOrientation) switch |
|||
{ |
|||
(true, DeviceOrientation.Orientation_0) => ScreenOrientation.Landscape, |
|||
(true, DeviceOrientation.Orientation_90) => ScreenOrientation.Portrait, |
|||
(true, DeviceOrientation.Orientation_180) => ScreenOrientation.LandscapeFlipped, |
|||
(true, DeviceOrientation.Orientation_270) => ScreenOrientation.PortraitFlipped, |
|||
(false, DeviceOrientation.Orientation_0) => ScreenOrientation.Portrait, |
|||
(false, DeviceOrientation.Orientation_90) => ScreenOrientation.Landscape, |
|||
(false, DeviceOrientation.Orientation_180) => ScreenOrientation.PortraitFlipped, |
|||
(false, DeviceOrientation.Orientation_270) => ScreenOrientation.LandscapeFlipped, |
|||
_ => ScreenOrientation.None |
|||
}; |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,75 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Platform; |
|||
using Foundation; |
|||
using ObjCRuntime; |
|||
using UIKit; |
|||
|
|||
namespace Avalonia.iOS; |
|||
|
|||
internal class iOSScreen(UIScreen screen) : PlatformScreen(new PlatformHandle(screen.Handle.Handle, nameof(UIScreen))) |
|||
{ |
|||
public void Refresh() |
|||
{ |
|||
IsPrimary = screen.Equals(UIScreen.MainScreen); |
|||
Scaling = screen.NativeScale; |
|||
DisplayName = IsPrimary ? nameof(UIScreen.MainScreen) : null; |
|||
|
|||
var nativeBounds = screen.NativeBounds; |
|||
var scaledBounds = screen.Bounds; |
|||
|
|||
#if !TVOS && !MACCATALYST
|
|||
var uiOrientation = IsPrimary ? |
|||
UIDevice.CurrentDevice.Orientation : |
|||
UIDeviceOrientation.LandscapeLeft; |
|||
CurrentOrientation = uiOrientation switch |
|||
{ |
|||
UIDeviceOrientation.Portrait => ScreenOrientation.Portrait, |
|||
UIDeviceOrientation.PortraitUpsideDown => ScreenOrientation.PortraitFlipped, |
|||
UIDeviceOrientation.LandscapeLeft => ScreenOrientation.Landscape, |
|||
UIDeviceOrientation.LandscapeRight => ScreenOrientation.LandscapeFlipped, |
|||
UIDeviceOrientation.FaceUp or UIDeviceOrientation.FaceDown => |
|||
nativeBounds.Width > nativeBounds.Height ? ScreenOrientation.Landscape : ScreenOrientation.Portrait, |
|||
_ => ScreenOrientation.None |
|||
}; |
|||
#endif
|
|||
|
|||
// "The bounding rectangle of the physical screen, measured in pixels" - so just cast it to int.
|
|||
// "This value does not change as the device rotates." - we need to rotate it to match other platforms.
|
|||
// As a reference, scaled bounds are always rotated.
|
|||
WorkingArea = Bounds = scaledBounds.Width > scaledBounds.Height && nativeBounds.Width < nativeBounds.Height ? |
|||
new PixelRect((int)nativeBounds.X, (int)nativeBounds.Y, (int)nativeBounds.Height, (int)nativeBounds.Width) : |
|||
new PixelRect((int)nativeBounds.X, (int)nativeBounds.Y, (int)nativeBounds.Width, (int)nativeBounds.Height); |
|||
} |
|||
} |
|||
|
|||
internal class iOSScreens : ScreensBase<UIScreen, iOSScreen> |
|||
{ |
|||
public iOSScreens() |
|||
{ |
|||
UIScreen.Notifications.ObserveDidConnect(OnScreenChanged); |
|||
UIScreen.Notifications.ObserveDidDisconnect(OnScreenChanged); |
|||
UIScreen.Notifications.ObserveModeDidChange(OnScreenChanged); |
|||
#if !TVOS
|
|||
UIDevice.Notifications.ObserveOrientationDidChange(OnScreenChanged); |
|||
#endif
|
|||
|
|||
void OnScreenChanged(object? sender, NSNotificationEventArgs e) => OnChanged(); |
|||
} |
|||
|
|||
protected override IReadOnlyList<UIScreen> GetAllScreenKeys() => UIScreen.Screens; |
|||
|
|||
protected override iOSScreen CreateScreenFromKey(UIScreen key) => new(key); |
|||
|
|||
protected override void ScreenChanged(iOSScreen screen) => screen.Refresh(); |
|||
|
|||
protected override Screen? ScreenFromPointCore(PixelPoint point) => null; |
|||
|
|||
protected override Screen? ScreenFromRectCore(PixelRect rect) => null; |
|||
|
|||
protected override Screen? ScreenFromTopLevelCore(ITopLevelImpl topLevel) |
|||
{ |
|||
var uiScreen = (topLevel as AvaloniaView.TopLevelImpl)?.View.Window.Screen; |
|||
return uiScreen is not null && TryGetScreen(uiScreen, out var screen) ? screen : null; |
|||
} |
|||
} |
|||
Loading…
Reference in new issue