@ -0,0 +1,16 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<Application |
||||
|
x:Class="ControlGallery.WinUI.App" |
||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
xmlns:local="using:ControlGallery.WinUI"> |
||||
|
<Application.Resources> |
||||
|
<ResourceDictionary> |
||||
|
<ResourceDictionary.MergedDictionaries> |
||||
|
<XamlControlsResources xmlns="using:Microsoft.UI.Xaml.Controls" /> |
||||
|
<!-- Other merged dictionaries here --> |
||||
|
</ResourceDictionary.MergedDictionaries> |
||||
|
<!-- Other app resources here --> |
||||
|
</ResourceDictionary> |
||||
|
</Application.Resources> |
||||
|
</Application> |
||||
@ -0,0 +1,31 @@ |
|||||
|
using global::Avalonia; |
||||
|
using global::Avalonia.Skia; |
||||
|
using global::Avalonia.Win32; |
||||
|
using WinUIApplication = Microsoft.UI.Xaml.Application; |
||||
|
using LaunchActivatedEventArgs = Microsoft.UI.Xaml.LaunchActivatedEventArgs; |
||||
|
using Window = Microsoft.UI.Xaml.Window; |
||||
|
|
||||
|
namespace ControlGallery.WinUI |
||||
|
{ |
||||
|
public partial class App : WinUIApplication |
||||
|
{ |
||||
|
private Window? _window; |
||||
|
|
||||
|
public App() |
||||
|
{ |
||||
|
InitializeComponent(); |
||||
|
} |
||||
|
|
||||
|
protected override void OnLaunched(LaunchActivatedEventArgs args) |
||||
|
{ |
||||
|
AppBuilder.Configure<ControlCatalog.App>() |
||||
|
.UseWin32() |
||||
|
.UseSkia() |
||||
|
.UseHarfBuzz() |
||||
|
.SetupWithoutStarting(); |
||||
|
|
||||
|
_window = new MainWindow(); |
||||
|
_window.Activate(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
After Width: | Height: | Size: 432 B |
|
After Width: | Height: | Size: 5.2 KiB |
|
After Width: | Height: | Size: 1.7 KiB |
|
After Width: | Height: | Size: 637 B |
|
After Width: | Height: | Size: 283 B |
|
After Width: | Height: | Size: 456 B |
|
After Width: | Height: | Size: 2.0 KiB |
@ -0,0 +1,67 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
<PropertyGroup> |
||||
|
<OutputType>WinExe</OutputType> |
||||
|
<TargetFramework>$(AvsCurrentWinUITargetFramework)</TargetFramework> |
||||
|
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion> |
||||
|
<RootNamespace>ControlGallery.WinUI</RootNamespace> |
||||
|
<ApplicationManifest>app.manifest</ApplicationManifest> |
||||
|
<Platforms>x86;x64;ARM64</Platforms> |
||||
|
<RuntimeIdentifiers>win-x86;win-x64;win-arm64</RuntimeIdentifiers> |
||||
|
<PublishProfile>win-$(Platform).pubxml</PublishProfile> |
||||
|
<UseWinUI>true</UseWinUI> |
||||
|
<WinUISDKReferences>false</WinUISDKReferences> |
||||
|
<EnableMsixTooling>true</EnableMsixTooling> |
||||
|
<Nullable>enable</Nullable> |
||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<Content Include="Assets\SplashScreen.scale-200.png" /> |
||||
|
<Content Include="Assets\LockScreenLogo.scale-200.png" /> |
||||
|
<Content Include="Assets\Square150x150Logo.scale-200.png" /> |
||||
|
<Content Include="Assets\Square44x44Logo.scale-200.png" /> |
||||
|
<Content Include="Assets\Square44x44Logo.targetsize-24_altform-unplated.png" /> |
||||
|
<Content Include="Assets\StoreLogo.png" /> |
||||
|
<Content Include="Assets\Wide310x150Logo.scale-200.png" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<ItemGroup> |
||||
|
<Manifest Include="$(ApplicationManifest)" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<!-- |
||||
|
Defining the "Msix" ProjectCapability here allows the Single-project MSIX Packaging |
||||
|
Tools extension to be activated for this project even if the Windows App SDK Nuget |
||||
|
package has not yet been restored. |
||||
|
--> |
||||
|
<ItemGroup Condition="'$(DisableMsixProjectCapabilityAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'"> |
||||
|
<ProjectCapability Include="Msix" /> |
||||
|
</ItemGroup> |
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" /> |
||||
|
<PackageReference Include="Microsoft.WindowsAppSDK" /> |
||||
|
</ItemGroup> |
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Windows\Avalonia.Win32\Avalonia.Win32.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\Windows\Avalonia.WinUI\Avalonia.WinUI.csproj" /> |
||||
|
<ProjectReference Include="..\..\src\HarfBuzz\Avalonia.HarfBuzz\Avalonia.HarfBuzz.csproj" /> |
||||
|
</ItemGroup> |
||||
|
|
||||
|
<!-- |
||||
|
Defining the "HasPackageAndPublishMenuAddedByProject" property here allows the Solution |
||||
|
Explorer "Package and Publish" context menu entry to be enabled for this project even if |
||||
|
the Windows App SDK Nuget package has not yet been restored. |
||||
|
--> |
||||
|
<PropertyGroup Condition="'$(DisableHasPackageAndPublishMenuAddedByProject)'!='true' and '$(EnableMsixTooling)'=='true'"> |
||||
|
<HasPackageAndPublishMenu>true</HasPackageAndPublishMenu> |
||||
|
</PropertyGroup> |
||||
|
|
||||
|
<!-- Publish Properties --> |
||||
|
<PropertyGroup> |
||||
|
<PublishReadyToRun Condition="'$(Configuration)' == 'Debug'">False</PublishReadyToRun> |
||||
|
<PublishReadyToRun Condition="'$(Configuration)' != 'Debug'">True</PublishReadyToRun> |
||||
|
<PublishTrimmed Condition="'$(Configuration)' == 'Debug'">False</PublishTrimmed> |
||||
|
<PublishTrimmed Condition="'$(Configuration)' != 'Debug'">True</PublishTrimmed> |
||||
|
</PropertyGroup> |
||||
|
</Project> |
||||
@ -0,0 +1,15 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<Window |
||||
|
x:Class="ControlGallery.WinUI.MainWindow" |
||||
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" |
||||
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" |
||||
|
xmlns:avalonia="using:Avalonia.WinUI" |
||||
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008" |
||||
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" |
||||
|
mc:Ignorable="d" |
||||
|
Title="ControlGallery.WinUI"> |
||||
|
|
||||
|
<Grid> |
||||
|
<avalonia:AvaloniaSwapChainPanel x:Name="AvaloniaPanel" /> |
||||
|
</Grid> |
||||
|
</Window> |
||||
@ -0,0 +1,11 @@ |
|||||
|
namespace ControlGallery.WinUI |
||||
|
{ |
||||
|
public sealed partial class MainWindow : Microsoft.UI.Xaml.Window |
||||
|
{ |
||||
|
public MainWindow() |
||||
|
{ |
||||
|
InitializeComponent(); |
||||
|
AvaloniaPanel.Content = new ControlCatalog.MainView(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,53 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
|
||||
|
<Package |
||||
|
xmlns="http://schemas.microsoft.com/appx/manifest/foundation/windows10" |
||||
|
xmlns:mp="http://schemas.microsoft.com/appx/2014/phone/manifest" |
||||
|
xmlns:uap="http://schemas.microsoft.com/appx/manifest/uap/windows10" |
||||
|
xmlns:rescap="http://schemas.microsoft.com/appx/manifest/foundation/windows10/restrictedcapabilities" |
||||
|
xmlns:systemai="http://schemas.microsoft.com/appx/manifest/systemai/windows10" |
||||
|
IgnorableNamespaces="uap rescap systemai"> |
||||
|
|
||||
|
<Identity |
||||
|
Name="2787a268-3e4a-4dd9-b385-8f677d254cb7" |
||||
|
Publisher="CN=drasticactions" |
||||
|
Version="1.0.0.0" /> |
||||
|
|
||||
|
<mp:PhoneIdentity PhoneProductId="2787a268-3e4a-4dd9-b385-8f677d254cb7" PhonePublisherId="00000000-0000-0000-0000-000000000000"/> |
||||
|
|
||||
|
<Properties> |
||||
|
<DisplayName>ControlGallery.WinUI</DisplayName> |
||||
|
<PublisherDisplayName>drasticactions</PublisherDisplayName> |
||||
|
<Logo>Assets\StoreLogo.png</Logo> |
||||
|
</Properties> |
||||
|
|
||||
|
<Dependencies> |
||||
|
<TargetDeviceFamily Name="Windows.Universal" MinVersion="10.0.17763.0" MaxVersionTested="10.0.26226.0" /> |
||||
|
<TargetDeviceFamily Name="Windows.Desktop" MinVersion="10.0.17763.0" MaxVersionTested="10.0.26226.0" /> |
||||
|
</Dependencies> |
||||
|
|
||||
|
<Resources> |
||||
|
<Resource Language="x-generate"/> |
||||
|
</Resources> |
||||
|
|
||||
|
<Applications> |
||||
|
<Application Id="App" |
||||
|
Executable="$targetnametoken$.exe" |
||||
|
EntryPoint="$targetentrypoint$"> |
||||
|
<uap:VisualElements |
||||
|
DisplayName="ControlGallery.WinUI" |
||||
|
Description="ControlGallery.WinUI" |
||||
|
BackgroundColor="transparent" |
||||
|
Square150x150Logo="Assets\Square150x150Logo.png" |
||||
|
Square44x44Logo="Assets\Square44x44Logo.png"> |
||||
|
<uap:DefaultTile Wide310x150Logo="Assets\Wide310x150Logo.png" /> |
||||
|
<uap:SplashScreen Image="Assets\SplashScreen.png" /> |
||||
|
</uap:VisualElements> |
||||
|
</Application> |
||||
|
</Applications> |
||||
|
|
||||
|
<Capabilities> |
||||
|
<rescap:Capability Name="runFullTrust" /> |
||||
|
<systemai:Capability Name="systemAIModels"/> |
||||
|
</Capabilities> |
||||
|
</Package> |
||||
@ -0,0 +1,10 @@ |
|||||
|
{ |
||||
|
"profiles": { |
||||
|
"ControlGallery.WinUI (Package)": { |
||||
|
"commandName": "MsixPackage" |
||||
|
}, |
||||
|
"ControlGallery.WinUI (Unpackaged)": { |
||||
|
"commandName": "Project" |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,19 @@ |
|||||
|
<?xml version="1.0" encoding="utf-8"?> |
||||
|
<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1"> |
||||
|
<assemblyIdentity version="1.0.0.0" name="ControlGallery.WinUI.app"/> |
||||
|
|
||||
|
<compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1"> |
||||
|
<application> |
||||
|
<!-- The ID below informs the system that this application is compatible with OS features first introduced in Windows 10. |
||||
|
It is necessary to support features in unpackaged applications, for example the custom titlebar implementation. |
||||
|
For more info see https://docs.microsoft.com/windows/apps/windows-app-sdk/use-windows-app-sdk-run-time#declare-os-compatibility-in-your-application-manifest --> |
||||
|
<supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" /> |
||||
|
</application> |
||||
|
</compatibility> |
||||
|
|
||||
|
<application xmlns="urn:schemas-microsoft-com:asm.v3"> |
||||
|
<windowsSettings> |
||||
|
<dpiAwareness xmlns="http://schemas.microsoft.com/SMI/2016/WindowsSettings">PerMonitorV2</dpiAwareness> |
||||
|
</windowsSettings> |
||||
|
</application> |
||||
|
</assembly> |
||||
@ -0,0 +1,29 @@ |
|||||
|
<Project Sdk="Microsoft.NET.Sdk"> |
||||
|
<PropertyGroup> |
||||
|
<TargetFramework>$(AvsCurrentWinUITargetFramework)</TargetFramework> |
||||
|
<TargetPlatformMinVersion>10.0.17763.0</TargetPlatformMinVersion> |
||||
|
<UseWinUI>true</UseWinUI> |
||||
|
<WinUISDKReferences>false</WinUISDKReferences> |
||||
|
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> |
||||
|
<EnableRuntimeMarshalling>true</EnableRuntimeMarshalling> |
||||
|
<Nullable>enable</Nullable> |
||||
|
</PropertyGroup> |
||||
|
<ItemGroup> |
||||
|
<PackageReference Include="Microsoft.Windows.SDK.BuildTools" /> |
||||
|
<PackageReference Include="Microsoft.WindowsAppSDK" /> |
||||
|
</ItemGroup> |
||||
|
<ItemGroup> |
||||
|
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" /> |
||||
|
<ProjectReference Include="..\Avalonia.Win32\Avalonia.Win32.csproj" /> |
||||
|
</ItemGroup> |
||||
|
<Import Project="..\..\..\build\NullableEnable.props" /> |
||||
|
<Import Project="..\..\..\build\DevAnalyzers.props" /> |
||||
|
<Import Project="..\..\..\build\TrimmingEnable.props" /> |
||||
|
<PropertyGroup Label="Warnings"> |
||||
|
<NoWarn>$(NoWarn);CA1416</NoWarn> |
||||
|
</PropertyGroup> |
||||
|
<ItemGroup> |
||||
|
<SupportedPlatform Remove="@(SupportedPlatform)" /> |
||||
|
<SupportedPlatform Include="Windows" /> |
||||
|
</ItemGroup> |
||||
|
</Project> |
||||
@ -0,0 +1,427 @@ |
|||||
|
using System; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using global::Avalonia; |
||||
|
using global::Avalonia.Controls.Embedding; |
||||
|
using global::Avalonia.Input; |
||||
|
using global::Avalonia.Input.Raw; |
||||
|
using Microsoft.UI.Xaml; |
||||
|
using Microsoft.UI.Xaml.Controls; |
||||
|
using Microsoft.UI.Xaml.Input; |
||||
|
using AvControl = global::Avalonia.Controls.Control; |
||||
|
using AvSize = global::Avalonia.Size; |
||||
|
using AvPoint = global::Avalonia.Point; |
||||
|
using AvVector = global::Avalonia.Vector; |
||||
|
using AvRect = global::Avalonia.Rect; |
||||
|
|
||||
|
namespace Avalonia.WinUI; |
||||
|
|
||||
|
public partial class AvaloniaSwapChainPanel : SwapChainPanel |
||||
|
{ |
||||
|
private SwapChainGlSurface? _glSurface; |
||||
|
private SwapChainPanelTopLevelImpl? _topLevelImpl; |
||||
|
private EmbeddableControlRoot? _root; |
||||
|
private AvControl? _content; |
||||
|
private readonly MouseDevice _mouseDevice = new(); |
||||
|
private readonly TouchDevice _touchDevice = new(); |
||||
|
private readonly PenDevice _penDevice = new(releasePointerOnPenUp: false); |
||||
|
private PixelSize _cachedPixelSize = new(1, 1); |
||||
|
private double _cachedScaling = 1.0; |
||||
|
|
||||
|
public AvaloniaSwapChainPanel() |
||||
|
{ |
||||
|
IsTabStop = true; |
||||
|
|
||||
|
Loaded += OnLoaded; |
||||
|
Unloaded += OnUnloaded; |
||||
|
SizeChanged += OnSizeChanged; |
||||
|
CompositionScaleChanged += OnCompositionScaleChanged; |
||||
|
|
||||
|
PointerPressed += OnPointerPressed; |
||||
|
PointerMoved += OnPointerMoved; |
||||
|
PointerReleased += OnPointerReleased; |
||||
|
PointerWheelChanged += OnPointerWheelChanged; |
||||
|
PointerCanceled += OnPointerCanceled; |
||||
|
KeyDown += OnKeyDown; |
||||
|
KeyUp += OnKeyUp; |
||||
|
CharacterReceived += OnCharacterReceived; |
||||
|
} |
||||
|
|
||||
|
public AvControl? Content |
||||
|
{ |
||||
|
get => _content; |
||||
|
set |
||||
|
{ |
||||
|
_content = value; |
||||
|
if (_root is not null) |
||||
|
_root.Content = value; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void UpdateCachedSize() |
||||
|
{ |
||||
|
var w = Math.Max(1, (int)(ActualWidth * CompositionScaleX)); |
||||
|
var h = Math.Max(1, (int)(ActualHeight * CompositionScaleY)); |
||||
|
_cachedPixelSize = new PixelSize(w, h); |
||||
|
_cachedScaling = CompositionScaleX; |
||||
|
} |
||||
|
|
||||
|
private PixelSize GetPixelSize() => _cachedPixelSize; |
||||
|
|
||||
|
private double GetScaling() => _cachedScaling; |
||||
|
|
||||
|
private void OnLoaded(object sender, RoutedEventArgs e) |
||||
|
{ |
||||
|
if (_glSurface is not null) |
||||
|
return; |
||||
|
|
||||
|
UpdateCachedSize(); |
||||
|
|
||||
|
// Create the GL surface — swap chain creation is deferred to CreateGlRenderTarget
|
||||
|
// where we have the actual rendering context's D3D device
|
||||
|
_glSurface = new SwapChainGlSurface(GetPixelSize, GetScaling, OnSwapChainCreated); |
||||
|
_topLevelImpl = new SwapChainPanelTopLevelImpl(_glSurface); |
||||
|
_topLevelImpl.ClientSize = new AvSize(ActualWidth, ActualHeight); |
||||
|
_topLevelImpl.RenderScaling = CompositionScaleX; |
||||
|
|
||||
|
// Create and start the EmbeddableControlRoot
|
||||
|
_root = new EmbeddableControlRoot(_topLevelImpl); |
||||
|
_root.Content = _content; |
||||
|
_root.Prepare(); |
||||
|
_root.StartRendering(); |
||||
|
} |
||||
|
|
||||
|
private unsafe void OnSwapChainCreated(IntPtr swapChainPtr) |
||||
|
{ |
||||
|
// Called from the render thread when the swap chain is first created.
|
||||
|
// Set it on the panel via ISwapChainPanelNative COM interop.
|
||||
|
DispatcherQueue.TryEnqueue(() => |
||||
|
{ |
||||
|
var panelUnknown = Marshal.GetIUnknownForObject(this); |
||||
|
try |
||||
|
{ |
||||
|
var iid = new Guid("63aad0b8-7c24-40ff-85a8-640d944cc325"); |
||||
|
Marshal.QueryInterface(panelUnknown, in iid, out var nativePtr); |
||||
|
if (nativePtr != IntPtr.Zero) |
||||
|
{ |
||||
|
try |
||||
|
{ |
||||
|
var vtable = *(IntPtr**)nativePtr; |
||||
|
var setSwapChain = (delegate* unmanaged[Stdcall]<IntPtr, IntPtr, int>)vtable[3]; |
||||
|
var hr = setSwapChain(nativePtr, swapChainPtr); |
||||
|
Marshal.ThrowExceptionForHR(hr); |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
Marshal.Release(nativePtr); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
Marshal.Release(panelUnknown); |
||||
|
} |
||||
|
}); |
||||
|
} |
||||
|
|
||||
|
private void OnUnloaded(object sender, RoutedEventArgs e) |
||||
|
{ |
||||
|
_root?.StopRendering(); |
||||
|
_root?.Dispose(); |
||||
|
_root = null; |
||||
|
_topLevelImpl = null; |
||||
|
_glSurface?.DisposeSwapChain(); |
||||
|
_glSurface = null; |
||||
|
_touchDevice.Dispose(); |
||||
|
_penDevice.Dispose(); |
||||
|
} |
||||
|
|
||||
|
private void OnSizeChanged(object sender, SizeChangedEventArgs e) |
||||
|
{ |
||||
|
UpdateCachedSize(); |
||||
|
if (_topLevelImpl is not null) |
||||
|
_topLevelImpl.ClientSize = new AvSize(e.NewSize.Width, e.NewSize.Height); |
||||
|
} |
||||
|
|
||||
|
private void OnCompositionScaleChanged(SwapChainPanel sender, object args) |
||||
|
{ |
||||
|
UpdateCachedSize(); |
||||
|
if (_topLevelImpl is not null) |
||||
|
_topLevelImpl.RenderScaling = CompositionScaleX; |
||||
|
} |
||||
|
|
||||
|
// Input forwarding
|
||||
|
|
||||
|
private IPointerDevice GetPointerDevice(PointerRoutedEventArgs e) |
||||
|
{ |
||||
|
return e.Pointer.PointerDeviceType switch |
||||
|
{ |
||||
|
Microsoft.UI.Input.PointerDeviceType.Touch => _touchDevice, |
||||
|
Microsoft.UI.Input.PointerDeviceType.Pen => _penDevice, |
||||
|
_ => _mouseDevice |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private RawPointerPoint CreateRawPointerPoint(PointerRoutedEventArgs e) |
||||
|
{ |
||||
|
var point = e.GetCurrentPoint(this); |
||||
|
var props = point.Properties; |
||||
|
var pos = point.Position; |
||||
|
|
||||
|
var rawPoint = new RawPointerPoint |
||||
|
{ |
||||
|
Position = new AvPoint(pos.X, pos.Y), |
||||
|
Pressure = props.Pressure, |
||||
|
Twist = props.Twist, |
||||
|
XTilt = props.XTilt, |
||||
|
YTilt = props.YTilt, |
||||
|
}; |
||||
|
|
||||
|
if (props.ContactRect is { Width: > 0 } or { Height: > 0 }) |
||||
|
{ |
||||
|
var cr = props.ContactRect; |
||||
|
rawPoint.ContactRect = new AvRect(cr.X, cr.Y, cr.Width, cr.Height); |
||||
|
} |
||||
|
|
||||
|
return rawPoint; |
||||
|
} |
||||
|
|
||||
|
private RawPointerEventArgs CreatePointerArgs( |
||||
|
IInputDevice device, ulong timestamp, IInputRoot inputRoot, |
||||
|
RawPointerEventType type, RawPointerPoint point, |
||||
|
RawInputModifiers modifiers, uint pointerId) |
||||
|
{ |
||||
|
return device is TouchDevice |
||||
|
? new RawTouchEventArgs(device, timestamp, inputRoot, type, point, modifiers, pointerId) |
||||
|
: new RawPointerEventArgs(device, timestamp, inputRoot, type, point, modifiers) |
||||
|
{ |
||||
|
RawPointerId = pointerId |
||||
|
}; |
||||
|
} |
||||
|
|
||||
|
private ulong GetTimestamp(PointerRoutedEventArgs e) |
||||
|
{ |
||||
|
// WinUI PointerPoint.Timestamp is in microseconds; Avalonia expects milliseconds.
|
||||
|
return e.GetCurrentPoint(this).Timestamp / 1000; |
||||
|
} |
||||
|
|
||||
|
private RawInputModifiers GetPointerModifiers(PointerRoutedEventArgs e) |
||||
|
{ |
||||
|
var point = e.GetCurrentPoint(this); |
||||
|
var props = point.Properties; |
||||
|
var mods = WinUIKeyInterop.ModifiersFromVirtualKeyModifiers(e.KeyModifiers); |
||||
|
if (props.IsLeftButtonPressed) |
||||
|
mods |= RawInputModifiers.LeftMouseButton; |
||||
|
if (props.IsRightButtonPressed) |
||||
|
mods |= RawInputModifiers.RightMouseButton; |
||||
|
if (props.IsMiddleButtonPressed) |
||||
|
mods |= RawInputModifiers.MiddleMouseButton; |
||||
|
|
||||
|
if (e.Pointer.PointerDeviceType == Microsoft.UI.Input.PointerDeviceType.Pen) |
||||
|
{ |
||||
|
if (props.IsBarrelButtonPressed) |
||||
|
mods |= RawInputModifiers.PenBarrelButton; |
||||
|
if (props.IsEraser) |
||||
|
mods |= RawInputModifiers.PenEraser; |
||||
|
if (props.IsInverted) |
||||
|
mods |= RawInputModifiers.PenInverted; |
||||
|
} |
||||
|
|
||||
|
return mods; |
||||
|
} |
||||
|
|
||||
|
private void OnPointerPressed(object sender, PointerRoutedEventArgs e) |
||||
|
{ |
||||
|
if (_topLevelImpl?.Input is not { } input || _topLevelImpl.InputRoot is not { } inputRoot) |
||||
|
return; |
||||
|
|
||||
|
var device = GetPointerDevice(e); |
||||
|
var timestamp = GetTimestamp(e); |
||||
|
var rawPoint = CreateRawPointerPoint(e); |
||||
|
var modifiers = GetPointerModifiers(e); |
||||
|
var pointerId = e.Pointer.PointerId; |
||||
|
|
||||
|
RawPointerEventType type; |
||||
|
if (device is TouchDevice) |
||||
|
{ |
||||
|
type = RawPointerEventType.TouchBegin; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var point = e.GetCurrentPoint(this); |
||||
|
var props = point.Properties; |
||||
|
if (props.IsLeftButtonPressed) |
||||
|
type = RawPointerEventType.LeftButtonDown; |
||||
|
else if (props.IsRightButtonPressed) |
||||
|
type = RawPointerEventType.RightButtonDown; |
||||
|
else if (props.IsMiddleButtonPressed) |
||||
|
type = RawPointerEventType.MiddleButtonDown; |
||||
|
else |
||||
|
return; |
||||
|
} |
||||
|
|
||||
|
Focus(FocusState.Pointer); |
||||
|
CapturePointer(e.Pointer); |
||||
|
|
||||
|
input(CreatePointerArgs(device, timestamp, inputRoot, type, rawPoint, modifiers, pointerId)); |
||||
|
e.Handled = true; |
||||
|
} |
||||
|
|
||||
|
private void OnPointerMoved(object sender, PointerRoutedEventArgs e) |
||||
|
{ |
||||
|
if (_topLevelImpl?.Input is not { } input || _topLevelImpl.InputRoot is not { } inputRoot) |
||||
|
return; |
||||
|
|
||||
|
var device = GetPointerDevice(e); |
||||
|
var timestamp = GetTimestamp(e); |
||||
|
var rawPoint = CreateRawPointerPoint(e); |
||||
|
var modifiers = GetPointerModifiers(e); |
||||
|
var pointerId = e.Pointer.PointerId; |
||||
|
|
||||
|
var type = device is TouchDevice ? RawPointerEventType.TouchUpdate : RawPointerEventType.Move; |
||||
|
|
||||
|
input(CreatePointerArgs(device, timestamp, inputRoot, type, rawPoint, modifiers, pointerId)); |
||||
|
} |
||||
|
|
||||
|
private void OnPointerReleased(object sender, PointerRoutedEventArgs e) |
||||
|
{ |
||||
|
if (_topLevelImpl?.Input is not { } input || _topLevelImpl.InputRoot is not { } inputRoot) |
||||
|
return; |
||||
|
|
||||
|
var device = GetPointerDevice(e); |
||||
|
var timestamp = GetTimestamp(e); |
||||
|
var rawPoint = CreateRawPointerPoint(e); |
||||
|
var modifiers = GetPointerModifiers(e); |
||||
|
var pointerId = e.Pointer.PointerId; |
||||
|
|
||||
|
RawPointerEventType type; |
||||
|
if (device is TouchDevice) |
||||
|
{ |
||||
|
type = RawPointerEventType.TouchEnd; |
||||
|
} |
||||
|
else |
||||
|
{ |
||||
|
var point = e.GetCurrentPoint(this); |
||||
|
var props = point.Properties; |
||||
|
switch (props.PointerUpdateKind) |
||||
|
{ |
||||
|
case Microsoft.UI.Input.PointerUpdateKind.LeftButtonReleased: |
||||
|
type = RawPointerEventType.LeftButtonUp; |
||||
|
break; |
||||
|
case Microsoft.UI.Input.PointerUpdateKind.RightButtonReleased: |
||||
|
type = RawPointerEventType.RightButtonUp; |
||||
|
break; |
||||
|
case Microsoft.UI.Input.PointerUpdateKind.MiddleButtonReleased: |
||||
|
type = RawPointerEventType.MiddleButtonUp; |
||||
|
break; |
||||
|
default: |
||||
|
return; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
ReleasePointerCapture(e.Pointer); |
||||
|
input(CreatePointerArgs(device, timestamp, inputRoot, type, rawPoint, modifiers, pointerId)); |
||||
|
e.Handled = true; |
||||
|
} |
||||
|
|
||||
|
private void OnPointerWheelChanged(object sender, PointerRoutedEventArgs e) |
||||
|
{ |
||||
|
if (_topLevelImpl?.Input is not { } input || _topLevelImpl.InputRoot is not { } inputRoot) |
||||
|
return; |
||||
|
|
||||
|
var device = GetPointerDevice(e); |
||||
|
var timestamp = GetTimestamp(e); |
||||
|
var point = e.GetCurrentPoint(this); |
||||
|
var delta = point.Properties.MouseWheelDelta; |
||||
|
var pos = point.Position; |
||||
|
|
||||
|
input(new RawMouseWheelEventArgs(device as MouseDevice ?? _mouseDevice, timestamp, inputRoot, |
||||
|
new AvPoint(pos.X, pos.Y), new AvVector(0, delta), GetPointerModifiers(e))); |
||||
|
e.Handled = true; |
||||
|
} |
||||
|
|
||||
|
private void OnPointerCanceled(object sender, PointerRoutedEventArgs e) |
||||
|
{ |
||||
|
if (_topLevelImpl?.Input is not { } input || _topLevelImpl.InputRoot is not { } inputRoot) |
||||
|
return; |
||||
|
|
||||
|
var device = GetPointerDevice(e); |
||||
|
var timestamp = GetTimestamp(e); |
||||
|
var rawPoint = CreateRawPointerPoint(e); |
||||
|
var modifiers = GetPointerModifiers(e); |
||||
|
var pointerId = e.Pointer.PointerId; |
||||
|
|
||||
|
var type = device is TouchDevice ? RawPointerEventType.TouchCancel : RawPointerEventType.LeaveWindow; |
||||
|
|
||||
|
input(CreatePointerArgs(device, timestamp, inputRoot, type, rawPoint, modifiers, pointerId)); |
||||
|
e.Handled = true; |
||||
|
} |
||||
|
|
||||
|
private void OnKeyDown(object sender, KeyRoutedEventArgs e) |
||||
|
{ |
||||
|
if (_topLevelImpl?.Input is not { } input || _topLevelImpl.InputRoot is not { } inputRoot) |
||||
|
return; |
||||
|
|
||||
|
var key = WinUIKeyInterop.KeyFromVirtualKey(e.Key); |
||||
|
if (key != Key.None) |
||||
|
{ |
||||
|
var keyboard = GetKeyboardDevice(); |
||||
|
if (keyboard is null) return; |
||||
|
input(new RawKeyEventArgs(keyboard, (ulong)Environment.TickCount64, inputRoot, |
||||
|
RawKeyEventType.KeyDown, key, GetCurrentModifiers(), |
||||
|
PhysicalKey.None, null)); |
||||
|
e.Handled = true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void OnKeyUp(object sender, KeyRoutedEventArgs e) |
||||
|
{ |
||||
|
if (_topLevelImpl?.Input is not { } input || _topLevelImpl.InputRoot is not { } inputRoot) |
||||
|
return; |
||||
|
|
||||
|
var key = WinUIKeyInterop.KeyFromVirtualKey(e.Key); |
||||
|
if (key != Key.None) |
||||
|
{ |
||||
|
var keyboard = GetKeyboardDevice(); |
||||
|
if (keyboard is null) return; |
||||
|
input(new RawKeyEventArgs(keyboard, (ulong)Environment.TickCount64, inputRoot, |
||||
|
RawKeyEventType.KeyUp, key, GetCurrentModifiers(), |
||||
|
PhysicalKey.None, null)); |
||||
|
e.Handled = true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private void OnCharacterReceived(UIElement sender, CharacterReceivedRoutedEventArgs e) |
||||
|
{ |
||||
|
if (_topLevelImpl?.Input is not { } input || _topLevelImpl.InputRoot is not { } inputRoot) |
||||
|
return; |
||||
|
|
||||
|
var keyboard = GetKeyboardDevice(); |
||||
|
if (keyboard is null) return; |
||||
|
|
||||
|
var ch = e.Character; |
||||
|
if (!char.IsControl(ch) || ch == '\r' || ch == '\n' || ch == '\t') |
||||
|
{ |
||||
|
input(new RawTextInputEventArgs(keyboard, (ulong)Environment.TickCount64, inputRoot, |
||||
|
new string(ch, 1))); |
||||
|
e.Handled = true; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
private static IKeyboardDevice? GetKeyboardDevice() |
||||
|
=> AvaloniaLocator.Current.GetService<IKeyboardDevice>(); |
||||
|
|
||||
|
private static RawInputModifiers GetCurrentModifiers() |
||||
|
{ |
||||
|
var mods = RawInputModifiers.None; |
||||
|
var ctrlState = Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.Control); |
||||
|
if (ctrlState.HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down)) |
||||
|
mods |= RawInputModifiers.Control; |
||||
|
var shiftState = Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.Shift); |
||||
|
if (shiftState.HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down)) |
||||
|
mods |= RawInputModifiers.Shift; |
||||
|
var altState = Microsoft.UI.Input.InputKeyboardSource.GetKeyStateForCurrentThread(Windows.System.VirtualKey.Menu); |
||||
|
if (altState.HasFlag(Windows.UI.Core.CoreVirtualKeyStates.Down)) |
||||
|
mods |= RawInputModifiers.Alt; |
||||
|
return mods; |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,248 @@ |
|||||
|
using System; |
||||
|
using System.Runtime.InteropServices; |
||||
|
using global::Avalonia; |
||||
|
using global::Avalonia.OpenGL; |
||||
|
using global::Avalonia.OpenGL.Egl; |
||||
|
using global::Avalonia.OpenGL.Surfaces; |
||||
|
using global::Avalonia.Platform; |
||||
|
using global::Avalonia.Win32.DirectX; |
||||
|
using global::Avalonia.Win32.OpenGl.Angle; |
||||
|
using MicroCom.Runtime; |
||||
|
|
||||
|
namespace Avalonia.WinUI; |
||||
|
|
||||
|
internal unsafe class SwapChainGlSurface : EglGlPlatformSurfaceBase |
||||
|
{ |
||||
|
// QI for IDXGISwapChain2 fails on some Windows builds even though
|
||||
|
// IDXGISwapChain3/4 succeed. Use IDXGISwapChain3 which inherits from
|
||||
|
// IDXGISwapChain2 and has SetMatrixTransform at the same vtable slot.
|
||||
|
private static readonly Guid IDXGISwapChain3Guid = new("94d99bdb-f1f8-4ab0-b236-7da0170edab1"); |
||||
|
|
||||
|
[DllImport("dxgi.dll", ExactSpelling = true)] |
||||
|
private static extern int CreateDXGIFactory2(uint Flags, in Guid riid, out IntPtr ppFactory); |
||||
|
|
||||
|
private readonly Func<PixelSize> _getSizeFunc; |
||||
|
private readonly Func<double> _getScalingFunc; |
||||
|
private readonly Action<IntPtr> _setSwapChainCallback; |
||||
|
private IDXGISwapChain1? _swapChain; |
||||
|
private IntPtr _swapChain3Ptr; |
||||
|
|
||||
|
public SwapChainGlSurface( |
||||
|
Func<PixelSize> getSizeFunc, |
||||
|
Func<double> getScalingFunc, |
||||
|
Action<IntPtr> setSwapChainCallback) |
||||
|
{ |
||||
|
_getSizeFunc = getSizeFunc; |
||||
|
_getScalingFunc = getScalingFunc; |
||||
|
_setSwapChainCallback = setSwapChainCallback; |
||||
|
} |
||||
|
|
||||
|
public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget(IGlContext context) |
||||
|
{ |
||||
|
var eglContext = (EglContext)context; |
||||
|
|
||||
|
if (_swapChain is null) |
||||
|
{ |
||||
|
_swapChain = CreateSwapChain(eglContext); |
||||
|
|
||||
|
var swapChainPtr = MicroComRuntime.GetNativeIntPtr(_swapChain); |
||||
|
var guid = IDXGISwapChain3Guid; |
||||
|
var qiHr = Marshal.QueryInterface(swapChainPtr, in guid, out _swapChain3Ptr); |
||||
|
if (qiHr != 0 || _swapChain3Ptr == IntPtr.Zero) |
||||
|
throw new InvalidOperationException( |
||||
|
$"QI for IDXGISwapChain3 failed: HR=0x{qiHr:X8}, ptr={_swapChain3Ptr}"); |
||||
|
|
||||
|
SetInverseScaleTransform(_getScalingFunc()); |
||||
|
|
||||
|
_setSwapChainCallback(swapChainPtr); |
||||
|
} |
||||
|
|
||||
|
return new SwapChainGlRenderTarget(eglContext, _swapChain, _getSizeFunc, _getScalingFunc, _swapChain3Ptr, this); |
||||
|
} |
||||
|
|
||||
|
[StructLayout(LayoutKind.Sequential)] |
||||
|
internal struct DXGI_MATRIX_3X2_F |
||||
|
{ |
||||
|
public float _11, _12; |
||||
|
public float _21, _22; |
||||
|
public float _31, _32; |
||||
|
} |
||||
|
|
||||
|
internal void SetInverseScaleTransform(double scaling) |
||||
|
{ |
||||
|
if (_swapChain3Ptr == IntPtr.Zero || scaling <= 0) |
||||
|
return; |
||||
|
|
||||
|
var inverseScale = new DXGI_MATRIX_3X2_F |
||||
|
{ |
||||
|
_11 = 1.0f / (float)scaling, |
||||
|
_22 = 1.0f / (float)scaling |
||||
|
}; |
||||
|
|
||||
|
// IDXGISwapChain2::SetMatrixTransform vtable slot:
|
||||
|
// IUnknown(3) + IDXGIObject(4) + IDXGIDeviceSubObject(1) +
|
||||
|
// IDXGISwapChain(10) + IDXGISwapChain1(11) +
|
||||
|
// SetSourceSize, GetSourceSize, SetMaximumFrameLatency,
|
||||
|
// GetMaximumFrameLatency, GetFrameLatencyWaitableObject = 5
|
||||
|
// → SetMatrixTransform is at slot 34
|
||||
|
var vtable = *(IntPtr**)_swapChain3Ptr; |
||||
|
var setMatrixTransform = (delegate* unmanaged[Stdcall]<IntPtr, DXGI_MATRIX_3X2_F*, int>)vtable[34]; |
||||
|
var hr = setMatrixTransform(_swapChain3Ptr, &inverseScale); |
||||
|
Marshal.ThrowExceptionForHR(hr); |
||||
|
} |
||||
|
|
||||
|
private IDXGISwapChain1 CreateSwapChain(EglContext eglContext) |
||||
|
{ |
||||
|
var eglDisplay = (AngleWin32EglDisplay)eglContext.Display; |
||||
|
var d3dDevicePtr = eglDisplay.GetDirect3DDevice(); |
||||
|
var d3dDevice = MicroComRuntime.CreateProxyFor<IUnknown>(d3dDevicePtr, false); |
||||
|
|
||||
|
IDXGIDevice dxgiDevice; |
||||
|
using (d3dDevice) |
||||
|
dxgiDevice = d3dDevice.QueryInterface<IDXGIDevice>(); |
||||
|
|
||||
|
Guid factoryGuid = MicroComRuntime.GetGuidFor(typeof(IDXGIFactory2)); |
||||
|
var hr = CreateDXGIFactory2(0, in factoryGuid, out var factoryPtr); |
||||
|
Marshal.ThrowExceptionForHR(hr); |
||||
|
var dxgiFactory = MicroComRuntime.CreateProxyFor<IDXGIFactory2>(factoryPtr, true); |
||||
|
|
||||
|
var pixelSize = _getSizeFunc(); |
||||
|
var desc = new DXGI_SWAP_CHAIN_DESC1 |
||||
|
{ |
||||
|
Format = DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM, |
||||
|
SampleDesc = new DXGI_SAMPLE_DESC { Count = 1, Quality = 0 }, |
||||
|
BufferUsage = DxgiRenderTarget.DXGI_USAGE_RENDER_TARGET_OUTPUT, |
||||
|
BufferCount = 2, |
||||
|
SwapEffect = DXGI_SWAP_EFFECT.DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL, |
||||
|
AlphaMode = DXGI_ALPHA_MODE.DXGI_ALPHA_MODE_PREMULTIPLIED, |
||||
|
Width = (uint)pixelSize.Width, |
||||
|
Height = (uint)pixelSize.Height, |
||||
|
Flags = 0 |
||||
|
}; |
||||
|
|
||||
|
var swapChain = dxgiFactory.CreateSwapChainForComposition(dxgiDevice, &desc, null); |
||||
|
|
||||
|
dxgiFactory.Dispose(); |
||||
|
dxgiDevice.Dispose(); |
||||
|
|
||||
|
return swapChain; |
||||
|
} |
||||
|
|
||||
|
public void DisposeSwapChain() |
||||
|
{ |
||||
|
if (_swapChain3Ptr != IntPtr.Zero) |
||||
|
{ |
||||
|
Marshal.Release(_swapChain3Ptr); |
||||
|
_swapChain3Ptr = IntPtr.Zero; |
||||
|
} |
||||
|
_swapChain?.Dispose(); |
||||
|
_swapChain = null; |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
internal unsafe class SwapChainGlRenderTarget : EglPlatformSurfaceRenderTargetBase |
||||
|
{ |
||||
|
private static readonly Guid ID3D11Texture2DGuid = Guid.Parse("6F15AAF2-D208-4E89-9AB4-489535D34F9C"); |
||||
|
|
||||
|
private readonly IDXGISwapChain1 _swapChain; |
||||
|
private readonly Func<PixelSize> _getSizeFunc; |
||||
|
private readonly Func<double> _getScalingFunc; |
||||
|
private readonly IntPtr _swapChain3Ptr; |
||||
|
private readonly SwapChainGlSurface _owner; |
||||
|
|
||||
|
private IUnknown? _renderTexture; |
||||
|
private EglSurface? _surface; |
||||
|
private PixelSize _lastSize; |
||||
|
private double _lastScaling; |
||||
|
|
||||
|
public SwapChainGlRenderTarget( |
||||
|
EglContext context, |
||||
|
IDXGISwapChain1 swapChain, |
||||
|
Func<PixelSize> getSizeFunc, |
||||
|
Func<double> getScalingFunc, |
||||
|
IntPtr swapChain3Ptr, |
||||
|
SwapChainGlSurface owner) : base(context) |
||||
|
{ |
||||
|
_swapChain = swapChain; |
||||
|
_getSizeFunc = getSizeFunc; |
||||
|
_getScalingFunc = getScalingFunc; |
||||
|
_swapChain3Ptr = swapChain3Ptr; |
||||
|
_owner = owner; |
||||
|
} |
||||
|
|
||||
|
public override IGlPlatformSurfaceRenderingSession BeginDrawCore(IRenderTarget.RenderTargetSceneInfo sceneInfo) |
||||
|
{ |
||||
|
var contextLock = Context.EnsureCurrent(); |
||||
|
var success = false; |
||||
|
try |
||||
|
{ |
||||
|
var size = _getSizeFunc(); |
||||
|
var scaling = _getScalingFunc(); |
||||
|
|
||||
|
if (scaling != _lastScaling) |
||||
|
{ |
||||
|
_owner.SetInverseScaleTransform(scaling); |
||||
|
_lastScaling = scaling; |
||||
|
} |
||||
|
|
||||
|
if (size != _lastSize) |
||||
|
{ |
||||
|
_surface?.Dispose(); |
||||
|
_surface = null; |
||||
|
_renderTexture?.Dispose(); |
||||
|
_renderTexture = null; |
||||
|
|
||||
|
_swapChain.ResizeBuffers(2, |
||||
|
(ushort)size.Width, |
||||
|
(ushort)size.Height, |
||||
|
DXGI_FORMAT.DXGI_FORMAT_B8G8R8A8_UNORM, |
||||
|
0); |
||||
|
|
||||
|
_lastSize = size; |
||||
|
} |
||||
|
|
||||
|
if (_renderTexture is null) |
||||
|
{ |
||||
|
_surface?.Dispose(); |
||||
|
_surface = null; |
||||
|
|
||||
|
Guid textureGuid = ID3D11Texture2DGuid; |
||||
|
_renderTexture = MicroComRuntime.CreateProxyFor<IUnknown>( |
||||
|
_swapChain.GetBuffer(0, &textureGuid), true); |
||||
|
} |
||||
|
|
||||
|
if (_surface is null) |
||||
|
{ |
||||
|
_surface = ((AngleWin32EglDisplay)Context.Display).WrapDirect3D11Texture( |
||||
|
MicroComRuntime.GetNativeIntPtr(_renderTexture), |
||||
|
0, 0, size.Width, size.Height); |
||||
|
} |
||||
|
|
||||
|
var res = base.BeginDraw(_surface, size, scaling, () => |
||||
|
{ |
||||
|
_swapChain.Present(1, 0); |
||||
|
contextLock?.Dispose(); |
||||
|
}, true); |
||||
|
success = true; |
||||
|
return res; |
||||
|
} |
||||
|
finally |
||||
|
{ |
||||
|
if (!success) |
||||
|
{ |
||||
|
_surface?.Dispose(); |
||||
|
_surface = null; |
||||
|
_renderTexture?.Dispose(); |
||||
|
_renderTexture = null; |
||||
|
contextLock.Dispose(); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public override void Dispose() |
||||
|
{ |
||||
|
base.Dispose(); |
||||
|
_surface?.Dispose(); |
||||
|
_renderTexture?.Dispose(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,100 @@ |
|||||
|
using System; |
||||
|
using System.Collections.Generic; |
||||
|
using global::Avalonia; |
||||
|
using global::Avalonia.Controls; |
||||
|
using global::Avalonia.Input; |
||||
|
using global::Avalonia.Input.Raw; |
||||
|
using global::Avalonia.OpenGL.Surfaces; |
||||
|
using global::Avalonia.Platform; |
||||
|
using global::Avalonia.Platform.Surfaces; |
||||
|
using global::Avalonia.Rendering.Composition; |
||||
|
using Size = global::Avalonia.Size; |
||||
|
using Point = global::Avalonia.Point; |
||||
|
using Rect = global::Avalonia.Rect; |
||||
|
using PixelPoint = global::Avalonia.PixelPoint; |
||||
|
|
||||
|
namespace Avalonia.WinUI; |
||||
|
|
||||
|
internal class SwapChainPanelTopLevelImpl : ITopLevelImpl |
||||
|
{ |
||||
|
private readonly IGlPlatformSurface _glSurface; |
||||
|
private Size _clientSize; |
||||
|
private double _scaling = 1.0; |
||||
|
|
||||
|
public SwapChainPanelTopLevelImpl(IGlPlatformSurface glSurface) |
||||
|
{ |
||||
|
_glSurface = glSurface; |
||||
|
var platformGraphics = AvaloniaLocator.Current.GetService<IPlatformGraphics>(); |
||||
|
Compositor = new Compositor(platformGraphics); |
||||
|
} |
||||
|
|
||||
|
public Size ClientSize |
||||
|
{ |
||||
|
get => _clientSize; |
||||
|
set |
||||
|
{ |
||||
|
_clientSize = value; |
||||
|
Resized?.Invoke(value, WindowResizeReason.Unspecified); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public double RenderScaling |
||||
|
{ |
||||
|
get => _scaling; |
||||
|
set |
||||
|
{ |
||||
|
_scaling = value; |
||||
|
ScalingChanged?.Invoke(value); |
||||
|
} |
||||
|
} |
||||
|
|
||||
|
public double DesktopScaling => _scaling; |
||||
|
|
||||
|
public IPlatformHandle? Handle => null; |
||||
|
|
||||
|
public Compositor Compositor { get; } |
||||
|
|
||||
|
public IPlatformRenderSurface[] Surfaces => [_glSurface]; |
||||
|
|
||||
|
public Action<RawInputEventArgs>? Input { get; set; } |
||||
|
|
||||
|
public Action<Rect>? Paint { get; set; } |
||||
|
|
||||
|
public Action<Size, WindowResizeReason>? Resized { get; set; } |
||||
|
|
||||
|
public Action<double>? ScalingChanged { get; set; } |
||||
|
|
||||
|
public Action<WindowTransparencyLevel>? TransparencyLevelChanged { get; set; } |
||||
|
|
||||
|
public Action? Closed { get; set; } |
||||
|
|
||||
|
public Action? LostFocus { get; set; } |
||||
|
|
||||
|
public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None; |
||||
|
|
||||
|
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } = new(1, 1, 1); |
||||
|
|
||||
|
public IInputRoot? InputRoot { get; private set; } |
||||
|
|
||||
|
public void SetInputRoot(IInputRoot inputRoot) => InputRoot = inputRoot; |
||||
|
|
||||
|
public Point PointToClient(PixelPoint point) => point.ToPoint(_scaling); |
||||
|
|
||||
|
public PixelPoint PointToScreen(Point point) => PixelPoint.FromPoint(point, _scaling); |
||||
|
|
||||
|
public void SetCursor(ICursorImpl? cursor) { } |
||||
|
|
||||
|
// Uses overlays instead of popups.
|
||||
|
public IPopupImpl? CreatePopup() => null; |
||||
|
|
||||
|
public void SetTransparencyLevelHint(IReadOnlyList<WindowTransparencyLevel> transparencyLevels) { } |
||||
|
|
||||
|
public void SetFrameThemeVariant(PlatformThemeVariant themeVariant) { } |
||||
|
|
||||
|
public object? TryGetFeature(Type featureType) => null; |
||||
|
|
||||
|
public void Dispose() |
||||
|
{ |
||||
|
Closed?.Invoke(); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,31 @@ |
|||||
|
using global::Avalonia.Input; |
||||
|
using global::Avalonia.Input.Raw; |
||||
|
using global::Avalonia.Win32.Input; |
||||
|
using Windows.System; |
||||
|
|
||||
|
namespace Avalonia.WinUI; |
||||
|
|
||||
|
internal static class WinUIKeyInterop |
||||
|
{ |
||||
|
public static Key KeyFromVirtualKey(VirtualKey virtualKey) |
||||
|
{ |
||||
|
// WinUI VirtualKey enum values match Win32 VK_ constants.
|
||||
|
// Pass keyData=0; modifier disambiguation (L/R shift etc.) won't work
|
||||
|
// via scan codes, but WinUI doesn't distinguish those anyway.
|
||||
|
return KeyInterop.KeyFromVirtualKey((int)virtualKey, 0); |
||||
|
} |
||||
|
|
||||
|
public static RawInputModifiers ModifiersFromVirtualKeyModifiers(VirtualKeyModifiers modifiers) |
||||
|
{ |
||||
|
var result = RawInputModifiers.None; |
||||
|
if (modifiers.HasFlag(VirtualKeyModifiers.Control)) |
||||
|
result |= RawInputModifiers.Control; |
||||
|
if (modifiers.HasFlag(VirtualKeyModifiers.Shift)) |
||||
|
result |= RawInputModifiers.Shift; |
||||
|
if (modifiers.HasFlag(VirtualKeyModifiers.Menu)) |
||||
|
result |= RawInputModifiers.Alt; |
||||
|
if (modifiers.HasFlag(VirtualKeyModifiers.Windows)) |
||||
|
result |= RawInputModifiers.Meta; |
||||
|
return result; |
||||
|
} |
||||
|
} |
||||