зеркало из https://github.com/AvaloniaUI/Avalonia.git
Screens api refactor mobile (#16647)
* 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 suppression
This commit is contained in:
Родитель
afa60f38de
Коммит
37ba4ce58e
|
@ -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>
|
|
@ -1,5 +1,5 @@
|
|||
using System;
|
||||
|
||||
using Android.Runtime;
|
||||
using Android.Views;
|
||||
|
||||
using Avalonia.Controls.Platform;
|
||||
|
@ -7,20 +7,16 @@ using Avalonia.Platform;
|
|||
|
||||
namespace Avalonia.Android
|
||||
{
|
||||
public class AndroidViewControlHandle : INativeControlHostDestroyableControlHandle
|
||||
public class AndroidViewControlHandle : PlatformHandle, INativeControlHostDestroyableControlHandle
|
||||
{
|
||||
internal const string AndroidDescriptor = "JavaObjectHandle";
|
||||
internal static string AndroidViewDescriptor = "android.view.View";
|
||||
|
||||
public AndroidViewControlHandle(View view)
|
||||
public AndroidViewControlHandle(View view) : base(view.Handle, AndroidViewDescriptor)
|
||||
{
|
||||
View = view;
|
||||
}
|
||||
|
||||
public View View { get; }
|
||||
|
||||
public string HandleDescriptor => AndroidDescriptor;
|
||||
|
||||
IntPtr IPlatformHandle.Handle => View.Handle;
|
||||
public View View { get; private set; }
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
|
|
|
@ -121,6 +121,7 @@ namespace Avalonia.Android
|
|||
var settings =
|
||||
AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>() as AndroidPlatformSettings;
|
||||
settings?.OnViewConfigurationChanged(context);
|
||||
((AndroidScreens)_view.TryGetFeature<IScreenImpl>()!).OnChanged();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -52,7 +52,7 @@ namespace Avalonia.Android.Platform
|
|||
};
|
||||
}
|
||||
|
||||
public bool IsCompatibleWith(IPlatformHandle handle) => handle.HandleDescriptor == AndroidViewControlHandle.AndroidDescriptor;
|
||||
public bool IsCompatibleWith(IPlatformHandle handle) => handle.HandleDescriptor == AndroidViewControlHandle.AndroidViewDescriptor;
|
||||
|
||||
private class AndroidNativeControlAttachment : INativeControlHostControlTopLevelAttachment
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -44,6 +44,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
|
|||
private readonly AndroidInsetsManager? _insetsManager;
|
||||
private readonly ClipboardImpl _clipboard;
|
||||
private readonly AndroidLauncher? _launcher;
|
||||
private readonly AndroidScreens? _screens;
|
||||
private ViewImpl _view;
|
||||
private WindowTransparencyLevel _transparencyLevel;
|
||||
|
||||
|
@ -61,6 +62,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
|
|||
_gl = new EglGlPlatformSurface(this);
|
||||
_framebuffer = new FramebufferManager(this);
|
||||
_clipboard = new ClipboardImpl(avaloniaView.Context.GetSystemService(Context.ClipboardService).JavaCast<ClipboardManager>());
|
||||
_screens = new AndroidScreens(avaloniaView.Context);
|
||||
|
||||
RenderScaling = _view.Scaling;
|
||||
|
||||
|
@ -399,6 +401,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
|
|||
return _launcher;
|
||||
}
|
||||
|
||||
if (featureType == typeof(IScreenImpl))
|
||||
{
|
||||
return _screens;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -165,6 +165,11 @@ namespace Avalonia.Browser
|
|||
return AvaloniaLocator.Current.GetService<ISystemNavigationManagerImpl>();
|
||||
}
|
||||
|
||||
if (featureType == typeof(IScreenImpl))
|
||||
{
|
||||
return AvaloniaLocator.Current.GetService<IScreenImpl>();
|
||||
}
|
||||
|
||||
if (featureType == typeof(INativeControlHostImpl))
|
||||
{
|
||||
return _nativeControlHost;
|
||||
|
|
|
@ -50,4 +50,11 @@ internal static partial class DomHelper
|
|||
(AvaloniaLocator.Current.GetService<IActivatableLifetime>() as BrowserActivatableLifetime)?.OnVisibilityStateChanged(visibilityState);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
[JSExport]
|
||||
public static Task ScreensChanged()
|
||||
{
|
||||
(AvaloniaLocator.Current.GetService<IScreenImpl>() as BrowserScreens)?.OnChanged();
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
|
@ -1,28 +1,34 @@
|
|||
using System;
|
||||
using System.Runtime.CompilerServices;
|
||||
using System.Runtime.InteropServices.JavaScript;
|
||||
|
||||
using Avalonia.Controls.Platform;
|
||||
using Avalonia.Platform;
|
||||
|
||||
namespace Avalonia.Browser;
|
||||
|
||||
public class JSObjectControlHandle : INativeControlHostDestroyableControlHandle
|
||||
public class JSObjectPlatformHandle : PlatformHandle
|
||||
{
|
||||
internal const string ElementReferenceDescriptor = "JSObject";
|
||||
|
||||
public JSObjectControlHandle(JSObject reference)
|
||||
// GetHashCode returns internal JSHandle.
|
||||
internal JSObjectPlatformHandle(JSObject reference) : base(reference.GetHashCode(), ElementReferenceDescriptor)
|
||||
{
|
||||
Object = reference;
|
||||
}
|
||||
|
||||
public JSObject Object { get; }
|
||||
}
|
||||
|
||||
public IntPtr Handle => throw new NotSupportedException();
|
||||
|
||||
public string? HandleDescriptor => ElementReferenceDescriptor;
|
||||
public class JSObjectControlHandle : JSObjectPlatformHandle, INativeControlHostDestroyableControlHandle
|
||||
{
|
||||
public JSObjectControlHandle(JSObject reference) : base(reference)
|
||||
{
|
||||
}
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
if (Object is JSObject inProcess && !inProcess.IsDisposed)
|
||||
if (Object is { } inProcess && !inProcess.IsDisposed)
|
||||
{
|
||||
inProcess.Dispose();
|
||||
}
|
||||
|
|
|
@ -88,6 +88,7 @@ internal class BrowserWindowingPlatform : IWindowingPlatform
|
|||
.Bind<IKeyboardDevice>().ToConstant(s_keyboard)
|
||||
.Bind<IPlatformSettings>().ToSingleton<BrowserPlatformSettings>()
|
||||
.Bind<ISystemNavigationManagerImpl>().ToSingleton<BrowserSystemNavigationManagerImpl>()
|
||||
.Bind<IScreenImpl>().ToSingleton<BrowserScreens>()
|
||||
.Bind<IWindowingPlatform>().ToConstant(instance)
|
||||
.Bind<IPlatformIconLoader>().ToSingleton<IconLoaderStub>()
|
||||
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
|
||||
|
|
|
@ -11,6 +11,7 @@ import { WebRenderTargetRegistry } from "./avalonia/rendering/webRenderTargetReg
|
|||
import { WebRenderTarget } from "./avalonia/rendering/webRenderTarget";
|
||||
import { SoftwareRenderTarget } from "./avalonia/rendering/softwareRenderTarget";
|
||||
import { WebGlRenderTarget } from "./avalonia/rendering/webGlRenderTarget";
|
||||
import { ScreenHelper } from "./avalonia/screens";
|
||||
|
||||
async function registerServiceWorker(path: string, scope: string | undefined) {
|
||||
if ("serviceWorker" in navigator) {
|
||||
|
@ -26,6 +27,7 @@ export {
|
|||
NativeControlHost,
|
||||
NavigationHelper,
|
||||
GeneralHelpers,
|
||||
ScreenHelper,
|
||||
TimerHelper,
|
||||
WebRenderTarget,
|
||||
CanvasSurface,
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -11,6 +11,7 @@
|
|||
"devDependencies": {
|
||||
"@types/emscripten": "^1.39.6",
|
||||
"@types/offscreencanvas": "2019.7.1",
|
||||
"@types/webscreens-window-placement": "^0.1.3",
|
||||
"@typescript-eslint/eslint-plugin": "^5.38.1",
|
||||
"esbuild": "^0.15.7",
|
||||
"eslint": "^8.24.0",
|
||||
|
@ -179,6 +180,12 @@
|
|||
"integrity": "sha512-+HSrJgjBW77ALieQdMJvXhRZUIRN1597L+BKvsyeiIlHHERnqjcuOLyodK3auJ3Y3zRezNKtKAhuQWYJfEgFHQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/webscreens-window-placement": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/webscreens-window-placement/-/webscreens-window-placement-0.1.3.tgz",
|
||||
"integrity": "sha512-OunHLGJkmAuNlvd7PrRbQy/VleLyxxP3NKwuUo9OS412vO/tzKGwW2K3FqvnM1yebTkCM0W+gszr08m9oDz0lg==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@typescript-eslint/eslint-plugin": {
|
||||
"version": "5.38.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.38.1.tgz",
|
||||
|
@ -493,12 +500,12 @@
|
|||
}
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"fill-range": "^7.0.1"
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
|
@ -1612,9 +1619,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
|
@ -2248,18 +2255,6 @@
|
|||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"yallist": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/memorystream": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
|
||||
|
@ -2380,9 +2375,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/normalize-package-data/node_modules/semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver"
|
||||
|
@ -2498,9 +2493,9 @@
|
|||
}
|
||||
},
|
||||
"node_modules/npm-run-all/node_modules/semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"semver": "bin/semver"
|
||||
|
@ -2942,13 +2937,10 @@
|
|||
}
|
||||
},
|
||||
"node_modules/semver": {
|
||||
"version": "7.3.7",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
|
||||
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
|
||||
"version": "7.6.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
|
||||
"integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"lru-cache": "^6.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"semver": "bin/semver.js"
|
||||
},
|
||||
|
@ -3317,12 +3309,6 @@
|
|||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
|
@ -3447,6 +3433,12 @@
|
|||
"integrity": "sha512-+HSrJgjBW77ALieQdMJvXhRZUIRN1597L+BKvsyeiIlHHERnqjcuOLyodK3auJ3Y3zRezNKtKAhuQWYJfEgFHQ==",
|
||||
"dev": true
|
||||
},
|
||||
"@types/webscreens-window-placement": {
|
||||
"version": "0.1.3",
|
||||
"resolved": "https://registry.npmjs.org/@types/webscreens-window-placement/-/webscreens-window-placement-0.1.3.tgz",
|
||||
"integrity": "sha512-OunHLGJkmAuNlvd7PrRbQy/VleLyxxP3NKwuUo9OS412vO/tzKGwW2K3FqvnM1yebTkCM0W+gszr08m9oDz0lg==",
|
||||
"dev": true
|
||||
},
|
||||
"@typescript-eslint/eslint-plugin": {
|
||||
"version": "5.38.1",
|
||||
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.38.1.tgz",
|
||||
|
@ -3636,12 +3628,12 @@
|
|||
}
|
||||
},
|
||||
"braces": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
|
||||
"integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"fill-range": "^7.0.1"
|
||||
"fill-range": "^7.1.1"
|
||||
}
|
||||
},
|
||||
"builtins": {
|
||||
|
@ -4370,9 +4362,9 @@
|
|||
}
|
||||
},
|
||||
"fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
|
||||
"integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
|
@ -4829,15 +4821,6 @@
|
|||
"integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
|
||||
"dev": true
|
||||
},
|
||||
"lru-cache": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
|
||||
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"yallist": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"memorystream": {
|
||||
"version": "0.3.1",
|
||||
"resolved": "https://registry.npmjs.org/memorystream/-/memorystream-0.3.1.tgz",
|
||||
|
@ -4919,9 +4902,9 @@
|
|||
},
|
||||
"dependencies": {
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||
"dev": true
|
||||
}
|
||||
}
|
||||
|
@ -5010,9 +4993,9 @@
|
|||
"dev": true
|
||||
},
|
||||
"semver": {
|
||||
"version": "5.7.1",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz",
|
||||
"integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==",
|
||||
"version": "5.7.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
|
||||
"integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
|
||||
"dev": true
|
||||
},
|
||||
"shebang-command": {
|
||||
|
@ -5303,13 +5286,10 @@
|
|||
}
|
||||
},
|
||||
"semver": {
|
||||
"version": "7.3.7",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.3.7.tgz",
|
||||
"integrity": "sha512-QlYTucUYOews+WeEujDoEGziz4K6c47V/Bd+LjSSYcA94p+DmINdf7ncaUinThfvZyu13lN9OY1XDxt8C0Tw0g==",
|
||||
"dev": true,
|
||||
"requires": {
|
||||
"lru-cache": "^6.0.0"
|
||||
}
|
||||
"version": "7.6.2",
|
||||
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.2.tgz",
|
||||
"integrity": "sha512-FNAIBWCx9qcRhoHcgcJ0gvU7SN1lYU2ZXuSfl04bSC5OpvDHFyJCjdNHomPXxjQlCBU67YW64PzY7/VIEH7F2w==",
|
||||
"dev": true
|
||||
},
|
||||
"shebang-command": {
|
||||
"version": "2.0.0",
|
||||
|
@ -5584,12 +5564,6 @@
|
|||
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
|
||||
"dev": true
|
||||
},
|
||||
"yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
|
||||
"dev": true
|
||||
},
|
||||
"yocto-queue": {
|
||||
"version": "0.1.0",
|
||||
"resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
"@types/emscripten": "^1.39.6",
|
||||
"@types/offscreencanvas": "2019.7.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.38.1",
|
||||
"@types/webscreens-window-placement": "^0.1.3",
|
||||
"esbuild": "^0.15.7",
|
||||
"eslint": "^8.24.0",
|
||||
"eslint-config-standard-with-typescript": "^23.0.0",
|
||||
|
|
|
@ -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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
|
@ -22,7 +22,7 @@ internal static class TizenRuntimePlatformServices
|
|||
|
||||
internal class TizenRuntimePlatform : StandardRuntimePlatform
|
||||
{
|
||||
private static readonly Lazy<RuntimePlatformInfo> Info = new(() =>
|
||||
public static readonly Lazy<RuntimePlatformInfo> Info = new(() =>
|
||||
{
|
||||
global::Tizen.System.Information.TryGetValue("http://tizen.org/feature/profile", out string profile);
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ internal class TopLevelImpl : ITopLevelImpl
|
|||
private readonly ITizenView _view;
|
||||
private readonly NuiClipboardImpl _clipboard;
|
||||
private readonly IStorageProvider _storageProvider;
|
||||
private readonly NuiScreens _screen;
|
||||
|
||||
public TopLevelImpl(ITizenView view, IEnumerable<object> surfaces)
|
||||
{
|
||||
|
@ -24,6 +25,7 @@ internal class TopLevelImpl : ITopLevelImpl
|
|||
|
||||
_storageProvider = new TizenStorageProvider();
|
||||
_clipboard = new NuiClipboardImpl();
|
||||
_screen = new NuiScreens();
|
||||
}
|
||||
|
||||
public double DesktopScaling => RenderScaling;
|
||||
|
@ -112,6 +114,11 @@ internal class TopLevelImpl : ITopLevelImpl
|
|||
return new TizenLauncher();
|
||||
}
|
||||
|
||||
if (featureType == typeof(IScreenImpl))
|
||||
{
|
||||
return _screen;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
|
|
|
@ -296,6 +296,11 @@ namespace Avalonia.iOS
|
|||
return new IOSLauncher();
|
||||
}
|
||||
|
||||
if (featureType == typeof(IScreenImpl))
|
||||
{
|
||||
return (iOSScreens)AvaloniaLocator.Current.GetRequiredService<IScreenImpl>();
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,7 @@ using System.Diagnostics.CodeAnalysis;
|
|||
using Avalonia.Controls.Platform;
|
||||
using Avalonia.Platform;
|
||||
using CoreGraphics;
|
||||
using Foundation;
|
||||
using ObjCRuntime;
|
||||
using UIKit;
|
||||
|
||||
|
@ -134,21 +135,16 @@ namespace Avalonia.iOS
|
|||
}
|
||||
}
|
||||
|
||||
public class UIViewControlHandle : INativeControlHostDestroyableControlHandle
|
||||
public class UIViewControlHandle : PlatformHandle, INativeControlHostDestroyableControlHandle
|
||||
{
|
||||
internal const string UIViewDescriptor = "UIView";
|
||||
|
||||
|
||||
public UIViewControlHandle(UIView view)
|
||||
public UIViewControlHandle(UIView view) : base(view.Handle.Handle, UIViewDescriptor)
|
||||
{
|
||||
View = view;
|
||||
}
|
||||
|
||||
public UIView View { get; }
|
||||
|
||||
public string HandleDescriptor => UIViewDescriptor;
|
||||
|
||||
IntPtr IPlatformHandle.Handle => View.Handle.Handle;
|
||||
|
||||
public void Destroy()
|
||||
{
|
||||
|
|
|
@ -82,6 +82,7 @@ namespace Avalonia.iOS
|
|||
.Bind<IWindowingPlatform>().ToConstant(new WindowingPlatformStub())
|
||||
.Bind<IPlatformSettings>().ToSingleton<PlatformSettings>()
|
||||
.Bind<IPlatformIconLoader>().ToConstant(new PlatformIconLoaderStub())
|
||||
.Bind<IScreenImpl>().ToSingleton<iOSScreens>()
|
||||
.Bind<PlatformHotkeyConfiguration>().ToSingleton<PlatformHotkeyConfiguration>()
|
||||
.Bind<KeyGestureFormatInfo>().ToConstant(new KeyGestureFormatInfo(new Dictionary<Key, string>()
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
Загрузка…
Ссылка в новой задаче