Browse Source

Init 1

Init 2

Init 3

Init 4

Init 5

Add Windows SDK
dev/timill/winui-test
Timothy Miller 2 weeks ago
parent
commit
43e18db661
  1. 794
      Avalonia.sln
  2. 2
      Directory.Packages.props
  3. 1
      build/TargetFrameworks.props
  4. 16
      samples/ControlGallery.WinUI/App.xaml
  5. 31
      samples/ControlGallery.WinUI/App.xaml.cs
  6. BIN
      samples/ControlGallery.WinUI/Assets/LockScreenLogo.scale-200.png
  7. BIN
      samples/ControlGallery.WinUI/Assets/SplashScreen.scale-200.png
  8. BIN
      samples/ControlGallery.WinUI/Assets/Square150x150Logo.scale-200.png
  9. BIN
      samples/ControlGallery.WinUI/Assets/Square44x44Logo.scale-200.png
  10. BIN
      samples/ControlGallery.WinUI/Assets/Square44x44Logo.targetsize-24_altform-unplated.png
  11. BIN
      samples/ControlGallery.WinUI/Assets/StoreLogo.png
  12. BIN
      samples/ControlGallery.WinUI/Assets/Wide310x150Logo.scale-200.png
  13. 67
      samples/ControlGallery.WinUI/ControlGallery.WinUI.csproj
  14. 15
      samples/ControlGallery.WinUI/MainWindow.xaml
  15. 11
      samples/ControlGallery.WinUI/MainWindow.xaml.cs
  16. 53
      samples/ControlGallery.WinUI/Package.appxmanifest
  17. 10
      samples/ControlGallery.WinUI/Properties/launchSettings.json
  18. 19
      samples/ControlGallery.WinUI/app.manifest
  19. 2
      src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
  20. 29
      src/Windows/Avalonia.WinUI/Avalonia.WinUI.csproj
  21. 427
      src/Windows/Avalonia.WinUI/AvaloniaSwapChainPanel.cs
  22. 248
      src/Windows/Avalonia.WinUI/SwapChainGlSurface.cs
  23. 100
      src/Windows/Avalonia.WinUI/SwapChainPanelTopLevelImpl.cs
  24. 31
      src/Windows/Avalonia.WinUI/WinUIKeyInterop.cs

794
Avalonia.sln

File diff suppressed because it is too large

2
Directory.Packages.props

@ -30,6 +30,8 @@
<PackageVersion Include="Microsoft.Reactive.Testing" Version="6.1.0" />
<PackageVersion Include="Microsoft.Testing.Extensions.TrxReport" Version="2.0.2" />
<PackageVersion Include="Microsoft.Windows.CsWin32" Version="0.3.257" />
<PackageVersion Include="Microsoft.Windows.SDK.BuildTools" Version="10.0.26100.7705" />
<PackageVersion Include="Microsoft.WindowsAppSDK" Version="1.8.260209005" />
<PackageVersion Include="Mono.Cecil" Version="0.11.6" />
<PackageVersion Include="MonoMac.NetStandard" Version="0.0.4" />
<PackageVersion Include="Moq" Version="4.20.72" />

1
build/TargetFrameworks.props

@ -8,6 +8,7 @@
<AvsCurrentIOSTargetFramework>$(AvsCurrentTargetFramework)-ios26.0</AvsCurrentIOSTargetFramework>
<AvsCurrentTvOSTargetFramework>$(AvsCurrentTargetFramework)-tvos26.0</AvsCurrentTvOSTargetFramework>
<AvsCurrentBrowserTargetFramework>$(AvsCurrentTargetFramework)-browser</AvsCurrentBrowserTargetFramework>
<AvsCurrentWinUITargetFramework>$(AvsCurrentTargetFramework)-windows10.0.19041.0</AvsCurrentWinUITargetFramework>
</PropertyGroup>
<PropertyGroup Condition="'$(AvsSkipBuildingLegacyTargetFrameworks)' != 'True'">
<AvsLegacyTargetFrameworks>net8.0</AvsLegacyTargetFrameworks>

16
samples/ControlGallery.WinUI/App.xaml

@ -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>

31
samples/ControlGallery.WinUI/App.xaml.cs

@ -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();
}
}
}

BIN
samples/ControlGallery.WinUI/Assets/LockScreenLogo.scale-200.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 432 B

BIN
samples/ControlGallery.WinUI/Assets/SplashScreen.scale-200.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.2 KiB

BIN
samples/ControlGallery.WinUI/Assets/Square150x150Logo.scale-200.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

BIN
samples/ControlGallery.WinUI/Assets/Square44x44Logo.scale-200.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 637 B

BIN
samples/ControlGallery.WinUI/Assets/Square44x44Logo.targetsize-24_altform-unplated.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 283 B

BIN
samples/ControlGallery.WinUI/Assets/StoreLogo.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 456 B

BIN
samples/ControlGallery.WinUI/Assets/Wide310x150Logo.scale-200.png

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

67
samples/ControlGallery.WinUI/ControlGallery.WinUI.csproj

@ -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>

15
samples/ControlGallery.WinUI/MainWindow.xaml

@ -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>

11
samples/ControlGallery.WinUI/MainWindow.xaml.cs

@ -0,0 +1,11 @@
namespace ControlGallery.WinUI
{
public sealed partial class MainWindow : Microsoft.UI.Xaml.Window
{
public MainWindow()
{
InitializeComponent();
AvaloniaPanel.Content = new ControlCatalog.MainView();
}
}
}

53
samples/ControlGallery.WinUI/Package.appxmanifest

@ -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>

10
samples/ControlGallery.WinUI/Properties/launchSettings.json

@ -0,0 +1,10 @@
{
"profiles": {
"ControlGallery.WinUI (Package)": {
"commandName": "MsixPackage"
},
"ControlGallery.WinUI (Unpackaged)": {
"commandName": "Project"
}
}
}

19
samples/ControlGallery.WinUI/app.manifest

@ -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>

2
src/Windows/Avalonia.Win32/Avalonia.Win32.csproj

@ -36,6 +36,8 @@
</PropertyGroup>
<ItemGroup>
<InternalsVisibleTo Include="Avalonia.Win32.Interoperability, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="Avalonia.WinUI, PublicKey=$(AvaloniaPublicKey)" />
<InternalsVisibleTo Include="ControlGallery.WinUI, PublicKey=$(AvaloniaPublicKey)" />
</ItemGroup>
<ItemGroup>
<!-- By default, any projects supports Windows, Linux, MacOS platforms. -->

29
src/Windows/Avalonia.WinUI/Avalonia.WinUI.csproj

@ -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>

427
src/Windows/Avalonia.WinUI/AvaloniaSwapChainPanel.cs

@ -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;
}
}

248
src/Windows/Avalonia.WinUI/SwapChainGlSurface.cs

@ -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();
}
}

100
src/Windows/Avalonia.WinUI/SwapChainPanelTopLevelImpl.cs

@ -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();
}
}

31
src/Windows/Avalonia.WinUI/WinUIKeyInterop.cs

@ -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;
}
}
Loading…
Cancel
Save