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