diff --git a/.ncrunch/Avalonia.Web.Blazor.v3.ncrunchproject b/.ncrunch/Avalonia.Web.Blazor.v3.ncrunchproject
new file mode 100644
index 0000000000..319cd523ce
--- /dev/null
+++ b/.ncrunch/Avalonia.Web.Blazor.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ True
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Web.v3.ncrunchproject b/.ncrunch/Avalonia.Web.v3.ncrunchproject
new file mode 100644
index 0000000000..319cd523ce
--- /dev/null
+++ b/.ncrunch/Avalonia.Web.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ True
+
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Win32.net6.0.v3.ncrunchproject b/.ncrunch/Avalonia.Win32.net6.0.v3.ncrunchproject
index 28b692bb51..95a483b433 100644
--- a/.ncrunch/Avalonia.Win32.net6.0.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.Win32.net6.0.v3.ncrunchproject
@@ -1,7 +1,3 @@
-
-
- ..\..\tools\MicroComGenerator\bin\Debug\net6.0\**.*
-
-
+
\ No newline at end of file
diff --git a/.ncrunch/Avalonia.Win32.netstandard2.0.v3.ncrunchproject b/.ncrunch/Avalonia.Win32.netstandard2.0.v3.ncrunchproject
index 28b692bb51..95a483b433 100644
--- a/.ncrunch/Avalonia.Win32.netstandard2.0.v3.ncrunchproject
+++ b/.ncrunch/Avalonia.Win32.netstandard2.0.v3.ncrunchproject
@@ -1,7 +1,3 @@
-
-
- ..\..\tools\MicroComGenerator\bin\Debug\net6.0\**.*
-
-
+
\ No newline at end of file
diff --git a/.ncrunch/ControlCatalog.Blazor.Web.v3.ncrunchproject b/.ncrunch/ControlCatalog.Blazor.Web.v3.ncrunchproject
new file mode 100644
index 0000000000..319cd523ce
--- /dev/null
+++ b/.ncrunch/ControlCatalog.Blazor.Web.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ True
+
+
\ No newline at end of file
diff --git a/.ncrunch/MobileSandbox.Android.v3.ncrunchproject b/.ncrunch/MobileSandbox.Android.v3.ncrunchproject
new file mode 100644
index 0000000000..319cd523ce
--- /dev/null
+++ b/.ncrunch/MobileSandbox.Android.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ True
+
+
\ No newline at end of file
diff --git a/.ncrunch/MobileSandbox.Desktop.v3.ncrunchproject b/.ncrunch/MobileSandbox.Desktop.v3.ncrunchproject
new file mode 100644
index 0000000000..319cd523ce
--- /dev/null
+++ b/.ncrunch/MobileSandbox.Desktop.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ True
+
+
\ No newline at end of file
diff --git a/.ncrunch/MobileSandbox.iOS.v3.ncrunchproject b/.ncrunch/MobileSandbox.iOS.v3.ncrunchproject
new file mode 100644
index 0000000000..319cd523ce
--- /dev/null
+++ b/.ncrunch/MobileSandbox.iOS.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ True
+
+
\ No newline at end of file
diff --git a/.ncrunch/MobileSandbox.net6.0.v3.ncrunchproject b/.ncrunch/MobileSandbox.net6.0.v3.ncrunchproject
new file mode 100644
index 0000000000..319cd523ce
--- /dev/null
+++ b/.ncrunch/MobileSandbox.net6.0.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ True
+
+
\ No newline at end of file
diff --git a/.ncrunch/MobileSandbox.netstandard2.0.v3.ncrunchproject b/.ncrunch/MobileSandbox.netstandard2.0.v3.ncrunchproject
new file mode 100644
index 0000000000..319cd523ce
--- /dev/null
+++ b/.ncrunch/MobileSandbox.netstandard2.0.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ True
+
+
\ No newline at end of file
diff --git a/.ncrunch/_build.v3.ncrunchproject b/.ncrunch/_build.v3.ncrunchproject
new file mode 100644
index 0000000000..319cd523ce
--- /dev/null
+++ b/.ncrunch/_build.v3.ncrunchproject
@@ -0,0 +1,5 @@
+
+
+ True
+
+
\ No newline at end of file
diff --git a/Avalonia.Desktop.slnf b/Avalonia.Desktop.slnf
new file mode 100644
index 0000000000..6ba05332be
--- /dev/null
+++ b/Avalonia.Desktop.slnf
@@ -0,0 +1,60 @@
+{
+ "solution": {
+ "path": "Avalonia.sln",
+ "projects": [
+ "packages\\Avalonia\\Avalonia.csproj",
+ "samples\\ControlCatalog.NetCore\\ControlCatalog.NetCore.csproj",
+ "samples\\ControlCatalog\\ControlCatalog.csproj",
+ "samples\\IntegrationTestApp\\IntegrationTestApp.csproj",
+ "samples\\MiniMvvm\\MiniMvvm.csproj",
+ "samples\\SampleControls\\ControlSamples.csproj",
+ "samples\\Sandbox\\Sandbox.csproj",
+ "src\\Avalonia.Base\\Avalonia.Base.csproj",
+ "src\\Avalonia.Build.Tasks\\Avalonia.Build.Tasks.csproj",
+ "src\\Avalonia.Controls.ColorPicker\\Avalonia.Controls.ColorPicker.csproj",
+ "src\\Avalonia.Controls.DataGrid\\Avalonia.Controls.DataGrid.csproj",
+ "src\\Avalonia.Controls\\Avalonia.Controls.csproj",
+ "src\\Avalonia.DesignerSupport\\Avalonia.DesignerSupport.csproj",
+ "src\\Avalonia.Desktop\\Avalonia.Desktop.csproj",
+ "src\\Avalonia.Diagnostics\\Avalonia.Diagnostics.csproj",
+ "src\\Avalonia.Dialogs\\Avalonia.Dialogs.csproj",
+ "src\\Avalonia.FreeDesktop\\Avalonia.FreeDesktop.csproj",
+ "src\\Avalonia.Headless.Vnc\\Avalonia.Headless.Vnc.csproj",
+ "src\\Avalonia.Headless\\Avalonia.Headless.csproj",
+ "src\\Avalonia.MicroCom\\Avalonia.MicroCom.csproj",
+ "src\\Avalonia.Native\\Avalonia.Native.csproj",
+ "src\\Avalonia.OpenGL\\Avalonia.OpenGL.csproj",
+ "src\\Avalonia.ReactiveUI\\Avalonia.ReactiveUI.csproj",
+ "src\\Avalonia.Remote.Protocol\\Avalonia.Remote.Protocol.csproj",
+ "src\\Avalonia.Themes.Fluent\\Avalonia.Themes.Fluent.csproj",
+ "src\\Avalonia.Themes.Simple\\Avalonia.Themes.Simple.csproj",
+ "src\\Avalonia.X11\\Avalonia.X11.csproj",
+ "src\\Linux\\Avalonia.LinuxFramebuffer\\Avalonia.LinuxFramebuffer.csproj",
+ "src\\Markup\\Avalonia.Markup.Xaml.Loader\\Avalonia.Markup.Xaml.Loader.csproj",
+ "src\\Markup\\Avalonia.Markup.Xaml\\Avalonia.Markup.Xaml.csproj",
+ "src\\Markup\\Avalonia.Markup\\Avalonia.Markup.csproj",
+ "src\\Skia\\Avalonia.Skia\\Avalonia.Skia.csproj",
+ "src\\Windows\\Avalonia.Direct2D1\\Avalonia.Direct2D1.csproj",
+ "src\\Windows\\Avalonia.Win32.Interop\\Avalonia.Win32.Interop.csproj",
+ "src\\Windows\\Avalonia.Win32\\Avalonia.Win32.csproj",
+ "src\\tools\\DevAnalyzers\\DevAnalyzers.csproj",
+ "src\\tools\\DevGenerators\\DevGenerators.csproj",
+ "tests\\Avalonia.Base.UnitTests\\Avalonia.Base.UnitTests.csproj",
+ "tests\\Avalonia.Benchmarks\\Avalonia.Benchmarks.csproj",
+ "tests\\Avalonia.Controls.DataGrid.UnitTests\\Avalonia.Controls.DataGrid.UnitTests.csproj",
+ "tests\\Avalonia.Controls.UnitTests\\Avalonia.Controls.UnitTests.csproj",
+ "tests\\Avalonia.DesignerSupport.TestApp\\Avalonia.DesignerSupport.TestApp.csproj",
+ "tests\\Avalonia.DesignerSupport.Tests\\Avalonia.DesignerSupport.Tests.csproj",
+ "tests\\Avalonia.Direct2D1.RenderTests\\Avalonia.Direct2D1.RenderTests.csproj",
+ "tests\\Avalonia.Direct2D1.UnitTests\\Avalonia.Direct2D1.UnitTests.csproj",
+ "tests\\Avalonia.IntegrationTests.Appium\\Avalonia.IntegrationTests.Appium.csproj",
+ "tests\\Avalonia.LeakTests\\Avalonia.LeakTests.csproj",
+ "tests\\Avalonia.Markup.UnitTests\\Avalonia.Markup.UnitTests.csproj",
+ "tests\\Avalonia.Markup.Xaml.UnitTests\\Avalonia.Markup.Xaml.UnitTests.csproj",
+ "tests\\Avalonia.ReactiveUI.UnitTests\\Avalonia.ReactiveUI.UnitTests.csproj",
+ "tests\\Avalonia.Skia.RenderTests\\Avalonia.Skia.RenderTests.csproj",
+ "tests\\Avalonia.Skia.UnitTests\\Avalonia.Skia.UnitTests.csproj",
+ "tests\\Avalonia.UnitTests\\Avalonia.UnitTests.csproj"
+ ]
+ }
+}
\ No newline at end of file
diff --git a/Avalonia.sln b/Avalonia.sln
index c000f56d09..e2f04ddc35 100644
--- a/Avalonia.sln
+++ b/Avalonia.sln
@@ -41,6 +41,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Shared", "Shared", "{A689DE
src\Shared\IsExternalInit.cs = src\Shared\IsExternalInit.cs
src\Shared\ModuleInitializer.cs = src\Shared\ModuleInitializer.cs
src\Shared\SourceGeneratorAttributes.cs = src\Shared\SourceGeneratorAttributes.cs
+ src\Avalonia.Base\Compatibility\StringCompatibilityExtensions.cs = src\Avalonia.Base\Compatibility\StringCompatibilityExtensions.cs
EndProjectSection
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.ReactiveUI", "src\Avalonia.ReactiveUI\Avalonia.ReactiveUI.csproj", "{6417B24E-49C2-4985-8DB2-3AB9D898EC91}"
@@ -521,6 +522,7 @@ Global
{3B8519C1-2F51-4F12-A348-120AB91D4532}.Release|Any CPU.Build.0 = Release|Any CPU
{C90FE60B-B01E-4F35-91D6-379D6966030F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C90FE60B-B01E-4F35-91D6-379D6966030F}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C90FE60B-B01E-4F35-91D6-379D6966030F}.Debug|Any CPU.Deploy.0 = Debug|Any CPU
{C90FE60B-B01E-4F35-91D6-379D6966030F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C90FE60B-B01E-4F35-91D6-379D6966030F}.Release|Any CPU.Build.0 = Release|Any CPU
{FED9A71D-00D7-4F40-A9E4-1229EEA28EEB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
diff --git a/Directory.Build.targets b/Directory.Build.targets
new file mode 100644
index 0000000000..73954c7f4d
--- /dev/null
+++ b/Directory.Build.targets
@@ -0,0 +1,5 @@
+
+
+ $(DefineConstants);NET7SDK
+
+
diff --git a/Documentation/build.md b/Documentation/build.md
index ddd38be887..fd6b26337c 100644
--- a/Documentation/build.md
+++ b/Documentation/build.md
@@ -1,8 +1,8 @@
# Windows
-Avalonia requires at least Visual Studio 2022 and dotnet 6 SDK 6.0.100 to build on all platforms.
+Avalonia requires at least Visual Studio 2022 and dotnet 7-rc2 SDK 7.0.100-rc.2 to build on all platforms.
-### Clone the Avalonia repository
+## Clone the Avalonia repository
```
git clone https://github.com/AvaloniaUI/Avalonia.git
@@ -10,15 +10,30 @@ cd Avalonia
git submodule update --init
```
-### Install the required version of the .NET Core SDK
+## Install the required version of the .NET Core SDK
Go to https://dotnet.microsoft.com/download/visual-studio-sdks and install the latest version of the .NET Core SDK compatible with Avalonia UI. Make sure to download the SDK (not just the "runtime") package. The version compatible is indicated within the [global.json](https://github.com/AvaloniaUI/Avalonia/blob/master/global.json) file. Note that Avalonia UI does not always use the latest version and is hardcoded to use the last version known to be compatible (SDK releases may break the builds from time-to-time).
-### Open in Visual Studio
+## Build and Run Avalonia
-Open the `Avalonia.sln` solution in Visual Studio 2022 or newer. The free Visual Studio Community edition works fine. Build and run the `Samples\ControlCatalog.Desktop` or `ControlCatalog.NetCore` project to see the sample application.
+```
+cd samples\ControlCatalog.NetCore
+dotnet restore
+dotnet run
+```
+
+## Opening in Visual Studio
-### Troubleshooting
+If you want to open Avalonia in Visual Studio you have two options:
+
+- Avalonia.sln: This contains the whole of Avalonia in including desktop, mobile and web. You must have a number of dotnet workloads installed in order to build everything in this solution
+- Avalonia.Desktop.slnf: This solution filter opens only the parts of Avalonia required to run on desktop. This requires no extra workloads to be installed.
+
+Avalonia requires Visual Studio 2022 or newer. The free Visual Studio Community edition works fine.
+
+Build and run `ControlCatalog.NetCore` project to see the sample application.
+
+### Visual Studio Troubleshooting
* **Error CS0006: Avalonia.DesktopRuntime.dll could not be found**
@@ -35,16 +50,15 @@ It's *not* possible to build the *whole* project on Linux/macOS. You can only bu
MonoDevelop, Xamarin Studio and Visual Studio for Mac aren't capable of properly opening our solution. You can use Rider (at least 2017.2 EAP) or VSCode instead. They will fail to load most of platform specific projects, but you don't need them to run on .NET Core.
-### Install the latest version of the .NET Core SDK
+## Install the latest version of the .NET Core SDK
Go to https://www.microsoft.com/net/core and follow the instructions for your OS. Make sure to download the SDK (not just the "runtime") package.
-### Additional requirements for macOS
+## Additional requirements for macOS
The build process needs [Xcode](https://developer.apple.com/xcode/) to build the native library. Following the install instructions at the [Xcode](https://developer.apple.com/xcode/) website to properly install.
-
-### Clone the Avalonia repository
+## Clone the Avalonia repository
```
git clone https://github.com/AvaloniaUI/Avalonia.git
@@ -52,7 +66,7 @@ cd Avalonia
git submodule update --init --recursive
```
-### Build native libraries (macOS only)
+## Build native libraries (macOS only)
On macOS it is necessary to build and manually install the respective native libraries using [Xcode](https://developer.apple.com/xcode/). Execute the build script in the root project with the `CompileNative` task. It will build the headers, build the libraries, and place them in the appropriate place to allow .NET to find them at compilation and run time.
@@ -60,7 +74,7 @@ On macOS it is necessary to build and manually install the respective native lib
./build.sh CompileNative
```
-### Build and Run Avalonia
+## Build and Run Avalonia
```
cd samples/ControlCatalog.NetCore
diff --git a/azure-pipelines-integrationtests.yml b/azure-pipelines-integrationtests.yml
index 4fba4ca36f..43253ac6be 100644
--- a/azure-pipelines-integrationtests.yml
+++ b/azure-pipelines-integrationtests.yml
@@ -18,9 +18,9 @@ jobs:
version: 6.0.401
- task: UseDotNet@2
- displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23'
+ displayName: 'Use .NET Core SDK 7.0.100'
inputs:
- version: 7.0.100-rc.2.22477.23
+ version: 7.0.100
- script: system_profiler SPDisplaysDataType |grep Resolution
@@ -32,7 +32,7 @@ jobs:
rm -rf $(osascript -e "POSIX path of (path to application id \"net.avaloniaui.avalonia.integrationtestapp\")")
pkill IntegrationTestApp
./samples/IntegrationTestApp/bundle.sh
- open -n ./samples/IntegrationTestApp/bin/Debug/net6.0/osx-arm64/publish/IntegrationTestApp.app
+ open -n ./samples/IntegrationTestApp/bin/Debug/net7.0/osx-arm64/publish/IntegrationTestApp.app
pkill IntegrationTestApp
- task: DotNetCoreCLI@2
@@ -56,9 +56,9 @@ jobs:
version: 6.0.401
- task: UseDotNet@2
- displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23'
+ displayName: 'Use .NET Core SDK 7.0.100'
inputs:
- version: 7.0.100-rc.2.22477.23
+ version: 7.0.100
- task: Windows Application Driver@0
inputs:
diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index 903f9e3843..a3bbc33418 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -35,9 +35,9 @@ jobs:
version: 6.0.401
- task: UseDotNet@2
- displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23'
+ displayName: 'Use .NET Core SDK 7.0'
inputs:
- version: 7.0.100-rc.2.22477.23
+ version: 7.0.100
- task: CmdLine@2
displayName: 'Install Workloads'
@@ -72,9 +72,9 @@ jobs:
version: 6.0.401
- task: UseDotNet@2
- displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23'
+ displayName: 'Use .NET Core SDK 7.0.100'
inputs:
- version: 7.0.100-rc.2.22477.23
+ version: 7.0.100
- task: CmdLine@2
displayName: 'Install Workloads'
@@ -143,9 +143,9 @@ jobs:
version: 6.0.401
- task: UseDotNet@2
- displayName: 'Use .NET Core SDK 7.0.100-rc.2.22477.23'
+ displayName: 'Use .NET Core SDK 7.0.100'
inputs:
- version: 7.0.100-rc.2.22477.23
+ version: 7.0.100
- task: CmdLine@2
displayName: 'Install Workloads'
diff --git a/build/BuildTargets.targets b/build/BuildTargets.targets
index a5543cd050..481dbf06b2 100644
--- a/build/BuildTargets.targets
+++ b/build/BuildTargets.targets
@@ -3,6 +3,7 @@
$(MSBuildThisFileDirectory)\..\src\Avalonia.Build.Tasks\bin\$(Configuration)\netstandard2.0\Avalonia.Build.Tasks.dll
true
true
+ true
diff --git a/build/SharedVersion.props b/build/SharedVersion.props
index 1b60bb4df9..5838519596 100644
--- a/build/SharedVersion.props
+++ b/build/SharedVersion.props
@@ -8,7 +8,7 @@
https://github.com/AvaloniaUI/Avalonia/
true
CS1591
- latest
+ preview
MIT
Icon.png
Avalonia is a cross-platform UI framework for .NET providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS and with experimental support for Android, iOS and WebAssembly.
diff --git a/global.json b/global.json
index dc6da556b3..a9318b212f 100644
--- a/global.json
+++ b/global.json
@@ -1,6 +1,6 @@
{
"sdk": {
- "version": "7.0.100-rc.2.22477.23",
+ "version": "7.0.100",
"rollForward": "latestFeature"
},
"msbuild-sdks": {
diff --git a/native/Avalonia.Native/src/OSX/AvnWindow.mm b/native/Avalonia.Native/src/OSX/AvnWindow.mm
index ebd9f39d30..4c7341f834 100644
--- a/native/Avalonia.Native/src/OSX/AvnWindow.mm
+++ b/native/Avalonia.Native/src/OSX/AvnWindow.mm
@@ -385,7 +385,7 @@
return true;
}
--(void)resignKeyWindow
+-(void)windowDidResignKey:(NSNotification *)notification
{
if(_parent)
_parent->BaseEvents->Deactivated();
@@ -393,8 +393,6 @@
[self showAppMenuOnly];
[self invalidateShadow];
-
- [super resignKeyWindow];
}
- (void)windowDidMove:(NSNotification *_Nonnull)notification
diff --git a/native/Avalonia.Native/src/OSX/Screens.mm b/native/Avalonia.Native/src/OSX/Screens.mm
index b9c75ed742..83ab1bfd01 100644
--- a/native/Avalonia.Native/src/OSX/Screens.mm
+++ b/native/Avalonia.Native/src/OSX/Screens.mm
@@ -41,9 +41,9 @@ public:
ret->WorkingArea.X = [screen visibleFrame].origin.x;
ret->WorkingArea.Y = ConvertPointY(ToAvnPoint([screen visibleFrame].origin)).Y - ret->WorkingArea.Height;
- ret->PixelDensity = [screen backingScaleFactor];
+ ret->Scaling = [screen backingScaleFactor];
- ret->Primary = index == 0;
+ ret->IsPrimary = index == 0;
return S_OK;
}
diff --git a/native/Avalonia.Native/src/OSX/WindowImpl.mm b/native/Avalonia.Native/src/OSX/WindowImpl.mm
index ddc50c26b6..2443965957 100644
--- a/native/Avalonia.Native/src/OSX/WindowImpl.mm
+++ b/native/Avalonia.Native/src/OSX/WindowImpl.mm
@@ -63,7 +63,7 @@ HRESULT WindowImpl::Show(bool activate, bool isDialog) {
START_COM_CALL;
@autoreleasepool {
- _isDialog = isDialog;
+ _isDialog = isDialog || _parent != nullptr;
WindowBaseImpl::Show(activate, isDialog);
@@ -96,6 +96,8 @@ HRESULT WindowImpl::SetParent(IAvnWindow *parent) {
auto cparent = dynamic_cast(parent);
_parent = cparent;
+
+ _isDialog = _parent != nullptr;
if(_parent != nullptr && Window != nullptr){
// If one tries to show a child window with a minimized parent window, then the parent window will be
diff --git a/native/Avalonia.Native/src/OSX/app.mm b/native/Avalonia.Native/src/OSX/app.mm
index a15d0c9601..9cc9fc9523 100644
--- a/native/Avalonia.Native/src/OSX/app.mm
+++ b/native/Avalonia.Native/src/OSX/app.mm
@@ -95,11 +95,14 @@ ComPtr _events;
}
@end
-extern void InitializeAvnApp(IAvnApplicationEvents* events)
+extern void InitializeAvnApp(IAvnApplicationEvents* events, bool disableAppDelegate)
{
- NSApplication* app = [AvnApplication sharedApplication];
- id delegate = [[AvnAppDelegate alloc] initWithEvents:events];
- [app setDelegate:delegate];
+ if(!disableAppDelegate)
+ {
+ NSApplication* app = [AvnApplication sharedApplication];
+ id delegate = [[AvnAppDelegate alloc] initWithEvents:events];
+ [app setDelegate:delegate];
+ }
}
HRESULT AvnApplicationCommands::HideApp()
diff --git a/native/Avalonia.Native/src/OSX/common.h b/native/Avalonia.Native/src/OSX/common.h
index a90a235b9d..7ee7205776 100644
--- a/native/Avalonia.Native/src/OSX/common.h
+++ b/native/Avalonia.Native/src/OSX/common.h
@@ -32,7 +32,7 @@ extern void SetServicesMenu (IAvnMenu* menu);
extern IAvnMenu* GetAppMenu ();
extern NSMenuItem* GetAppMenuItem ();
-extern void InitializeAvnApp(IAvnApplicationEvents* events);
+extern void InitializeAvnApp(IAvnApplicationEvents* events, bool disableAppDelegate);
extern NSApplicationActivationPolicy AvnDesiredActivationPolicy;
extern NSPoint ToNSPoint (AvnPoint p);
extern NSRect ToNSRect (AvnRect r);
diff --git a/native/Avalonia.Native/src/OSX/main.mm b/native/Avalonia.Native/src/OSX/main.mm
index 6ee86b21ae..c29d4108d0 100644
--- a/native/Avalonia.Native/src/OSX/main.mm
+++ b/native/Avalonia.Native/src/OSX/main.mm
@@ -3,6 +3,8 @@
#include "common.h"
static NSString* s_appTitle = @"Avalonia";
+static int disableSetProcessName = 0;
+static bool disableAppDelegate = false;
// Copyright (c) 2011 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
@@ -111,11 +113,17 @@ public:
@autoreleasepool
{
auto appTitle = [NSString stringWithUTF8String: utf8String];
-
- [[NSProcessInfo processInfo] setProcessName:appTitle];
-
-
- SetProcessName(appTitle);
+ if (disableSetProcessName == 0)
+ {
+ [[NSProcessInfo processInfo] setProcessName:appTitle];
+
+ SetProcessName(appTitle);
+ }
+ if (disableSetProcessName == 1)
+ {
+ auto rootMenu = [NSApp mainMenu];
+ [rootMenu setTitle:appTitle];
+ }
return S_OK;
}
@@ -133,6 +141,27 @@ public:
}
}
+ virtual HRESULT SetDisableSetProcessName(int disable) override
+ {
+ START_COM_CALL;
+
+ @autoreleasepool
+ {
+ disableSetProcessName = disable;
+ return S_OK;
+ }
+ }
+
+ virtual HRESULT SetDisableAppDelegate(int disable) override
+ {
+ START_COM_CALL;
+
+ @autoreleasepool {
+ disableAppDelegate = disable;
+ return S_OK;
+ }
+ }
+
};
/// See "Using POSIX Threads in a Cocoa Application" section here:
@@ -175,7 +204,7 @@ public:
@autoreleasepool{
[[ThreadingInitializer new] do];
}
- InitializeAvnApp(events);
+ InitializeAvnApp(events, disableAppDelegate);
return S_OK;
};
diff --git a/native/Avalonia.Native/src/OSX/menu.h b/native/Avalonia.Native/src/OSX/menu.h
index ce46ac11e0..405938318c 100644
--- a/native/Avalonia.Native/src/OSX/menu.h
+++ b/native/Avalonia.Native/src/OSX/menu.h
@@ -59,11 +59,20 @@ public:
void RaiseOnClicked();
};
+class AvnAppMenu;
+
+@interface AvnMenuDelegate : NSObject
+- (id) initWithParent: (AvnAppMenu*) parent;
+- (void) parentDestroyed;
+@end
+
+
class AvnAppMenu : public ComSingleObject
{
private:
AvnMenu* _native;
ComPtr _baseEvents;
+ AvnMenuDelegate* _delegate;
public:
FORWARD_IUNKNOWN()
@@ -83,12 +92,10 @@ public:
virtual HRESULT SetTitle (char* utf8String) override;
virtual HRESULT Clear () override;
+ virtual ~AvnAppMenu() override;
};
-@interface AvnMenuDelegate : NSObject
-- (id) initWithParent: (AvnAppMenu*) parent;
-@end
#endif
diff --git a/native/Avalonia.Native/src/OSX/menu.mm b/native/Avalonia.Native/src/OSX/menu.mm
index b05588a441..cd1871de21 100644
--- a/native/Avalonia.Native/src/OSX/menu.mm
+++ b/native/Avalonia.Native/src/OSX/menu.mm
@@ -292,8 +292,13 @@ void AvnAppMenuItem::RaiseOnClicked()
AvnAppMenu::AvnAppMenu(IAvnMenuEvents* events)
{
_baseEvents = events;
- id del = [[AvnMenuDelegate alloc] initWithParent: this];
- _native = [[AvnMenu alloc] initWithDelegate: del];
+ _delegate = [[AvnMenuDelegate alloc] initWithParent: this];
+ _native = [[AvnMenu alloc] initWithDelegate: _delegate];
+}
+
+AvnAppMenu::~AvnAppMenu()
+{
+ [_delegate parentDestroyed];
}
@@ -394,7 +399,7 @@ HRESULT AvnAppMenu::Clear()
@implementation AvnMenuDelegate
{
- ComPtr _parent;
+ AvnAppMenu* _parent;
}
- (id) initWithParent:(AvnAppMenu *)parent
{
@@ -402,6 +407,12 @@ HRESULT AvnAppMenu::Clear()
_parent = parent;
return self;
}
+
+- (void) parentDestroyed
+{
+ _parent = nullptr;
+}
+
- (BOOL)menu:(NSMenu *)menu updateItem:(NSMenuItem *)item atIndex:(NSInteger)index shouldCancel:(BOOL)shouldCancel
{
if(shouldCancel)
@@ -416,17 +427,20 @@ HRESULT AvnAppMenu::Clear()
- (void)menuNeedsUpdate:(NSMenu *)menu
{
- _parent->RaiseNeedsUpdate();
+ if(_parent)
+ _parent->RaiseNeedsUpdate();
}
- (void)menuWillOpen:(NSMenu *)menu
{
- _parent->RaiseOpening();
+ if(_parent)
+ _parent->RaiseOpening();
}
- (void)menuDidClose:(NSMenu *)menu
{
- _parent->RaiseClosed();
+ if(_parent)
+ _parent->RaiseClosed();
}
@end
diff --git a/native/Avalonia.Native/src/OSX/rendertarget.mm b/native/Avalonia.Native/src/OSX/rendertarget.mm
index 2075cc85ab..1c22c91207 100644
--- a/native/Avalonia.Native/src/OSX/rendertarget.mm
+++ b/native/Avalonia.Native/src/OSX/rendertarget.mm
@@ -183,8 +183,11 @@ static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* ta
[_layer setContents: (__bridge IOSurface*) surface->surface];
}
[CATransaction commit];
- [CATransaction flush];
}
+ // This can trigger event processing on the main thread
+ // which might need to lock the renderer
+ // which can cause a deadlock. So flush call is outside of the lock
+ [CATransaction flush];
}
else
dispatch_async(dispatch_get_main_queue(), ^{
diff --git a/nukebuild/Build.cs b/nukebuild/Build.cs
index 7425c344c3..2295c0beda 100644
--- a/nukebuild/Build.cs
+++ b/nukebuild/Build.cs
@@ -145,8 +145,25 @@ partial class Build : NukeBuild
{
Information($"Running tests from {projectName}");
var project = Solution.GetProject(projectName).NotNull("project != null");
+ // Nuke and MSBuild tools have build-in helpers to get target frameworks from the project.
+ // Unfortunately, it gets broken with every second SDK update, so we had to do it manually.
+ var fileXml = XDocument.Parse(File.ReadAllText(project.Path));
+ var targetFrameworks = fileXml.Descendants("TargetFrameworks")
+ .FirstOrDefault()?.Value.Split(';').Select(f => f.Trim());
+ if (targetFrameworks is null)
+ {
+ var targetFramework = fileXml.Descendants("TargetFramework").FirstOrDefault()?.Value;
+ if (targetFramework is not null)
+ {
+ targetFrameworks = new[] { targetFramework };
+ }
+ }
+ if (targetFrameworks is null)
+ {
+ throw new InvalidOperationException("No target frameworks were found in the test project");
+ }
- foreach (var fw in project.GetTargetFrameworks())
+ foreach (var fw in targetFrameworks)
{
if (fw.StartsWith("net4")
&& RuntimeInformation.IsOSPlatform(OSPlatform.Linux)
diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj
index 8c0d824298..92d9732e91 100644
--- a/nukebuild/_build.csproj
+++ b/nukebuild/_build.csproj
@@ -13,11 +13,11 @@
-
+
-
-
+
+
all
runtime; build; native; contentfiles; analyzers; buildtransitive
diff --git a/packages/Avalonia/AvaloniaBuildTasks.targets b/packages/Avalonia/AvaloniaBuildTasks.targets
index 4b9e33ffe8..01bf303a20 100644
--- a/packages/Avalonia/AvaloniaBuildTasks.targets
+++ b/packages/Avalonia/AvaloniaBuildTasks.targets
@@ -4,6 +4,7 @@
<_AvaloniaUseExternalMSBuild Condition="'$(_AvaloniaForceInternalMSBuild)' == 'true'">false
low
<_AvaloniaSkipXamlCompilation Condition="'$(_AvaloniaSkipXamlCompilation)' == ''">false
+ false
@@ -43,7 +44,7 @@
$(BuildAvaloniaResourcesDependsOn);AddAvaloniaResources;ResolveReferences;_GenerateAvaloniaResourcesDependencyCache
-
+
@@ -106,6 +107,7 @@
DelaySign="$(DelaySign)"
SkipXamlCompilation="$(_AvaloniaSkipXamlCompilation)"
DebuggerLaunch="$(AvaloniaXamlIlDebuggerLaunch)"
+ DefaultCompileBindings="$(AvaloniaUseCompiledBindingsByDefault)"
/>
-
+
diff --git a/samples/BindingDemo/TestItemView.xaml b/samples/BindingDemo/TestItemView.xaml
index 46c34c6507..6edade34b2 100644
--- a/samples/BindingDemo/TestItemView.xaml
+++ b/samples/BindingDemo/TestItemView.xaml
@@ -1,8 +1,10 @@
+ xmlns:viewModels="using:BindingDemo.ViewModels"
+ x:Class="BindingDemo.TestItemView"
+ x:DataType="viewModels:TestItem">
-
\ No newline at end of file
+
diff --git a/samples/ControlCatalog.Blazor.Web/Properties/launchSettings.json b/samples/ControlCatalog.Blazor.Web/Properties/launchSettings.json
index e4da60f7ca..ad2b1e30f6 100644
--- a/samples/ControlCatalog.Blazor.Web/Properties/launchSettings.json
+++ b/samples/ControlCatalog.Blazor.Web/Properties/launchSettings.json
@@ -8,14 +8,6 @@
}
},
"profiles": {
- "ControlCatalog.Web - IIS Express": {
- "commandName": "IISExpress",
- "launchBrowser": true,
- "inspectUri": "{wsProtocol}://{url.hostname}:{url.port}/_framework/debug/ws-proxy?browser={browserInspectUri}",
- "environmentVariables": {
- "ASPNETCORE_ENVIRONMENT": "Development"
- }
- },
"ControlCatalog.Web": {
"commandName": "Project",
"dotnetRunMessages": "true",
diff --git a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
index 6c17e9ac43..e4c83dca49 100644
--- a/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
+++ b/samples/ControlCatalog.NetCore/ControlCatalog.NetCore.csproj
@@ -5,7 +5,7 @@
net6.0
true
true
- 6.0.9
+ 6.0.8
diff --git a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj
index 0ddec3444b..06a5466619 100644
--- a/samples/ControlCatalog.Web/ControlCatalog.Web.csproj
+++ b/samples/ControlCatalog.Web/ControlCatalog.Web.csproj
@@ -16,9 +16,8 @@
full
true
true
- true
- -O3
- -O3
+ -O2
+ -O2
diff --git a/samples/ControlCatalog.Web/Roots.xml b/samples/ControlCatalog.Web/Roots.xml
index 3c13098159..b07fd86fa2 100644
--- a/samples/ControlCatalog.Web/Roots.xml
+++ b/samples/ControlCatalog.Web/Roots.xml
@@ -3,4 +3,5 @@
+
diff --git a/samples/ControlCatalog/App.xaml b/samples/ControlCatalog/App.xaml
index b228e72f72..5570ada27b 100644
--- a/samples/ControlCatalog/App.xaml
+++ b/samples/ControlCatalog/App.xaml
@@ -2,7 +2,6 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:vm="using:ControlCatalog.ViewModels"
x:DataType="vm:ApplicationViewModel"
- x:CompileBindings="True"
Name="Avalonia ControlCatalog"
x:Class="ControlCatalog.App">
diff --git a/samples/ControlCatalog/ControlCatalog.csproj b/samples/ControlCatalog/ControlCatalog.csproj
index 2654574a3e..6b550a30be 100644
--- a/samples/ControlCatalog/ControlCatalog.csproj
+++ b/samples/ControlCatalog/ControlCatalog.csproj
@@ -14,9 +14,6 @@
-
-
-
@@ -35,17 +32,5 @@
-
-
- MSBuild:Compile
-
-
-
-
-
- %(Filename)
-
-
-
diff --git a/samples/ControlCatalog/DecoratedWindow.xaml b/samples/ControlCatalog/DecoratedWindow.xaml
index c778b31c42..997ae54f41 100644
--- a/samples/ControlCatalog/DecoratedWindow.xaml
+++ b/samples/ControlCatalog/DecoratedWindow.xaml
@@ -2,7 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
x:Class="ControlCatalog.DecoratedWindow"
Title="Avalonia Control Gallery"
- xmlns:local="clr-namespace:ControlCatalog" SystemDecorations="None" Name="Window">
+ SystemDecorations="None" Name="Window">
@@ -43,8 +43,13 @@
Hello world!
- Decorated
-
+
+
+ None
+ BorderOnly
+ Full
+
+
CanResize
diff --git a/samples/ControlCatalog/MainView.xaml b/samples/ControlCatalog/MainView.xaml
index ec198c6bba..b5a09b5fbd 100644
--- a/samples/ControlCatalog/MainView.xaml
+++ b/samples/ControlCatalog/MainView.xaml
@@ -1,9 +1,11 @@
+ xmlns:controls="using:ControlSamples"
+ xmlns:models="using:ControlCatalog.Models"
+ xmlns:pages="using:ControlCatalog.Pages"
+ xmlns:viewModels="using:ControlCatalog.ViewModels"
+ x:DataType="viewModels:MainWindowViewModel">
-
-
+
+
@@ -118,6 +120,9 @@
+
+
+
@@ -130,7 +135,7 @@
-
+
diff --git a/samples/ControlCatalog/MainWindow.xaml b/samples/ControlCatalog/MainWindow.xaml
index d5513904c0..cebb3e0916 100644
--- a/samples/ControlCatalog/MainWindow.xaml
+++ b/samples/ControlCatalog/MainWindow.xaml
@@ -1,19 +1,20 @@
+ x:Class="ControlCatalog.MainWindow" WindowState="{Binding WindowState, Mode=TwoWay}"
+ x:DataType="vm:MainWindowViewModel">
diff --git a/samples/ControlCatalog/MainWindow.xaml.cs b/samples/ControlCatalog/MainWindow.xaml.cs
index c61296ac8f..c589f41442 100644
--- a/samples/ControlCatalog/MainWindow.xaml.cs
+++ b/samples/ControlCatalog/MainWindow.xaml.cs
@@ -11,23 +11,13 @@ namespace ControlCatalog
{
public class MainWindow : Window
{
- private WindowNotificationManager _notificationArea;
private NativeMenu? _recentMenu;
public MainWindow()
{
this.InitializeComponent();
- //Renderer.DrawFps = true;
- //Renderer.DrawDirtyRects = Renderer.DrawFps = true;
-
- _notificationArea = new WindowNotificationManager(this)
- {
- Position = NotificationPosition.TopRight,
- MaxItems = 3
- };
-
- DataContext = new MainWindowViewModel(_notificationArea);
+ DataContext = new MainWindowViewModel();
_recentMenu = ((NativeMenu.GetMenu(this)?.Items[0] as NativeMenuItem)?.Menu?.Items[2] as NativeMenuItem)?.Menu;
}
diff --git a/samples/ControlCatalog/Models/StateData.cs b/samples/ControlCatalog/Models/StateData.cs
new file mode 100644
index 0000000000..bd6d186252
--- /dev/null
+++ b/samples/ControlCatalog/Models/StateData.cs
@@ -0,0 +1,20 @@
+namespace ControlCatalog.Models;
+
+public class StateData
+{
+ public string Name { get; private set; }
+ public string Abbreviation { get; private set; }
+ public string Capital { get; private set; }
+
+ public StateData(string name, string abbreviatoin, string capital)
+ {
+ Name = name;
+ Abbreviation = abbreviatoin;
+ Capital = capital;
+ }
+
+ public override string ToString()
+ {
+ return Name;
+ }
+}
\ No newline at end of file
diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
index faa12bc8da..a492808f1d 100644
--- a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
+++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml
@@ -2,8 +2,8 @@
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
- xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:sys="clr-namespace:System;assembly=netstandard"
+ xmlns:sys="using:System"
+ xmlns:models="using:ControlCatalog.Models"
d:DesignHeight="600"
d:DesignWidth="400">
-
+
diff --git a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs
index bc18327f12..8d9cd74ea7 100644
--- a/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/AutoCompleteBoxPage.xaml.cs
@@ -8,30 +8,12 @@ using System.Threading;
using System.Threading.Tasks;
using Avalonia.Data.Converters;
using Avalonia.Data;
+using ControlCatalog.Models;
namespace ControlCatalog.Pages
{
public class AutoCompleteBoxPage : UserControl
{
- public class StateData
- {
- public string Name { get; private set; }
- public string Abbreviation { get; private set; }
- public string Capital { get; private set; }
-
- public StateData(string name, string abbreviatoin, string capital)
- {
- Name = name;
- Abbreviation = abbreviatoin;
- Capital = capital;
- }
-
- public override string ToString()
- {
- return Name;
- }
- }
-
private StateData[] BuildAllStates()
{
return new StateData[]
diff --git a/samples/ControlCatalog/Pages/BorderPage.xaml b/samples/ControlCatalog/Pages/BorderPage.xaml
index bfc4f86698..7ec7e81e80 100644
--- a/samples/ControlCatalog/Pages/BorderPage.xaml
+++ b/samples/ControlCatalog/Pages/BorderPage.xaml
@@ -1,7 +1,6 @@
diff --git a/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml b/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml
index 323d6d36e9..900e304559 100644
--- a/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml
+++ b/samples/ControlCatalog/Pages/ButtonSpinnerPage.xaml
@@ -1,7 +1,7 @@
+ xmlns:sys="using:System">
The ButtonSpinner control allows you to add button spinners to any element and then respond to the Spin event to manipulate that element.
diff --git a/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml b/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml
index aef96802f4..7a22c0ddab 100644
--- a/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml
+++ b/samples/ControlCatalog/Pages/CalendarDatePickerPage.xaml
@@ -1,6 +1,6 @@
diff --git a/samples/ControlCatalog/Pages/ColorPickerPage.xaml b/samples/ControlCatalog/Pages/ColorPickerPage.xaml
index 69ceaea328..649256ba83 100644
--- a/samples/ControlCatalog/Pages/ColorPickerPage.xaml
+++ b/samples/ControlCatalog/Pages/ColorPickerPage.xaml
@@ -2,9 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:controls="clr-namespace:Avalonia.Controls;assembly=Avalonia.Controls.ColorPicker"
- xmlns:primitives="clr-namespace:Avalonia.Controls.Primitives;assembly=Avalonia.Controls"
- xmlns:pc="clr-namespace:Avalonia.Controls.Primitives.Converters;assembly=Avalonia.Controls.ColorPicker"
+ xmlns:controls="using:Avalonia.Controls"
mc:Ignorable="d"
d:DesignWidth="800"
d:DesignHeight="450"
@@ -24,18 +22,19 @@
HsvColor="hsv(120, 1, 1)"
Margin="0,50,0,0">
-
+
+ RowDefinitions="Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto,Auto">
+
-
+
+
diff --git a/samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs b/samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs
index 4671bbdb7c..52d63ded32 100644
--- a/samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/ColorPickerPage.xaml.cs
@@ -20,6 +20,7 @@ namespace ControlCatalog.Pages
Color = Colors.Blue,
Margin = new Thickness(0, 50, 0, 0),
HorizontalAlignment = HorizontalAlignment.Center,
+ Palette = new MaterialHalfColorPalette(),
};
Grid.SetColumn(colorPicker, 2);
Grid.SetRow(colorPicker, 1);
diff --git a/samples/ControlCatalog/Pages/ComboBoxPage.xaml b/samples/ControlCatalog/Pages/ComboBoxPage.xaml
index 9f2fbb88f3..748a46c447 100644
--- a/samples/ControlCatalog/Pages/ComboBoxPage.xaml
+++ b/samples/ControlCatalog/Pages/ComboBoxPage.xaml
@@ -3,7 +3,9 @@
xmlns="https://github.com/avaloniaui"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:col="using:System.Collections"
- xmlns:sys="using:System">
+ xmlns:sys="using:System"
+ xmlns:viewModels="using:ControlCatalog.ViewModels"
+ x:DataType="viewModels:ComboBoxPageViewModel">
A drop-down list.
@@ -39,7 +41,7 @@
-
+
@@ -71,7 +73,7 @@
SelectedIndex="0"
WrapSelection="{Binding WrapSelection}">
-
+
diff --git a/samples/ControlCatalog/Pages/CompositionPage.axaml b/samples/ControlCatalog/Pages/CompositionPage.axaml
index 22c5c88941..403f45b8eb 100644
--- a/samples/ControlCatalog/Pages/CompositionPage.axaml
+++ b/samples/ControlCatalog/Pages/CompositionPage.axaml
@@ -1,6 +1,6 @@
Implicit animations
@@ -42,4 +42,4 @@
-
\ No newline at end of file
+
diff --git a/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml b/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml
index 871efa5b7a..6ef6a202b6 100644
--- a/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml
+++ b/samples/ControlCatalog/Pages/ContextFlyoutPage.xaml
@@ -3,9 +3,11 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:viewModels="using:ControlCatalog.ViewModels"
d:DesignHeight="450"
d:DesignWidth="800"
- mc:Ignorable="d">
+ mc:Ignorable="d"
+ x:DataType="viewModels:ContextPageViewModel">
diff --git a/samples/ControlCatalog/Pages/DataGridPage.xaml b/samples/ControlCatalog/Pages/DataGridPage.xaml
index 27272a9ff7..f1fcd8035a 100644
--- a/samples/ControlCatalog/Pages/DataGridPage.xaml
+++ b/samples/ControlCatalog/Pages/DataGridPage.xaml
@@ -4,13 +4,13 @@
x:Class="ControlCatalog.Pages.DataGridPage">
-
+
-
+
@@ -49,15 +49,15 @@
AlternatingRowBackground="#1fff">
-
-
+
-
-
+
+
+ IsVisible="{Binding #ShowGDP.IsChecked}"
+ x:DataType="local:Country" />
@@ -65,11 +65,11 @@
-
-
-
-
-
+
+
+
+
+
@@ -77,9 +77,9 @@
-
-
-
+
+
+
diff --git a/samples/ControlCatalog/Pages/DateTimePickerPage.xaml b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml
index 6217d39b21..47753f56b6 100644
--- a/samples/ControlCatalog/Pages/DateTimePickerPage.xaml
+++ b/samples/ControlCatalog/Pages/DateTimePickerPage.xaml
@@ -2,7 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:sys="clr-namespace:System;assembly=netstandard"
+ xmlns:sys="using:System"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="ControlCatalog.Pages.DateTimePickerPage">
diff --git a/samples/ControlCatalog/Pages/ExpanderPage.xaml b/samples/ControlCatalog/Pages/ExpanderPage.xaml
index f9ae1c7a3c..f0a80fd7ab 100644
--- a/samples/ControlCatalog/Pages/ExpanderPage.xaml
+++ b/samples/ControlCatalog/Pages/ExpanderPage.xaml
@@ -1,6 +1,8 @@
+ xmlns:viewModels="using:ControlCatalog.ViewModels"
+ x:Class="ControlCatalog.Pages.ExpanderPage"
+ x:DataType="viewModels:ExpanderPageViewModel">
Expands to show nested content
@@ -32,6 +34,23 @@
Expanded content
+
+
+
+
+
+ Expanded content
+
+
+
+
+ Expanded content
+
+
Rounded
diff --git a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
index 1d42b92096..5ca4ca9bdd 100644
--- a/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
+++ b/samples/ControlCatalog/Pages/ItemsRepeaterPage.xaml
@@ -1,6 +1,8 @@
+ xmlns:viewModels="using:ControlCatalog.ViewModels"
+ x:Class="ControlCatalog.Pages.ItemsRepeaterPage"
+ x:DataType="viewModels:ItemsRepeaterPageViewModel">
diff --git a/samples/ControlCatalog/Pages/NotificationsPage.xaml b/samples/ControlCatalog/Pages/NotificationsPage.xaml
index d48b338fe8..46c1fe52de 100644
--- a/samples/ControlCatalog/Pages/NotificationsPage.xaml
+++ b/samples/ControlCatalog/Pages/NotificationsPage.xaml
@@ -1,6 +1,8 @@
+ xmlns:viewModels="using:ControlCatalog.ViewModels"
+ x:Class="ControlCatalog.Pages.NotificationsPage"
+ x:DataType="viewModels:NotificationViewModel">
diff --git a/samples/ControlCatalog/Pages/NotificationsPage.xaml.cs b/samples/ControlCatalog/Pages/NotificationsPage.xaml.cs
index eadf92b602..bfd49a2c00 100644
--- a/samples/ControlCatalog/Pages/NotificationsPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/NotificationsPage.xaml.cs
@@ -1,18 +1,33 @@
+using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
+using ControlCatalog.ViewModels;
namespace ControlCatalog.Pages
{
public class NotificationsPage : UserControl
{
+ private NotificationViewModel _viewModel;
+
public NotificationsPage()
{
this.InitializeComponent();
+
+ _viewModel = new NotificationViewModel();
+
+ DataContext = _viewModel;
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
+
+ protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e)
+ {
+ base.OnAttachedToVisualTree(e);
+
+ _viewModel.NotificationManager = new Avalonia.Controls.Notifications.WindowNotificationManager(VisualRoot as TopLevel);
+ }
}
}
diff --git a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml
index 045ba4a059..e76787f184 100644
--- a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml
+++ b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml
@@ -1,8 +1,10 @@
+ xmlns:sys="using:System"
+ xmlns:converter="using:ControlCatalog.Converter"
+ xmlns:pages="using:ControlCatalog.Pages"
+ x:Class="ControlCatalog.Pages.NumericUpDownPage"
+ x:DataType="pages:NumbersPageViewModel">
Numeric up-down control provides a TextBox with button spinners that allow incrementing and decrementing numeric values by using the spinner buttons, keyboard up/down arrows, or mouse wheel.
@@ -43,7 +45,7 @@
VerticalAlignment="Center" Margin="2"/>
CultureInfo:
-
Watermark:
@@ -77,6 +79,7 @@
diff --git a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs
index edd4921632..8785eac354 100644
--- a/samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/NumericUpDownPage.xaml.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using Avalonia.Controls;
+using Avalonia.Data.Converters;
using Avalonia.Markup.Xaml;
using MiniMvvm;
@@ -22,6 +23,8 @@ namespace ControlCatalog.Pages
AvaloniaXamlLoader.Load(this);
}
+ public static IValueConverter CultureConverter =
+ new FuncValueConverter(c => (c ?? CultureInfo.CurrentCulture).NumberFormat);
}
public class NumbersPageViewModel : ViewModelBase
diff --git a/samples/ControlCatalog/Pages/OpenGlPage.xaml b/samples/ControlCatalog/Pages/OpenGlPage.xaml
index 0eb09004ba..2a97956e30 100644
--- a/samples/ControlCatalog/Pages/OpenGlPage.xaml
+++ b/samples/ControlCatalog/Pages/OpenGlPage.xaml
@@ -1,7 +1,7 @@
+ xmlns:pages="using:ControlCatalog.Pages">
diff --git a/samples/ControlCatalog/Pages/PlatformInfoPage.xaml b/samples/ControlCatalog/Pages/PlatformInfoPage.xaml
new file mode 100644
index 0000000000..f02741d2da
--- /dev/null
+++ b/samples/ControlCatalog/Pages/PlatformInfoPage.xaml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/ControlCatalog/Pages/PlatformInfoPage.xaml.cs b/samples/ControlCatalog/Pages/PlatformInfoPage.xaml.cs
new file mode 100644
index 0000000000..1f37451782
--- /dev/null
+++ b/samples/ControlCatalog/Pages/PlatformInfoPage.xaml.cs
@@ -0,0 +1,20 @@
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml;
+using ControlCatalog.ViewModels;
+
+namespace ControlCatalog.Pages
+{
+ public class PlatformInfoPage : UserControl
+ {
+ public PlatformInfoPage()
+ {
+ this.InitializeComponent();
+ DataContext = new PlatformInformationViewModel();
+ }
+
+ private void InitializeComponent()
+ {
+ AvaloniaXamlLoader.Load(this);
+ }
+ }
+}
diff --git a/samples/ControlCatalog/Pages/ProgressBarPage.xaml b/samples/ControlCatalog/Pages/ProgressBarPage.xaml
index 8e73f1d0f5..dcddcd5965 100644
--- a/samples/ControlCatalog/Pages/ProgressBarPage.xaml
+++ b/samples/ControlCatalog/Pages/ProgressBarPage.xaml
@@ -31,7 +31,7 @@
+ Minimum="{Binding #minimum.Value}" Maximum="{Binding #maximum.Value}"/>
diff --git a/samples/ControlCatalog/Pages/ScreenPage.cs b/samples/ControlCatalog/Pages/ScreenPage.cs
index 823f59e030..ff62b834c4 100644
--- a/samples/ControlCatalog/Pages/ScreenPage.cs
+++ b/samples/ControlCatalog/Pages/ScreenPage.cs
@@ -62,10 +62,10 @@ namespace ControlCatalog.Pages
CreateFormattedText($"WorkArea: {screen.WorkingArea.Width}:{screen.WorkingArea.Height}");
context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 20));
- formattedText = CreateFormattedText($"Scaling: {screen.PixelDensity * 100}%");
+ formattedText = CreateFormattedText($"Scaling: {screen.Scaling * 100}%");
context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 40));
- formattedText = CreateFormattedText($"Primary: {screen.Primary}");
+ formattedText = CreateFormattedText($"IsPrimary: {screen.IsPrimary}");
context.DrawText(formattedText, boundsRect.Position.WithY(boundsRect.Size.Height + 60));
formattedText =
diff --git a/samples/ControlCatalog/Pages/ScrollViewerPage.xaml b/samples/ControlCatalog/Pages/ScrollViewerPage.xaml
index 1b3771ccb5..1903e50ed7 100644
--- a/samples/ControlCatalog/Pages/ScrollViewerPage.xaml
+++ b/samples/ControlCatalog/Pages/ScrollViewerPage.xaml
@@ -1,6 +1,8 @@
+ xmlns:pages="using:ControlCatalog.Pages"
+ x:Class="ControlCatalog.Pages.ScrollViewerPage"
+ x:DataType="pages:ScrollViewerPageViewModel">
Allows for horizontal and vertical content scrolling.
diff --git a/samples/ControlCatalog/Pages/SliderPage.xaml b/samples/ControlCatalog/Pages/SliderPage.xaml
index b2375922c2..9aa4322ad5 100644
--- a/samples/ControlCatalog/Pages/SliderPage.xaml
+++ b/samples/ControlCatalog/Pages/SliderPage.xaml
@@ -1,6 +1,6 @@
A control that lets the user select from a range of values by moving a Thumb control along a Track.
diff --git a/samples/ControlCatalog/Pages/SplitViewPage.xaml b/samples/ControlCatalog/Pages/SplitViewPage.xaml
index 6902b27715..61bfb490b8 100644
--- a/samples/ControlCatalog/Pages/SplitViewPage.xaml
+++ b/samples/ControlCatalog/Pages/SplitViewPage.xaml
@@ -2,8 +2,10 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:viewModels="using:ControlCatalog.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
- x:Class="ControlCatalog.Pages.SplitViewPage">
+ x:Class="ControlCatalog.Pages.SplitViewPage"
+ x:DataType="viewModels:SplitViewPageViewModel">
@@ -51,7 +53,7 @@
diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml b/samples/ControlCatalog/Pages/TabControlPage.xaml
index cba6fcd0ad..a830ce69ac 100644
--- a/samples/ControlCatalog/Pages/TabControlPage.xaml
+++ b/samples/ControlCatalog/Pages/TabControlPage.xaml
@@ -1,7 +1,9 @@
+ xmlns="https://github.com/avaloniaui"
+ xmlns:viewModels="using:ControlCatalog.ViewModels"
+ x:DataType="viewModels:TabControlPageViewModel">
-
+
-
+
@@ -68,7 +70,7 @@
-
diff --git a/samples/ControlCatalog/Pages/TabControlPage.xaml.cs b/samples/ControlCatalog/Pages/TabControlPage.xaml.cs
index bd0214c72e..74e9928b24 100644
--- a/samples/ControlCatalog/Pages/TabControlPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/TabControlPage.xaml.cs
@@ -5,8 +5,7 @@ using Avalonia.Controls;
using Avalonia.Markup.Xaml;
using Avalonia.Media.Imaging;
using Avalonia.Platform;
-
-using MiniMvvm;
+using ControlCatalog.ViewModels;
namespace ControlCatalog.Pages
{
@@ -18,23 +17,23 @@ namespace ControlCatalog.Pages
{
InitializeComponent();
- DataContext = new PageViewModel
+ DataContext = new TabControlPageViewModel
{
Tabs = new[]
{
- new TabItemViewModel
+ new TabControlPageViewModelItem
{
Header = "Arch",
Text = "This is the first templated tab page.",
Image = LoadBitmap("avares://ControlCatalog/Assets/delicate-arch-896885_640.jpg"),
},
- new TabItemViewModel
+ new TabControlPageViewModelItem
{
Header = "Leaf",
Text = "This is the second templated tab page.",
Image = LoadBitmap("avares://ControlCatalog/Assets/maple-leaf-888807_640.jpg"),
},
- new TabItemViewModel
+ new TabControlPageViewModelItem
{
Header = "Disabled",
Text = "You should not see this.",
@@ -55,26 +54,5 @@ namespace ControlCatalog.Pages
var assets = AvaloniaLocator.Current!.GetService()!;
return new Bitmap(assets.Open(new Uri(uri)));
}
-
- private class PageViewModel : ViewModelBase
- {
- private Dock _tabPlacement;
-
- public TabItemViewModel[]? Tabs { get; set; }
-
- public Dock TabPlacement
- {
- get { return _tabPlacement; }
- set { this.RaiseAndSetIfChanged(ref _tabPlacement, value); }
- }
- }
-
- private class TabItemViewModel
- {
- public string? Header { get; set; }
- public string? Text { get; set; }
- public IBitmap? Image { get; set; }
- public bool IsEnabled { get; set; } = true;
- }
}
}
diff --git a/samples/ControlCatalog/Pages/TabStripPage.xaml b/samples/ControlCatalog/Pages/TabStripPage.xaml
index 163665ef92..533590b48d 100644
--- a/samples/ControlCatalog/Pages/TabStripPage.xaml
+++ b/samples/ControlCatalog/Pages/TabStripPage.xaml
@@ -1,6 +1,8 @@
+ xmlns="https://github.com/avaloniaui"
+ xmlns:viewModels="using:ControlCatalog.ViewModels"
+ x:DataType="viewModels:TabControlPageViewModel">
A control which displays a selectable strip of tabs
@@ -16,14 +18,14 @@
Dynamically generated
-
+
-
-
+
diff --git a/samples/ControlCatalog/Pages/TabStripPage.xaml.cs b/samples/ControlCatalog/Pages/TabStripPage.xaml.cs
index 6d7135a90f..20fae13bbf 100644
--- a/samples/ControlCatalog/Pages/TabStripPage.xaml.cs
+++ b/samples/ControlCatalog/Pages/TabStripPage.xaml.cs
@@ -1,9 +1,6 @@
-using System;
-using Avalonia;
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
-using Avalonia.Media.Imaging;
-using Avalonia.Platform;
+using ControlCatalog.ViewModels;
namespace ControlCatalog.Pages
{
@@ -13,21 +10,24 @@ namespace ControlCatalog.Pages
{
InitializeComponent();
- DataContext = new[]
+ DataContext = new TabControlPageViewModel
{
- new TabStripItemViewModel
+ Tabs = new []
{
- Header = "Item 1",
- },
- new TabStripItemViewModel
- {
- Header = "Item 2",
- },
- new TabStripItemViewModel
- {
- Header = "Disabled",
- IsEnabled = false,
- },
+ new TabControlPageViewModelItem()
+ {
+ Header = "Item 1",
+ },
+ new TabControlPageViewModelItem
+ {
+ Header = "Item 2",
+ },
+ new TabControlPageViewModelItem
+ {
+ Header = "Disabled",
+ IsEnabled = false,
+ },
+ }
};
}
@@ -35,11 +35,5 @@ namespace ControlCatalog.Pages
{
AvaloniaXamlLoader.Load(this);
}
-
- private class TabStripItemViewModel
- {
- public string? Header { get; set; }
- public bool IsEnabled { get; set; } = true;
- }
}
}
diff --git a/samples/ControlCatalog/Pages/TextBlockPage.xaml b/samples/ControlCatalog/Pages/TextBlockPage.xaml
index 32914428ed..6bb428e2c7 100644
--- a/samples/ControlCatalog/Pages/TextBlockPage.xaml
+++ b/samples/ControlCatalog/Pages/TextBlockPage.xaml
@@ -118,7 +118,7 @@
-
+
This is a
TextBlock
with several
@@ -126,7 +126,7 @@
using a variety of styles
.
-
+
diff --git a/samples/ControlCatalog/Pages/TextBoxPage.xaml b/samples/ControlCatalog/Pages/TextBoxPage.xaml
index e3d0c5e4e2..6a4d9b7d0e 100644
--- a/samples/ControlCatalog/Pages/TextBoxPage.xaml
+++ b/samples/ControlCatalog/Pages/TextBoxPage.xaml
@@ -1,7 +1,7 @@
+ xmlns:sys="using:System">
diff --git a/samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml b/samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml
index a410577d27..42042652e3 100644
--- a/samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml
+++ b/samples/ControlCatalog/Pages/TransitioningContentControlPage.axaml
@@ -3,11 +3,10 @@
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:vm="using:ControlCatalog.ViewModels"
- xmlns:converter="clr-namespace:ControlCatalog.Converter"
+ xmlns:converter="using:ControlCatalog.Converter"
xmlns:system="using:System"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:DataType="vm:TransitioningContentControlPageViewModel"
- x:CompileBindings="True"
x:Class="ControlCatalog.Pages.TransitioningContentControlPage">
diff --git a/samples/ControlCatalog/Pages/TreeViewPage.xaml b/samples/ControlCatalog/Pages/TreeViewPage.xaml
index 31f4d73581..1665a59c0a 100644
--- a/samples/ControlCatalog/Pages/TreeViewPage.xaml
+++ b/samples/ControlCatalog/Pages/TreeViewPage.xaml
@@ -1,6 +1,8 @@
+ xmlns:viewModels="using:ControlCatalog.ViewModels"
+ x:Class="ControlCatalog.Pages.TreeViewPage"
+ x:DataType="viewModels:TreeViewPageViewModel">
Displays a hierachical tree of data.
diff --git a/samples/ControlCatalog/Pages/ViewboxPage.xaml b/samples/ControlCatalog/Pages/ViewboxPage.xaml
index e7e3007d35..a31a52d238 100644
--- a/samples/ControlCatalog/Pages/ViewboxPage.xaml
+++ b/samples/ControlCatalog/Pages/ViewboxPage.xaml
@@ -1,6 +1,6 @@
diff --git a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml
index caab42e98c..f1403905c4 100644
--- a/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml
+++ b/samples/ControlCatalog/Pages/WindowCustomizationsPage.xaml
@@ -2,8 +2,10 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:viewModels="using:ControlCatalog.ViewModels"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
- x:Class="ControlCatalog.Pages.WindowCustomizationsPage">
+ x:Class="ControlCatalog.Pages.WindowCustomizationsPage"
+ x:DataType="viewModels:MainWindowViewModel">
diff --git a/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs b/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs
index d73ab810eb..8a3f0ba947 100644
--- a/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs
+++ b/samples/ControlCatalog/ViewModels/CursorPageViewModel.cs
@@ -27,18 +27,18 @@ namespace ControlCatalog.ViewModels
public IEnumerable StandardCursors { get; }
public Cursor CustomCursor { get; }
-
- public class StandardCursorModel
+ }
+
+ public class StandardCursorModel
+ {
+ public StandardCursorModel(StandardCursorType type)
{
- public StandardCursorModel(StandardCursorType type)
- {
- Type = type;
- Cursor = new Cursor(type);
- }
+ Type = type;
+ Cursor = new Cursor(type);
+ }
- public StandardCursorType Type { get; }
+ public StandardCursorType Type { get; }
- public Cursor Cursor { get; }
- }
+ public Cursor Cursor { get; }
}
}
diff --git a/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs b/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs
index 3973c73662..8f8a959867 100644
--- a/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs
+++ b/samples/ControlCatalog/ViewModels/ItemsRepeaterPageViewModel.cs
@@ -10,25 +10,25 @@ namespace ControlCatalog.ViewModels
{
private int _newItemIndex = 1;
private int _newGenerationIndex = 0;
- private ObservableCollection- _items;
+ private ObservableCollection _items;
public ItemsRepeaterPageViewModel()
{
_items = CreateItems();
}
- public ObservableCollection
- Items
+ public ObservableCollection Items
{
get => _items;
set => this.RaiseAndSetIfChanged(ref _items, value);
}
- public Item? SelectedItem { get; set; }
+ public ItemsRepeaterPageViewModelItem? SelectedItem { get; set; }
public void AddItem()
{
var index = SelectedItem != null ? Items.IndexOf(SelectedItem) : -1;
- Items.Insert(index + 1, new Item(index + 1, $"New Item {_newItemIndex++}"));
+ Items.Insert(index + 1, new ItemsRepeaterPageViewModelItem(index + 1, $"New Item {_newItemIndex++}"));
}
public void RemoveItem()
@@ -59,33 +59,33 @@ namespace ControlCatalog.ViewModels
Items = CreateItems();
}
- private ObservableCollection
- CreateItems()
+ private ObservableCollection CreateItems()
{
var suffix = _newGenerationIndex == 0 ? string.Empty : $"[{_newGenerationIndex.ToString()}]";
_newGenerationIndex++;
- return new ObservableCollection
- (
- Enumerable.Range(1, 100000).Select(i => new Item(i, $"Item {i.ToString()} {suffix}")));
+ return new ObservableCollection(
+ Enumerable.Range(1, 100000).Select(i => new ItemsRepeaterPageViewModelItem(i, $"Item {i.ToString()} {suffix}")));
}
+ }
+
+ public class ItemsRepeaterPageViewModelItem : ViewModelBase
+ {
+ private double _height = double.NaN;
- public class Item : ViewModelBase
+ public ItemsRepeaterPageViewModelItem(int index, string text)
{
- private double _height = double.NaN;
-
- public Item(int index, string text)
- {
- Index = index;
- Text = text;
- }
- public int Index { get; }
- public string Text { get; }
+ Index = index;
+ Text = text;
+ }
+ public int Index { get; }
+ public string Text { get; }
- public double Height
- {
- get => _height;
- set => this.RaiseAndSetIfChanged(ref _height, value);
- }
+ public double Height
+ {
+ get => _height;
+ set => this.RaiseAndSetIfChanged(ref _height, value);
}
}
}
diff --git a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
index c2c2462246..b79eff780c 100644
--- a/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
+++ b/samples/ControlCatalog/ViewModels/MainWindowViewModel.cs
@@ -24,25 +24,8 @@ namespace ControlCatalog.ViewModels
private bool _preferSystemChromeEnabled;
private double _titleBarHeight;
- public MainWindowViewModel(IManagedNotificationManager notificationManager)
+ public MainWindowViewModel()
{
- _notificationManager = notificationManager;
-
- ShowCustomManagedNotificationCommand = MiniCommand.Create(() =>
- {
- NotificationManager.Show(new NotificationViewModel(NotificationManager) { Title = "Hey There!", Message = "Did you know that Avalonia now supports Custom In-Window Notifications?" });
- });
-
- ShowManagedNotificationCommand = MiniCommand.Create(() =>
- {
- NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Welcome", "Avalonia now supports Notifications.", NotificationType.Information));
- });
-
- ShowNativeNotificationCommand = MiniCommand.Create(() =>
- {
- NotificationManager.Show(new Avalonia.Controls.Notifications.Notification("Error", "Native Notifications are not quite ready. Coming soon.", NotificationType.Error));
- });
-
AboutCommand = MiniCommand.CreateFromTask(async () =>
{
var dialog = new AboutAvaloniaDialog();
@@ -52,7 +35,6 @@ namespace ControlCatalog.ViewModels
await dialog.ShowDialog(mainWindow);
}
});
-
ExitCommand = MiniCommand.Create(() =>
{
(App.Current?.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.Shutdown();
@@ -143,24 +125,12 @@ namespace ControlCatalog.ViewModels
set { this.RaiseAndSetIfChanged(ref _windowStates, value); }
}
- public IManagedNotificationManager NotificationManager
- {
- get { return _notificationManager; }
- set { this.RaiseAndSetIfChanged(ref _notificationManager, value); }
- }
-
public bool IsMenuItemChecked
{
get { return _isMenuItemChecked; }
set { this.RaiseAndSetIfChanged(ref _isMenuItemChecked, value); }
}
- public MiniCommand ShowCustomManagedNotificationCommand { get; }
-
- public MiniCommand ShowManagedNotificationCommand { get; }
-
- public MiniCommand ShowNativeNotificationCommand { get; }
-
public MiniCommand AboutCommand { get; }
public MiniCommand ExitCommand { get; }
diff --git a/samples/ControlCatalog/ViewModels/NotificationViewModel.cs b/samples/ControlCatalog/ViewModels/NotificationViewModel.cs
index b714c319a6..a31f164a2a 100644
--- a/samples/ControlCatalog/ViewModels/NotificationViewModel.cs
+++ b/samples/ControlCatalog/ViewModels/NotificationViewModel.cs
@@ -6,16 +6,33 @@ namespace ControlCatalog.ViewModels
{
public class NotificationViewModel
{
- public NotificationViewModel(INotificationManager manager)
+ public WindowNotificationManager? NotificationManager { get; set; }
+
+ public NotificationViewModel()
{
+ ShowCustomManagedNotificationCommand = MiniCommand.Create(() =>
+ {
+ NotificationManager?.Show(new NotificationViewModel() { Title = "Hey There!", Message = "Did you know that Avalonia now supports Custom In-Window Notifications?" , NotificationManager = NotificationManager});
+ });
+
+ ShowManagedNotificationCommand = MiniCommand.Create(() =>
+ {
+ NotificationManager?.Show(new Avalonia.Controls.Notifications.Notification("Welcome", "Avalonia now supports Notifications.", NotificationType.Information));
+ });
+
+ ShowNativeNotificationCommand = MiniCommand.Create(() =>
+ {
+ NotificationManager?.Show(new Avalonia.Controls.Notifications.Notification("Error", "Native Notifications are not quite ready. Coming soon.", NotificationType.Error));
+ });
+
YesCommand = MiniCommand.Create(() =>
{
- manager.Show(new Avalonia.Controls.Notifications.Notification("Avalonia Notifications", "Start adding notifications to your app today."));
+ NotificationManager?.Show(new Avalonia.Controls.Notifications.Notification("Avalonia Notifications", "Start adding notifications to your app today."));
});
NoCommand = MiniCommand.Create(() =>
{
- manager.Show(new Avalonia.Controls.Notifications.Notification("Avalonia Notifications", "Start adding notifications to your app today. To find out more visit..."));
+ NotificationManager?.Show(new Avalonia.Controls.Notifications.Notification("Avalonia Notifications", "Start adding notifications to your app today. To find out more visit..."));
});
}
@@ -26,5 +43,11 @@ namespace ControlCatalog.ViewModels
public MiniCommand NoCommand { get; }
+ public MiniCommand ShowCustomManagedNotificationCommand { get; }
+
+ public MiniCommand ShowManagedNotificationCommand { get; }
+
+ public MiniCommand ShowNativeNotificationCommand { get; }
+
}
}
diff --git a/samples/ControlCatalog/ViewModels/PlatformInformationViewModel.cs b/samples/ControlCatalog/ViewModels/PlatformInformationViewModel.cs
new file mode 100644
index 0000000000..e4f6c3ac73
--- /dev/null
+++ b/samples/ControlCatalog/ViewModels/PlatformInformationViewModel.cs
@@ -0,0 +1,54 @@
+using Avalonia;
+using Avalonia.Platform;
+using MiniMvvm;
+
+namespace ControlCatalog.ViewModels;
+#nullable enable
+
+public class PlatformInformationViewModel : ViewModelBase
+{
+ public PlatformInformationViewModel()
+ {
+ var runtimeInfo = AvaloniaLocator.Current.GetService()?.GetRuntimeInfo();
+
+ if (runtimeInfo is { } info)
+ {
+ if (info.IsBrowser)
+ {
+ if (info.IsDesktop)
+ {
+ PlatformInfo = "Platform: Desktop (browser)";
+ }
+ else if (info.IsMobile)
+ {
+ PlatformInfo = "Platform: Mobile (browser)";
+ }
+ else
+ {
+ PlatformInfo = "Platform: Unknown (browser) - please report";
+ }
+ }
+ else
+ {
+ if (info.IsDesktop)
+ {
+ PlatformInfo = "Platform: Desktop (native)";
+ }
+ else if (info.IsMobile)
+ {
+ PlatformInfo = "Platform: Mobile (native)";
+ }
+ else
+ {
+ PlatformInfo = "Platform: Unknown (native) - please report";
+ }
+ }
+ }
+ else
+ {
+
+ }
+ }
+
+ public string PlatformInfo { get; }
+}
diff --git a/samples/ControlCatalog/ViewModels/TabControlPageViewModel.cs b/samples/ControlCatalog/ViewModels/TabControlPageViewModel.cs
new file mode 100644
index 0000000000..b0e560b001
--- /dev/null
+++ b/samples/ControlCatalog/ViewModels/TabControlPageViewModel.cs
@@ -0,0 +1,26 @@
+using Avalonia.Controls;
+using Avalonia.Media.Imaging;
+using MiniMvvm;
+
+namespace ControlCatalog.ViewModels;
+
+public class TabControlPageViewModel : ViewModelBase
+{
+ private Dock _tabPlacement;
+
+ public TabControlPageViewModelItem[]? Tabs { get; set; }
+
+ public Dock TabPlacement
+ {
+ get { return _tabPlacement; }
+ set { this.RaiseAndSetIfChanged(ref _tabPlacement, value); }
+ }
+}
+
+public class TabControlPageViewModelItem
+{
+ public string? Header { get; set; }
+ public string? Text { get; set; }
+ public IBitmap? Image { get; set; }
+ public bool IsEnabled { get; set; } = true;
+}
diff --git a/samples/ControlCatalog/Views/CustomNotificationView.xaml b/samples/ControlCatalog/Views/CustomNotificationView.xaml
index 5b99ed8e4d..f06ea029a8 100644
--- a/samples/ControlCatalog/Views/CustomNotificationView.xaml
+++ b/samples/ControlCatalog/Views/CustomNotificationView.xaml
@@ -1,6 +1,8 @@
+ xmlns:viewModels="using:ControlCatalog.ViewModels"
+ x:Class="ControlCatalog.Views.CustomNotificationView"
+ x:DataType="viewModels:NotificationViewModel">
diff --git a/samples/IntegrationTestApp/IntegrationTestApp.csproj b/samples/IntegrationTestApp/IntegrationTestApp.csproj
index 4284399357..0a761d70ba 100644
--- a/samples/IntegrationTestApp/IntegrationTestApp.csproj
+++ b/samples/IntegrationTestApp/IntegrationTestApp.csproj
@@ -1,7 +1,7 @@
WinExe
- net6.0
+ net7.0
enable
diff --git a/samples/IntegrationTestApp/MainWindow.axaml b/samples/IntegrationTestApp/MainWindow.axaml
index b200dade76..038ced4e5c 100644
--- a/samples/IntegrationTestApp/MainWindow.axaml
+++ b/samples/IntegrationTestApp/MainWindow.axaml
@@ -2,10 +2,12 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:integrationTestApp="using:IntegrationTestApp"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="IntegrationTestApp.MainWindow"
Name="MainWindow"
- Title="IntegrationTestApp">
+ Title="IntegrationTestApp"
+ x:DataType="integrationTestApp:MainWindow">
@@ -69,6 +71,23 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/samples/IntegrationTestApp/MainWindow.axaml.cs b/samples/IntegrationTestApp/MainWindow.axaml.cs
index 3d929e8170..c1acc7ca88 100644
--- a/samples/IntegrationTestApp/MainWindow.axaml.cs
+++ b/samples/IntegrationTestApp/MainWindow.axaml.cs
@@ -4,6 +4,7 @@ using Avalonia;
using Avalonia.Automation;
using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Markup.Xaml;
using Avalonia.VisualTree;
@@ -17,6 +18,7 @@ namespace IntegrationTestApp
{
InitializeComponent();
InitializeViewMenu();
+ InitializeGesturesTab();
this.AttachDevTools();
AddHandler(Button.ClickEvent, OnButtonClick);
ListBoxItems = Enumerable.Range(0, 100).Select(x => "Item " + x).ToList();
@@ -122,6 +124,38 @@ namespace IntegrationTestApp
}
}
+ private void InitializeGesturesTab()
+ {
+ var gestureBorder = this.GetControl("GestureBorder");
+ var gestureBorder2 = this.GetControl("GestureBorder2");
+ var lastGesture = this.GetControl("LastGesture");
+ var resetGestures = this.GetControl
diff --git a/samples/RenderDemo/MainWindow.xaml b/samples/RenderDemo/MainWindow.xaml
index 823a0fbbef..20cc6c43e1 100644
--- a/samples/RenderDemo/MainWindow.xaml
+++ b/samples/RenderDemo/MainWindow.xaml
@@ -1,9 +1,11 @@
diff --git a/samples/RenderDemo/Pages/AnimationsPage.xaml b/samples/RenderDemo/Pages/AnimationsPage.xaml
index 9043bac33e..3f89a9d5f7 100644
--- a/samples/RenderDemo/Pages/AnimationsPage.xaml
+++ b/samples/RenderDemo/Pages/AnimationsPage.xaml
@@ -1,7 +1,9 @@
diff --git a/samples/RenderDemo/Pages/CustomAnimatorPage.xaml b/samples/RenderDemo/Pages/CustomAnimatorPage.xaml
index b386636cae..0c625b6a77 100644
--- a/samples/RenderDemo/Pages/CustomAnimatorPage.xaml
+++ b/samples/RenderDemo/Pages/CustomAnimatorPage.xaml
@@ -1,7 +1,7 @@
diff --git a/samples/RenderDemo/Pages/GlyphRunPage.xaml b/samples/RenderDemo/Pages/GlyphRunPage.xaml
index 7db58e5286..e267b458bd 100644
--- a/samples/RenderDemo/Pages/GlyphRunPage.xaml
+++ b/samples/RenderDemo/Pages/GlyphRunPage.xaml
@@ -2,7 +2,7 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
- xmlns:local="clr-namespace:RenderDemo.Pages"
+ xmlns:local="using:RenderDemo.Pages"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="RenderDemo.Pages.GlyphRunPage">
diff --git a/samples/RenderDemo/Pages/Transform3DPage.axaml b/samples/RenderDemo/Pages/Transform3DPage.axaml
index 30ed35bbac..7f3636648c 100644
--- a/samples/RenderDemo/Pages/Transform3DPage.axaml
+++ b/samples/RenderDemo/Pages/Transform3DPage.axaml
@@ -2,8 +2,10 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+ xmlns:viewModels="using:RenderDemo.ViewModels"
mc:Ignorable="d" d:DesignWidth="600" d:DesignHeight="700"
- x:Class="RenderDemo.Pages.Transform3DPage">
+ x:Class="RenderDemo.Pages.Transform3DPage"
+ x:DataType="viewModels:Transform3DPageViewModel">
diff --git a/samples/RenderDemo/Pages/TransitionsPage.xaml b/samples/RenderDemo/Pages/TransitionsPage.xaml
index 71b6ea0713..006c2eb20b 100644
--- a/samples/RenderDemo/Pages/TransitionsPage.xaml
+++ b/samples/RenderDemo/Pages/TransitionsPage.xaml
@@ -1,7 +1,9 @@
diff --git a/samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml b/samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml
index fbc51414e3..f36b79224a 100644
--- a/samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml
+++ b/samples/SampleControls/HamburgerMenu/HamburgerMenu.xaml
@@ -194,7 +194,7 @@
VerticalAlignment="Center"
Classes="h1"
Margin="{StaticResource HeaderMarginExpandedPane}"
- Text="{Binding $parent[TabControl].SelectedItem.Header, FallbackValue=''}">
+ Text="{Binding $parent[TabControl].SelectedItem.(TabItem.Header), FallbackValue=''}">
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml b/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml
index c404a5c382..0d2e739b2b 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/LayoutExplorerView.axaml
@@ -1,9 +1,10 @@
+ xmlns:controls="using:Avalonia.Diagnostics.Controls"
+ xmlns:converters="using:Avalonia.Diagnostics.Converters"
+ xmlns:viewModels="using:Avalonia.Diagnostics.ViewModels"
+ x:DataType="viewModels:ControlLayoutViewModel">
diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
index 35c9e27f1a..97e21079c1 100644
--- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
+++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
@@ -1,7 +1,9 @@
+ xmlns:views="using:Avalonia.Diagnostics.Views"
+ xmlns:viewModels="using:Avalonia.Diagnostics.ViewModels"
+ x:Class="Avalonia.Diagnostics.Views.MainView"
+ x:DataType="viewModels:MainViewModel">
diff --git a/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml b/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml
index d0280b16a2..d1707d0af2 100644
--- a/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/ManagedFileChooser.xaml
@@ -1,6 +1,7 @@
+ xmlns:dialogs="using:Avalonia.Dialogs"
+ xmlns:internal="using:Avalonia.Dialogs.Internal">
@@ -23,7 +24,7 @@
-
+
-
+
-
+
@@ -222,7 +224,7 @@
Background="Transparent"
ScrollViewer.HorizontalScrollBarVisibility="Disabled">
-
+
@@ -247,7 +249,7 @@
-
+
diff --git a/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml b/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml
index ed29addbc2..4060d7efbe 100644
--- a/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml
@@ -1,9 +1,6 @@
+ xmlns:conv="using:Avalonia.Controls.Converters">
diff --git a/src/Avalonia.Themes.Fluent/Controls/MenuScrollViewer.xaml b/src/Avalonia.Themes.Fluent/Controls/MenuScrollViewer.xaml
index bc71d88a0d..e8508935d2 100644
--- a/src/Avalonia.Themes.Fluent/Controls/MenuScrollViewer.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/MenuScrollViewer.xaml
@@ -1,6 +1,6 @@
+ xmlns:converters="using:Avalonia.Controls.Converters">
diff --git a/src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml b/src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml
index d3eaeafd01..1eb0493b08 100644
--- a/src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/NativeMenuBar.xaml
@@ -1,7 +1,6 @@
+ xmlns:local="using:Avalonia.Themes.Fluent">
@@ -11,16 +10,17 @@
IsVisible="{Binding $parent[TopLevel].(NativeMenu.IsNativeMenuExported), Converter={StaticResource AvaloniaThemesFluentNativeMenuBarInverseBooleanValueConverter}}"
Items="{Binding $parent[TopLevel].(NativeMenu.Menu).Items}">
-
diff --git a/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml b/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml
index dc591ede12..253d85852e 100644
--- a/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/ProgressBar.xaml
@@ -1,7 +1,6 @@
+ xmlns:converters="using:Avalonia.Controls.Converters">
diff --git a/src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml b/src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml
deleted file mode 100644
index 75af2efcb1..0000000000
--- a/src/Avalonia.Themes.Fluent/Controls/RichTextBlock.xaml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
diff --git a/src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml b/src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml
index 2c727b4435..71df4d419f 100644
--- a/src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/ScrollViewer.xaml
@@ -1,7 +1,6 @@
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
diff --git a/src/Avalonia.Themes.Fluent/Controls/SelectableTextBlock.xaml b/src/Avalonia.Themes.Fluent/Controls/SelectableTextBlock.xaml
new file mode 100644
index 0000000000..f630969ae6
--- /dev/null
+++ b/src/Avalonia.Themes.Fluent/Controls/SelectableTextBlock.xaml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Fluent/Controls/Slider.xaml b/src/Avalonia.Themes.Fluent/Controls/Slider.xaml
index f2351dd5be..1e3f51f7ef 100644
--- a/src/Avalonia.Themes.Fluent/Controls/Slider.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/Slider.xaml
@@ -1,6 +1,6 @@
+ xmlns:sys="using:System">
diff --git a/src/Avalonia.Themes.Fluent/Controls/SplitButton.xaml b/src/Avalonia.Themes.Fluent/Controls/SplitButton.xaml
index 9a93b2625b..1a7e72462a 100644
--- a/src/Avalonia.Themes.Fluent/Controls/SplitButton.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/SplitButton.xaml
@@ -1,6 +1,5 @@
diff --git a/src/Avalonia.Themes.Fluent/Controls/TabItem.xaml b/src/Avalonia.Themes.Fluent/Controls/TabItem.xaml
index c8376480d0..b11ad4e03a 100644
--- a/src/Avalonia.Themes.Fluent/Controls/TabItem.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/TabItem.xaml
@@ -1,7 +1,6 @@
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
diff --git a/src/Avalonia.Themes.Fluent/Controls/TabStripItem.xaml b/src/Avalonia.Themes.Fluent/Controls/TabStripItem.xaml
index fab8ba87aa..d5440025ae 100644
--- a/src/Avalonia.Themes.Fluent/Controls/TabStripItem.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/TabStripItem.xaml
@@ -1,7 +1,6 @@
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
diff --git a/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml b/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml
index 17c69da8fd..c6da9f72a3 100644
--- a/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/TextBox.xaml
@@ -1,6 +1,5 @@
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
@@ -161,6 +160,7 @@
TextAlignment="{TemplateBinding TextAlignment}"
TextWrapping="{TemplateBinding TextWrapping}"
LineHeight="{TemplateBinding LineHeight}"
+ LetterSpacing="{TemplateBinding LetterSpacing}"
PasswordChar="{TemplateBinding PasswordChar}"
RevealPassword="{TemplateBinding RevealPassword}"
SelectionBrush="{TemplateBinding SelectionBrush}"
@@ -234,11 +234,12 @@
+ IsChecked="{Binding $parent[TextBox].RevealPassword, Mode=TwoWay}"
+ ClipToBounds="True">
+ IsVisible="{Binding !$parent[ToggleButton].IsChecked}"/>
@@ -252,7 +253,8 @@
+ Command="{Binding $parent[TextBox].Clear}"
+ ClipToBounds="True">
diff --git a/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml b/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml
index 2414ce096e..71ff0eaebc 100644
--- a/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/TimePicker.xaml
@@ -7,8 +7,7 @@
+ xmlns:sys="using:System">
diff --git a/src/Avalonia.Themes.Fluent/Controls/ToggleSwitch.xaml b/src/Avalonia.Themes.Fluent/Controls/ToggleSwitch.xaml
index 90993fcc64..c88839f7d2 100644
--- a/src/Avalonia.Themes.Fluent/Controls/ToggleSwitch.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/ToggleSwitch.xaml
@@ -1,7 +1,6 @@
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
diff --git a/src/Avalonia.Themes.Fluent/Controls/ToolTip.xaml b/src/Avalonia.Themes.Fluent/Controls/ToolTip.xaml
index bc9dceb545..8b086a8d10 100644
--- a/src/Avalonia.Themes.Fluent/Controls/ToolTip.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/ToolTip.xaml
@@ -1,7 +1,6 @@
+ xmlns:sys="using:System">
diff --git a/src/Avalonia.Themes.Fluent/Controls/TreeViewItem.xaml b/src/Avalonia.Themes.Fluent/Controls/TreeViewItem.xaml
index 83abe7848c..9171791a0f 100644
--- a/src/Avalonia.Themes.Fluent/Controls/TreeViewItem.xaml
+++ b/src/Avalonia.Themes.Fluent/Controls/TreeViewItem.xaml
@@ -1,6 +1,6 @@
+ xmlns:converters="using:Avalonia.Controls.Converters">
diff --git a/src/Avalonia.Themes.Fluent/FluentDark.xaml b/src/Avalonia.Themes.Fluent/FluentDark.xaml
index 50b12efd78..aad71b18fa 100644
--- a/src/Avalonia.Themes.Fluent/FluentDark.xaml
+++ b/src/Avalonia.Themes.Fluent/FluentDark.xaml
@@ -1,6 +1,6 @@
+ xmlns:sys="using:System">
diff --git a/src/Avalonia.Themes.Fluent/FluentLight.xaml b/src/Avalonia.Themes.Fluent/FluentLight.xaml
index feb043c5f3..907efe7ee6 100644
--- a/src/Avalonia.Themes.Fluent/FluentLight.xaml
+++ b/src/Avalonia.Themes.Fluent/FluentLight.xaml
@@ -1,6 +1,6 @@
+ xmlns:sys="using:System">
diff --git a/src/Avalonia.Themes.Simple/Controls/ButtonSpinner.xaml b/src/Avalonia.Themes.Simple/Controls/ButtonSpinner.xaml
index 9798e5290b..57d6eabea4 100644
--- a/src/Avalonia.Themes.Simple/Controls/ButtonSpinner.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/ButtonSpinner.xaml
@@ -1,6 +1,5 @@
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
+ xmlns:sys="using:System">
diff --git a/src/Avalonia.Themes.Simple/Controls/CalendarItem.xaml b/src/Avalonia.Themes.Simple/Controls/CalendarItem.xaml
index 2a9ae7cf8d..f022746bf5 100644
--- a/src/Avalonia.Themes.Simple/Controls/CalendarItem.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/CalendarItem.xaml
@@ -162,7 +162,7 @@
-
+
+ xmlns:sys="using:System">
diff --git a/src/Avalonia.Themes.Simple/Controls/DateTimePickerShared.xaml b/src/Avalonia.Themes.Simple/Controls/DateTimePickerShared.xaml
index 8639a2baa2..5f1dea692f 100644
--- a/src/Avalonia.Themes.Simple/Controls/DateTimePickerShared.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/DateTimePickerShared.xaml
@@ -6,9 +6,7 @@
-->
+ xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
diff --git a/src/Avalonia.Themes.Simple/Controls/ManagedFileChooser.xaml b/src/Avalonia.Themes.Simple/Controls/ManagedFileChooser.xaml
index 3416b5406e..61dae9b445 100644
--- a/src/Avalonia.Themes.Simple/Controls/ManagedFileChooser.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/ManagedFileChooser.xaml
@@ -1,6 +1,7 @@
+ xmlns:dialogs="using:Avalonia.Dialogs"
+ xmlns:internal="using:Avalonia.Dialogs.Internal">
-
+
-
+
-
+
-
-
+
@@ -179,7 +180,7 @@
-
+
diff --git a/src/Avalonia.Themes.Simple/Controls/NativeMenuBar.xaml b/src/Avalonia.Themes.Simple/Controls/NativeMenuBar.xaml
index 8c51ec8609..160acd5872 100644
--- a/src/Avalonia.Themes.Simple/Controls/NativeMenuBar.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/NativeMenuBar.xaml
@@ -1,6 +1,6 @@
+ xmlns:default="using:Avalonia.Themes.Simple">
@@ -11,16 +11,17 @@
diff --git a/src/Avalonia.Themes.Simple/Controls/ProgressBar.xaml b/src/Avalonia.Themes.Simple/Controls/ProgressBar.xaml
index 5adb5bde7e..3eb158d5b6 100644
--- a/src/Avalonia.Themes.Simple/Controls/ProgressBar.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/ProgressBar.xaml
@@ -1,6 +1,6 @@
+ xmlns:converters="using:Avalonia.Controls.Converters">
diff --git a/src/Avalonia.Themes.Simple/Controls/RichTextBlock.xaml b/src/Avalonia.Themes.Simple/Controls/RichTextBlock.xaml
deleted file mode 100644
index c0570282cb..0000000000
--- a/src/Avalonia.Themes.Simple/Controls/RichTextBlock.xaml
+++ /dev/null
@@ -1,14 +0,0 @@
-
-
-
-
-
-
-
-
-
diff --git a/src/Avalonia.Themes.Simple/Controls/SelectableTextBlock.xaml b/src/Avalonia.Themes.Simple/Controls/SelectableTextBlock.xaml
new file mode 100644
index 0000000000..aaa6448aea
--- /dev/null
+++ b/src/Avalonia.Themes.Simple/Controls/SelectableTextBlock.xaml
@@ -0,0 +1,18 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Avalonia.Themes.Simple/Controls/SimpleControls.xaml b/src/Avalonia.Themes.Simple/Controls/SimpleControls.xaml
index 644c6ed416..4aefa0136c 100644
--- a/src/Avalonia.Themes.Simple/Controls/SimpleControls.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/SimpleControls.xaml
@@ -64,7 +64,7 @@
-
+
diff --git a/src/Avalonia.Themes.Simple/Controls/SplitButton.xaml b/src/Avalonia.Themes.Simple/Controls/SplitButton.xaml
index f0d22aac97..3c621a981d 100644
--- a/src/Avalonia.Themes.Simple/Controls/SplitButton.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/SplitButton.xaml
@@ -1,6 +1,5 @@
@@ -212,7 +213,8 @@
+ Theme="{StaticResource SimplePasswordBoxRevealButtonTheme}"
+ ClipToBounds="True" />
diff --git a/src/Avalonia.Themes.Simple/Controls/TimePicker.xaml b/src/Avalonia.Themes.Simple/Controls/TimePicker.xaml
index ef37230a1f..58066bb9a5 100644
--- a/src/Avalonia.Themes.Simple/Controls/TimePicker.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/TimePicker.xaml
@@ -7,8 +7,7 @@
+ xmlns:sys="using:System">
diff --git a/src/Avalonia.Themes.Simple/Controls/TreeViewItem.xaml b/src/Avalonia.Themes.Simple/Controls/TreeViewItem.xaml
index 8b3452244c..6ca3262419 100644
--- a/src/Avalonia.Themes.Simple/Controls/TreeViewItem.xaml
+++ b/src/Avalonia.Themes.Simple/Controls/TreeViewItem.xaml
@@ -1,6 +1,6 @@
+ xmlns:converters="using:Avalonia.Controls.Converters">
diff --git a/src/Avalonia.X11/Stubs.cs b/src/Avalonia.X11/Stubs.cs
deleted file mode 100644
index f73512f1e8..0000000000
--- a/src/Avalonia.X11/Stubs.cs
+++ /dev/null
@@ -1,17 +0,0 @@
-using System;
-using Avalonia.Platform;
-
-namespace Avalonia.X11
-{
- class PlatformSettingsStub : IPlatformSettings
- {
- public Size DoubleClickSize { get; } = new Size(2, 2);
- public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500);
-
- ///
- public Size TouchDoubleClickSize => new Size(16, 16);
-
- ///
- public TimeSpan TouchDoubleClickTime => DoubleClickTime;
- }
-}
diff --git a/src/Avalonia.X11/X11Platform.cs b/src/Avalonia.X11/X11Platform.cs
index 381cdd74c3..96dc16e186 100644
--- a/src/Avalonia.X11/X11Platform.cs
+++ b/src/Avalonia.X11/X11Platform.cs
@@ -80,7 +80,7 @@ namespace Avalonia.X11
.Bind().ToFunc(() => KeyboardDevice)
.Bind().ToConstant(new X11CursorFactory(Display))
.Bind().ToConstant(new X11Clipboard(this))
- .Bind().ToConstant(new PlatformSettingsStub())
+ .Bind().ToSingleton()
.Bind().ToConstant(new X11IconLoader())
.Bind().ToConstant(new LinuxMountedVolumeInfoProvider())
.Bind().ToConstant(new X11PlatformLifetimeEvents(this));
diff --git a/src/Avalonia.X11/X11Screens.cs b/src/Avalonia.X11/X11Screens.cs
index bcaafb6a53..ba6029b350 100644
--- a/src/Avalonia.X11/X11Screens.cs
+++ b/src/Avalonia.X11/X11Screens.cs
@@ -9,7 +9,7 @@ using JetBrains.Annotations;
namespace Avalonia.X11
{
- class X11Screens : IScreenImpl
+ class X11Screens : IScreenImpl
{
private IX11Screens _impl;
@@ -218,7 +218,7 @@ namespace Avalonia.X11
public int ScreenCount => _impl.Screens.Length;
public IReadOnlyList AllScreens =>
- _impl.Screens.Select(s => new Screen(s.PixelDensity, s.Bounds, s.WorkingArea, s.Primary)).ToArray();
+ _impl.Screens.Select(s => new Screen(s.Scaling, s.Bounds, s.WorkingArea, s.IsPrimary)).ToArray();
}
interface IX11Screens
@@ -281,30 +281,34 @@ namespace Avalonia.X11
{
private const int FullHDWidth = 1920;
private const int FullHDHeight = 1080;
- public bool Primary { get; }
+ public bool IsPrimary { get; }
public string Name { get; set; }
public PixelRect Bounds { get; set; }
public Size? PhysicalSize { get; set; }
- public double PixelDensity { get; set; }
+ public double Scaling { get; set; }
public PixelRect WorkingArea { get; set; }
- public X11Screen(PixelRect bounds, bool primary,
- string name, Size? physicalSize, double? pixelDensity)
+ public X11Screen(
+ PixelRect bounds,
+ bool isPrimary,
+ string name,
+ Size? physicalSize,
+ double? scaling)
{
- Primary = primary;
+ IsPrimary = isPrimary;
Name = name;
Bounds = bounds;
- if (physicalSize == null && pixelDensity == null)
+ if (physicalSize == null && scaling == null)
{
- PixelDensity = 1;
+ Scaling = 1;
}
- else if (pixelDensity == null)
+ else if (scaling == null)
{
- PixelDensity = GuessPixelDensity(bounds, physicalSize.Value);
+ Scaling = GuessPixelDensity(bounds, physicalSize.Value);
}
else
{
- PixelDensity = pixelDensity.Value;
+ Scaling = scaling.Value;
PhysicalSize = physicalSize;
}
}
diff --git a/src/Avalonia.X11/X11Window.Ime.cs b/src/Avalonia.X11/X11Window.Ime.cs
index d68feaca78..ca987d0a07 100644
--- a/src/Avalonia.X11/X11Window.Ime.cs
+++ b/src/Avalonia.X11/X11Window.Ime.cs
@@ -8,6 +8,7 @@ using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.Platform.Interop;
+using JetBrains.Annotations;
using static Avalonia.X11.XLib;
namespace Avalonia.X11
@@ -104,13 +105,18 @@ namespace Avalonia.X11
if (ev.KeyEvent.state.HasAllFlags(XModifierMask.Mod2Mask)
&& key > X11Key.Num_Lock && key <= X11Key.KP_9)
key = (X11Key)XKeycodeToKeysym(_x11.Display, ev.KeyEvent.keycode, index ? 0 : 1).ToInt32();
-
- var filtered = ScheduleKeyInput(new RawKeyEventArgs(_keyboard, (ulong)ev.KeyEvent.time.ToInt64(), _inputRoot,
- ev.type == XEventName.KeyPress ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
- X11KeyTransform.ConvertKey(key), TranslateModifiers(ev.KeyEvent.state)), ref ev, (int)key, ev.KeyEvent.keycode);
-
- if (ev.type == XEventName.KeyPress && !filtered)
- TriggerClassicTextInputEvent(ref ev);
+
+ var convertedKey = X11KeyTransform.ConvertKey(key);
+ var modifiers = TranslateModifiers(ev.KeyEvent.state);
+ var timestamp = (ulong)ev.KeyEvent.time.ToInt64();
+ RawKeyEventArgs args =
+ ev.type == XEventName.KeyPress
+ ? new RawKeyEventArgsWithText(_keyboard, timestamp, _inputRoot, RawKeyEventType.KeyDown,
+ convertedKey, modifiers, TranslateEventToString(ref ev))
+ : new RawKeyEventArgs(_keyboard, timestamp, _inputRoot, RawKeyEventType.KeyUp, convertedKey,
+ modifiers);
+
+ ScheduleKeyInput(args, ref ev, (int)key, ev.KeyEvent.keycode);
}
void TriggerClassicTextInputEvent(ref XEvent ev)
@@ -156,16 +162,15 @@ namespace Avalonia.X11
}
- bool ScheduleKeyInput(RawKeyEventArgs args, ref XEvent xev, int keyval, int keycode)
+ void ScheduleKeyInput(RawKeyEventArgs args, ref XEvent xev, int keyval, int keycode)
{
_x11.LastActivityTimestamp = xev.ButtonEvent.time;
- if (_imeControl != null && _imeControl.IsEnabled)
- {
- if (FilterIme(args, xev, keyval, keycode))
- return true;
- }
+
+ if (_imeControl is { IsEnabled: true }
+ && FilterIme(args, xev, keyval, keycode))
+ return;
+
ScheduleInput(args);
- return false;
}
bool FilterIme(RawKeyEventArgs args, XEvent xev, int keyval, int keycode)
@@ -190,11 +195,7 @@ namespace Avalonia.X11
{
var ev = _imeQueue.Dequeue();
if (_imeControl == null || !await _imeControl.HandleEventAsync(ev.args, ev.keyval, ev.keycode))
- {
ScheduleInput(ev.args);
- if (ev.args.Type == RawKeyEventType.KeyDown)
- TriggerClassicTextInputEvent(ref ev.xev);
- }
}
}
finally
@@ -202,5 +203,18 @@ namespace Avalonia.X11
_processingIme = false;
}
}
+
+ // This class is used to attach the text value of the key to an asynchronously dispatched KeyDown event
+ class RawKeyEventArgsWithText : RawKeyEventArgs
+ {
+ public RawKeyEventArgsWithText([NotNull] IKeyboardDevice device, ulong timestamp, [NotNull] IInputRoot root,
+ RawKeyEventType type, Key key, RawInputModifiers modifiers, string text) :
+ base(device, timestamp, root, type, key, modifiers)
+ {
+ Text = text;
+ }
+
+ public string Text { get; }
+ }
}
}
diff --git a/src/Avalonia.X11/X11Window.cs b/src/Avalonia.X11/X11Window.cs
index f24c33cafa..0b5ae281f4 100644
--- a/src/Avalonia.X11/X11Window.cs
+++ b/src/Avalonia.X11/X11Window.cs
@@ -120,7 +120,7 @@ namespace Avalonia.X11
if (!_popup && Screen != null)
{
- var monitor = Screen.AllScreens.OrderBy(x => x.PixelDensity)
+ var monitor = Screen.AllScreens.OrderBy(x => x.Scaling)
.FirstOrDefault(m => m.Bounds.Contains(Position));
if (monitor != null)
@@ -190,7 +190,7 @@ namespace Avalonia.X11
UpdateMotifHints();
UpdateSizeHints(null);
- _rawEventGrouper = new RawEventGrouper(e => Input?.Invoke(e));
+ _rawEventGrouper = new RawEventGrouper(DispatchInput);
_transparencyHelper = new TransparencyHelper(_x11, _handle, platform.Globals);
_transparencyHelper.SetTransparencyRequest(WindowTransparencyLevel.None);
@@ -570,9 +570,9 @@ namespace Avalonia.X11
newScaling = _scalingOverride.Value;
else
{
- var monitor = _platform.X11Screens.Screens.OrderBy(x => x.PixelDensity)
+ var monitor = _platform.X11Screens.Screens.OrderBy(x => x.Scaling)
.FirstOrDefault(m => m.Bounds.Contains(Position));
- newScaling = monitor?.PixelDensity ?? RenderScaling;
+ newScaling = monitor?.Scaling ?? RenderScaling;
}
if (RenderScaling != newScaling)
@@ -724,7 +724,13 @@ namespace Avalonia.X11
_x11.LastActivityTimestamp = xev.ButtonEvent.time;
ScheduleInput(args);
}
-
+
+ void DispatchInput(RawInputEventArgs args)
+ {
+ Input?.Invoke(args);
+ if (!args.Handled && args is RawKeyEventArgsWithText text && !string.IsNullOrWhiteSpace(text.Text))
+ Input?.Invoke(new RawTextInputEventArgs(_keyboard, args.Timestamp, _inputRoot, text.Text));
+ }
public void ScheduleXI2Input(RawInputEventArgs args)
{
@@ -994,7 +1000,7 @@ namespace Avalonia.X11
public IScreenImpl Screen => _platform.Screens;
- public Size MaxAutoSizeHint => _platform.X11Screens.Screens.Select(s => s.Bounds.Size.ToSize(s.PixelDensity))
+ public Size MaxAutoSizeHint => _platform.X11Screens.Screens.Select(s => s.Bounds.Size.ToSize(s.Scaling))
.OrderByDescending(x => x.Width + x.Height).FirstOrDefault();
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs b/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs
index e92ad02c7a..ce843952e7 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/DrmOutputOptions.cs
@@ -23,5 +23,11 @@ namespace Avalonia.LinuxFramebuffer
/// Default: R0 G0 B0 A0
///
public Color InitialBufferSwappingColor { get; set; } = new Color(0, 0, 0, 0);
+
+ ///
+ /// specific the video mode with which the DrmOutput should be created, if it is not found it will fallback to the preferred mode.
+ /// If NULL preferred mode will be used.
+ ///
+ public PixelSize? VideoMode { get; set; }
}
}
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.Pointer.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.Pointer.cs
new file mode 100644
index 0000000000..ef039a38bc
--- /dev/null
+++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.Pointer.cs
@@ -0,0 +1,110 @@
+using System;
+using Avalonia.Input;
+using Avalonia.Input.Raw;
+using Avalonia.Logging;
+using static Avalonia.LinuxFramebuffer.Input.LibInput.LibInputNativeUnsafeMethods;
+
+namespace Avalonia.LinuxFramebuffer.Input.LibInput;
+
+public partial class LibInputBackend
+{
+ private MouseDevice _mouse = new MouseDevice();
+ private Point _mousePosition;
+ private const string Pointer = LibInput + "/" + nameof(Pointer);
+
+ private void HandlePointer(IntPtr ev, LibInputEventType type)
+ {
+ var modifiers = RawInputModifiers.None; //TODO: support input modifiers
+ var pev = libinput_event_get_pointer_event(ev);
+ var info = _screen.ScaledSize;
+ var ts = libinput_event_pointer_get_time_usec(pev) / 1000;
+ switch (type)
+ {
+ case LibInputEventType.LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE:
+ _mousePosition = new Point(libinput_event_pointer_get_absolute_x_transformed(pev, (int)info.Width),
+ libinput_event_pointer_get_absolute_y_transformed(pev, (int)info.Height));
+ ScheduleInput(new RawPointerEventArgs(_mouse, ts, _inputRoot, RawPointerEventType.Move, _mousePosition,
+ modifiers));
+ break;
+ case LibInputEventType.LIBINPUT_EVENT_POINTER_BUTTON:
+ {
+ var button = (EvKey)libinput_event_pointer_get_button(pev);
+ var buttonState = libinput_event_pointer_get_button_state(pev);
+
+ RawPointerEventArgs evnt = button switch
+ {
+ EvKey.BTN_LEFT when buttonState == 1
+ => new(_mouse, ts, _inputRoot, RawPointerEventType.LeftButtonDown, _mousePosition, modifiers),
+ EvKey.BTN_LEFT when buttonState == 0
+ => new(_mouse, ts, _inputRoot, RawPointerEventType.LeftButtonUp, _mousePosition, modifiers),
+ EvKey.BTN_RIGHT when buttonState == 1
+ => new(_mouse, ts, _inputRoot, RawPointerEventType.RightButtonUp, _mousePosition, modifiers),
+ EvKey.BTN_RIGHT when buttonState == 2
+ => new(_mouse, ts, _inputRoot, RawPointerEventType.RightButtonDown, _mousePosition, modifiers),
+ EvKey.BTN_MIDDLE when buttonState == 1
+ => new(_mouse, ts, _inputRoot, RawPointerEventType.MiddleButtonDown, _mousePosition, modifiers),
+ EvKey.BTN_MIDDLE when buttonState == 2
+ => new(_mouse, ts, _inputRoot, RawPointerEventType.MiddleButtonUp, _mousePosition, modifiers),
+ _ => default,
+ };
+ if (evnt is not null)
+ {
+ ScheduleInput(evnt);
+ }
+ else
+ {
+ Logger.TryGet(LogEventLevel.Warning, Pointer)
+ ?.Log(this, $"The button {button} is not associated");
+ }
+ }
+ break;
+ // Backward compatibility with low-res wheel
+ case LibInputEventType.LIBINPUT_EVENT_POINTER_AXIS:
+ {
+ var sourceAxis = libinput_event_pointer_get_axis_source(pev);
+ switch (sourceAxis)
+ {
+ case LibInputPointerAxisSource.LIBINPUT_POINTER_AXIS_SOURCE_WHEEL:
+ {
+ var value = libinput_event_pointer_get_axis_value_discrete(pev,
+ LibInputPointerAxis.LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL);
+ ScheduleInput(new RawMouseWheelEventArgs(_mouse
+ , ts
+ , _inputRoot
+ , _mousePosition
+ , new Vector(0, -value)
+ , modifiers));
+ }
+ break;
+ case LibInputPointerAxisSource.LIBINPUT_POINTER_AXIS_SOURCE_FINGER:
+ case LibInputPointerAxisSource.LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS:
+ case LibInputPointerAxisSource.LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT:
+ default:
+ Logger.TryGet(LogEventLevel.Debug, Pointer)
+ ?.Log(this, $"The pointer axis {sourceAxis} is not managed.");
+ break;
+ }
+ }
+ break;
+ // Hi-Res wheel
+ case LibInputEventType.LIBINPUT_EVENT_POINTER_SCROLL_WHEEL:
+ {
+ var value = new Vector(0,
+ -libinput_event_pointer_get_scroll_value_v120(pev,
+ LibInputPointerAxis.LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL) / 120);
+ ScheduleInput(new RawMouseWheelEventArgs(_mouse
+ , ts
+ , _inputRoot
+ , _mousePosition
+ , value
+ , modifiers));
+ }
+ break;
+ default:
+ Logger.TryGet(LogEventLevel.Warning, Pointer)
+ ?.Log(this, $"The pointer event {type} is not mapped.");
+ break;
+ }
+
+ }
+}
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs
index 702ae3f8e5..77e8202fac 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputBackend.cs
@@ -4,18 +4,16 @@ using System.IO;
using System.Threading;
using Avalonia.Input;
using Avalonia.Input.Raw;
-using static Avalonia.LinuxFramebuffer.Input.LibInput.LibInputNativeUnsafeMethods;
+using static Avalonia.LinuxFramebuffer.Input.LibInput.LibInputNativeUnsafeMethods;
namespace Avalonia.LinuxFramebuffer.Input.LibInput
{
- public class LibInputBackend : IInputBackend
+ public partial class LibInputBackend : IInputBackend
{
private IScreenInfoProvider _screen;
private IInputRoot _inputRoot;
private readonly Queue _inputThreadActions = new Queue();
private TouchDevice _touch = new TouchDevice();
- private MouseDevice _mouse = new MouseDevice();
- private Point _mousePosition;
-
+ private const string LibInput = nameof(Avalonia.LinuxFramebuffer) + "/" + nameof(Avalonia.LinuxFramebuffer.Input) + "/" + nameof(LibInput);
private readonly RawEventGroupingThreadingHelper _inputQueue;
private Action _onInput;
private Dictionary _pointers = new Dictionary();
@@ -24,13 +22,13 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput
{
var ctx = libinput_path_create_context();
_inputQueue = new(e => _onInput?.Invoke(e));
- new Thread(()=>InputThread(ctx)).Start();
+ new Thread(() => InputThread(ctx)).Start();
}
private unsafe void InputThread(IntPtr ctx)
{
var fd = libinput_get_fd(ctx);
-
+
var timeval = stackalloc IntPtr[2];
@@ -38,12 +36,11 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput
libinput_path_add_device(ctx, f);
while (true)
{
-
IntPtr ev;
libinput_dispatch(ctx);
while ((ev = libinput_get_event(ctx)) != IntPtr.Zero)
{
-
+
var type = libinput_event_get_type(ev);
if (type >= LibInputEventType.LIBINPUT_EVENT_TOUCH_DOWN &&
type <= LibInputEventType.LIBINPUT_EVENT_TOUCH_CANCEL)
@@ -52,12 +49,12 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput
if (type >= LibInputEventType.LIBINPUT_EVENT_POINTER_MOTION
&& type <= LibInputEventType.LIBINPUT_EVENT_POINTER_AXIS)
HandlePointer(ev, type);
-
+
libinput_event_destroy(ev);
libinput_dispatch(ctx);
}
- pollfd pfd = new pollfd {fd = fd, events = 1};
+ pollfd pfd = new pollfd { fd = fd, events = 1 };
NativeUnsafeMethods.poll(&pfd, new IntPtr(1), 10);
}
}
@@ -67,7 +64,7 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput
private void HandleTouch(IntPtr ev, LibInputEventType type)
{
var tev = libinput_event_get_touch_event(ev);
- if(tev == IntPtr.Zero)
+ if (tev == IntPtr.Zero)
return;
if (type < LibInputEventType.LIBINPUT_EVENT_TOUCH_FRAME)
{
@@ -102,44 +99,6 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput
}
}
- private void HandlePointer(IntPtr ev, LibInputEventType type)
- {
- //TODO: support input modifiers
- var pev = libinput_event_get_pointer_event(ev);
- var info = _screen.ScaledSize;
- var ts = libinput_event_pointer_get_time_usec(pev) / 1000;
- if (type == LibInputEventType.LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE)
- {
- _mousePosition = new Point(libinput_event_pointer_get_absolute_x_transformed(pev, (int)info.Width),
- libinput_event_pointer_get_absolute_y_transformed(pev, (int)info.Height));
- ScheduleInput(new RawPointerEventArgs(_mouse, ts, _inputRoot, RawPointerEventType.Move, _mousePosition,
- RawInputModifiers.None));
- }
- else if (type == LibInputEventType.LIBINPUT_EVENT_POINTER_BUTTON)
- {
- var button = (EvKey)libinput_event_pointer_get_button(pev);
- var buttonState = libinput_event_pointer_get_button_state(pev);
-
-
- var evnt = button == EvKey.BTN_LEFT ?
- (buttonState == 1 ? RawPointerEventType.LeftButtonDown : RawPointerEventType.LeftButtonUp) :
- button == EvKey.BTN_MIDDLE ?
- (buttonState == 1 ? RawPointerEventType.MiddleButtonDown : RawPointerEventType.MiddleButtonUp) :
- button == EvKey.BTN_RIGHT ?
- (buttonState == 1 ?
- RawPointerEventType.RightButtonDown :
- RawPointerEventType.RightButtonUp) :
- (RawPointerEventType)(-1);
- if (evnt == (RawPointerEventType)(-1))
- return;
-
-
- ScheduleInput(
- new RawPointerEventArgs(_mouse, ts, _inputRoot, evnt, _mousePosition, RawInputModifiers.None));
- }
-
- }
-
public void Initialize(IScreenInfoProvider screen, Action onInput)
{
_screen = screen;
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputNativeUnsafeMethods.cs b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputNativeUnsafeMethods.cs
index 0492090461..df6defb653 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputNativeUnsafeMethods.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/Input/LibInput/LibInputNativeUnsafeMethods.cs
@@ -77,6 +77,9 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput
LIBINPUT_EVENT_POINTER_MOTION_ABSOLUTE,
LIBINPUT_EVENT_POINTER_BUTTON,
LIBINPUT_EVENT_POINTER_AXIS,
+ LIBINPUT_EVENT_POINTER_SCROLL_WHEEL,
+ LIBINPUT_EVENT_POINTER_SCROLL_FINGER,
+ LIBINPUT_EVENT_POINTER_SCROLL_CONTINUOUS,
LIBINPUT_EVENT_TOUCH_DOWN = 500,
LIBINPUT_EVENT_TOUCH_UP,
LIBINPUT_EVENT_TOUCH_MOTION,
@@ -97,8 +100,38 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput
LIBINPUT_EVENT_GESTURE_PINCH_END,
LIBINPUT_EVENT_SWITCH_TOGGLE = 900,
}
-
-
+
+ public enum LibInputPointerAxisSource
+ {
+ /**
+ * The event is caused by the rotation of a wheel.
+ **/
+ LIBINPUT_POINTER_AXIS_SOURCE_WHEEL = 1,
+ /**
+ * The event is caused by the movement of one or more fingers on a device.
+ **/
+ LIBINPUT_POINTER_AXIS_SOURCE_FINGER,
+ /**
+ * The event is caused by the motion of some device.
+ **/
+ LIBINPUT_POINTER_AXIS_SOURCE_CONTINUOUS,
+ /**
+ * The event is caused by the tilting of a mouse wheel rather than
+ * its rotation. This method is commonly used on mice without
+ * separate horizontal scroll wheels.
+ * @deprecated This axis source is deprecated as of libinput 1.16.
+ * It was never used by any device before libinput 1.16. All wheel
+ * tilt devices use @ref LIBINPUT_POINTER_AXIS_SOURCE_WHEEL instead.
+ **/
+ LIBINPUT_POINTER_AXIS_SOURCE_WHEEL_TILT,
+ };
+
+ public enum LibInputPointerAxis
+ {
+ LIBINPUT_POINTER_AXIS_SCROLL_VERTICAL = 0,
+ LIBINPUT_POINTER_AXIS_SCROLL_HORIZONTAL = 1,
+ };
+
[DllImport(LibInput)]
public extern static void libinput_event_destroy(IntPtr ev);
@@ -119,21 +152,29 @@ namespace Avalonia.LinuxFramebuffer.Input.LibInput
[DllImport(LibInput)]
public extern static IntPtr libinput_event_get_pointer_event(IntPtr ev);
-
-
+
[DllImport(LibInput)]
public extern static ulong libinput_event_pointer_get_time_usec(IntPtr ev);
[DllImport(LibInput)]
public extern static double libinput_event_pointer_get_absolute_x_transformed(IntPtr ev, int width);
-
+
[DllImport(LibInput)]
public extern static double libinput_event_pointer_get_absolute_y_transformed(IntPtr ev, int height);
-
+
[DllImport(LibInput)]
public extern static int libinput_event_pointer_get_button(IntPtr ev);
[DllImport(LibInput)]
public extern static int libinput_event_pointer_get_button_state(IntPtr ev);
+
+ [DllImport(LibInput)]
+ public extern static LibInputPointerAxisSource libinput_event_pointer_get_axis_source(IntPtr ev);
+
+ [DllImport((LibInput))]
+ public extern static double libinput_event_pointer_get_axis_value_discrete(IntPtr ev, LibInputPointerAxis axis);
+
+ [DllImport(LibInput)]
+ public extern static double libinput_event_pointer_get_scroll_value_v120(IntPtr ev, LibInputPointerAxis axis);
}
}
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
index 730eb0071d..d881a97af2 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/LinuxFramebufferPlatform.cs
@@ -51,7 +51,7 @@ namespace Avalonia.LinuxFramebuffer
.Bind().ToConstant(new RenderLoop())
.Bind().ToTransient()
.Bind().ToConstant(new KeyboardDevice())
- .Bind().ToSingleton()
+ .Bind().ToSingleton()
.Bind().ToSingleton();
Compositor = new Compositor(
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
index 5c7ec2bbd2..a4d7d8b1eb 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/Output/DrmOutput.cs
@@ -51,7 +51,16 @@ namespace Avalonia.LinuxFramebuffer.Output
if(connector == null)
throw new InvalidOperationException("Unable to find connected DRM connector");
- var mode = connector.Modes.OrderByDescending(x => x.IsPreferred)
+ DrmModeInfo? mode = null;
+
+ if (options?.VideoMode != null)
+ {
+ mode = connector.Modes
+ .FirstOrDefault(x => x.Resolution.Width == options.VideoMode.Value.Width &&
+ x.Resolution.Height == options.VideoMode.Value.Height);
+ }
+
+ mode ??= connector.Modes.OrderByDescending(x => x.IsPreferred)
.ThenByDescending(x => x.Resolution.Width * x.Resolution.Height)
//.OrderByDescending(x => x.Resolution.Width * x.Resolution.Height)
.FirstOrDefault();
diff --git a/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs b/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs
index dd60d5f09d..c5c7c13c67 100644
--- a/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs
+++ b/src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs
@@ -14,13 +14,4 @@ namespace Avalonia.LinuxFramebuffer
public void Dispose() { }
}
}
- internal class PlatformSettings : IPlatformSettings
- {
- public Size DoubleClickSize { get; } = new Size(4, 4);
- public TimeSpan DoubleClickTime { get; } = new TimeSpan(0, 0, 0, 0, 500);
-
- public Size TouchDoubleClickSize => new Size(16,16);
-
- public TimeSpan TouchDoubleClickTime => DoubleClickTime;
- }
}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj
index f9be3fd62a..1dc7ce5e99 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/Avalonia.Markup.Xaml.Loader.csproj
@@ -5,7 +5,6 @@
true
Avalonia.Markup.Xaml.Loader
$(DefineConstants);XAMLX_INTERNAL
- 11
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs
index 4df07bcdd8..9393bb0aa4 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaRuntimeXamlLoader.cs
@@ -26,6 +26,22 @@ namespace Avalonia.Markup.Xaml
return Load(stream, localAssembly, rootInstance, uri, designMode);
}
}
+
+ ///
+ /// Loads XAML from a string.
+ ///
+ /// The string containing the XAML.
+ /// Xaml loader configuration.
+ /// The loaded object.
+ public static object Load(string xaml, RuntimeXamlLoaderConfiguration configuration)
+ {
+ Contract.Requires(xaml != null);
+
+ using (var stream = new MemoryStream(Encoding.UTF8.GetBytes(xaml)))
+ {
+ return Load(stream, configuration);
+ }
+ }
///
/// Loads XAML from a stream.
@@ -38,7 +54,17 @@ namespace Avalonia.Markup.Xaml
/// The loaded object.
public static object Load(Stream stream, Assembly localAssembly, object rootInstance = null, Uri uri = null,
bool designMode = false)
- => AvaloniaXamlIlRuntimeCompiler.Load(stream, localAssembly, rootInstance, uri, designMode);
+ => AvaloniaXamlIlRuntimeCompiler.Load(stream, localAssembly, rootInstance, uri, designMode, false);
+
+ ///
+ /// Loads XAML from a stream.
+ ///
+ /// The stream containing the XAML.
+ /// Xaml loader configuration.
+ /// The loaded object.
+ public static object Load(Stream stream, RuntimeXamlLoaderConfiguration configuration)
+ => AvaloniaXamlIlRuntimeCompiler.Load(stream, configuration.LocalAssembly, configuration.RootInstance,
+ configuration.BaseUri, configuration.DesignMode, configuration.UseCompiledBindingsByDefault);
///
/// Parse XAML from a string.
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs
index 1e2a77c34d..b4b258e53e 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/AvaloniaXamlIlRuntimeCompiler.cs
@@ -150,12 +150,12 @@ namespace Avalonia.Markup.Xaml.XamlIl
}
- static object LoadSre(string xaml, Assembly localAssembly, object rootInstance, Uri uri, bool isDesignMode)
+ static object LoadSre(string xaml, Assembly localAssembly, object rootInstance, Uri uri, bool isDesignMode, bool useCompiledBindingsByDefault)
{
var success = false;
try
{
- var rv = LoadSreCore(xaml, localAssembly, rootInstance, uri, isDesignMode);
+ var rv = LoadSreCore(xaml, localAssembly, rootInstance, uri, isDesignMode, useCompiledBindingsByDefault);
success = true;
return rv;
}
@@ -167,7 +167,7 @@ namespace Avalonia.Markup.Xaml.XamlIl
}
- static object LoadSreCore(string xaml, Assembly localAssembly, object rootInstance, Uri uri, bool isDesignMode)
+ static object LoadSreCore(string xaml, Assembly localAssembly, object rootInstance, Uri uri, bool isDesignMode, bool useCompiledBindingsByDefault)
{
InitializeSre();
@@ -178,15 +178,14 @@ namespace Avalonia.Markup.Xaml.XamlIl
var clrPropertyBuilder = tb.DefineNestedType("ClrProperties_" + Guid.NewGuid().ToString("N"));
var indexerClosureType = _sreBuilder.DefineType("IndexerClosure_" + Guid.NewGuid().ToString("N"));
var trampolineBuilder = _sreBuilder.DefineType("Trampolines_" + Guid.NewGuid().ToString("N"));
-
+
var compiler = new AvaloniaXamlIlCompiler(new AvaloniaXamlIlCompilerConfiguration(_sreTypeSystem, asm,
_sreMappings, _sreXmlns, AvaloniaXamlIlLanguage.CustomValueConverter,
new XamlIlClrPropertyInfoEmitter(_sreTypeSystem.CreateTypeBuilder(clrPropertyBuilder)),
new XamlIlPropertyInfoAccessorFactoryEmitter(_sreTypeSystem.CreateTypeBuilder(indexerClosureType)),
new XamlIlTrampolineBuilder(_sreTypeSystem.CreateTypeBuilder(trampolineBuilder))),
_sreEmitMappings,
- _sreContextType) { EnableIlVerification = true };
-
+ _sreContextType) { EnableIlVerification = true, DefaultCompileBindings = useCompiledBindingsByDefault };
IXamlType overrideType = null;
if (rootInstance != null)
@@ -198,13 +197,14 @@ namespace Avalonia.Markup.Xaml.XamlIl
compiler.ParseAndCompile(xaml, uri?.ToString(), null, _sreTypeSystem.CreateTypeBuilder(tb), overrideType);
var created = tb.CreateTypeInfo();
clrPropertyBuilder.CreateTypeInfo();
+ indexerClosureType.CreateTypeInfo();
trampolineBuilder.CreateTypeInfo();
return LoadOrPopulate(created, rootInstance);
}
#endif
-
- static object LoadOrPopulate(Type created, object rootInstance)
+
+ static object LoadOrPopulate(Type created, object rootInstance)
{
var isp = Expression.Parameter(typeof(IServiceProvider));
@@ -250,15 +250,15 @@ namespace Avalonia.Markup.Xaml.XamlIl
}
public static object Load(Stream stream, Assembly localAssembly, object rootInstance, Uri uri,
- bool isDesignMode)
+ bool isDesignMode, bool useCompiledBindingsByDefault)
{
string xaml;
using (var sr = new StreamReader(stream))
xaml = sr.ReadToEnd();
#if RUNTIME_XAML_CECIL
- return LoadCecil(xaml, localAssembly, rootInstance, uri);
+ return LoadCecil(xaml, localAssembly, rootInstance, uri, useCompiledBindingsByDefault);
#else
- return LoadSre(xaml, localAssembly, rootInstance, uri, isDesignMode);
+ return LoadSre(xaml, localAssembly, rootInstance, uri, isDesignMode, useCompiledBindingsByDefault);
#endif
}
@@ -292,7 +292,7 @@ namespace Avalonia.Markup.Xaml.XamlIl
}
private static Dictionary _cecilGeneratedCache = new Dictionary();
- static object LoadCecil(string xaml, Assembly localAssembly, object rootInstance, Uri uri)
+ static object LoadCecil(string xaml, Assembly localAssembly, object rootInstance, Uri uri, bool useCompiledBindingsByDefault)
{
if (uri == null)
throw new InvalidOperationException("Please, go away");
@@ -302,8 +302,6 @@ namespace Avalonia.Markup.Xaml.XamlIl
{
overrideType = _cecilTypeSystem.GetType(rootInstance.GetType().FullName);
}
-
-
var safeUri = uri.ToString()
.Replace(":", "_")
@@ -327,13 +325,16 @@ namespace Avalonia.Markup.Xaml.XamlIl
asm.MainModule.Types.Add(contextDef);
var tb = _cecilTypeSystem.CreateTypeBuilder(def);
-
+
var compiler = new AvaloniaXamlIlCompiler(new XamlIlTransformerConfiguration(_cecilTypeSystem,
localAssembly == null ? null : _cecilTypeSystem.FindAssembly(localAssembly.GetName().Name),
_cecilMappings, XamlIlXmlnsMappings.Resolve(_cecilTypeSystem, _cecilMappings),
AvaloniaXamlIlLanguage.CustomValueConverter),
_cecilEmitMappings,
- _cecilTypeSystem.CreateTypeBuilder(contextDef));
+ _cecilTypeSystem.CreateTypeBuilder(contextDef))
+ {
+ DefaultCompileBindings = useCompiledBindingsByDefault
+ };
compiler.ParseAndCompile(xaml, uri.ToString(), tb, overrideType);
var asmPath = Path.Combine(_cecilEmitDir, safeUri + ".dll");
using(var f = File.Create(asmPath))
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
index f325e6e2d6..1692238d06 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlCompiler.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
+
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using XamlX;
using XamlX.Ast;
@@ -21,20 +22,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
private AvaloniaXamlIlCompiler(TransformerConfiguration configuration, XamlLanguageEmitMappings emitMappings)
: base(configuration, emitMappings, true)
{
- void InsertAfter(params IXamlAstTransformer[] t)
+ void InsertAfter(params IXamlAstTransformer[] t)
=> Transformers.InsertRange(Transformers.FindIndex(x => x is T) + 1, t);
- void InsertBefore(params IXamlAstTransformer[] t)
+ void InsertBefore(params IXamlAstTransformer[] t)
=> Transformers.InsertRange(Transformers.FindIndex(x => x is T), t);
// Before everything else
-
+
Transformers.Insert(0, new XNameTransformer());
Transformers.Insert(1, new IgnoredDirectivesTransformer());
Transformers.Insert(2, _designTransformer = new AvaloniaXamlIlDesignPropertiesTransformer());
Transformers.Insert(3, _bindingTransformer = new AvaloniaBindingExtensionTransformer());
-
+
// Targeted
InsertBefore(
new AvaloniaXamlIlResolveClassesPropertiesTransformer(),
@@ -48,14 +49,17 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
InsertBefore(
new AvaloniaXamlIlControlThemeTransformer(),
new AvaloniaXamlIlSelectorTransformer(),
- new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(),
+ new AvaloniaXamlIlControlTemplateTargetTypeMetadataTransformer(),
new AvaloniaXamlIlBindingPathParser(),
new AvaloniaXamlIlPropertyPathTransformer(),
+ new AvaloniaXamlIlSetterTargetTypeMetadataTransformer(),
new AvaloniaXamlIlSetterTransformer(),
new AvaloniaXamlIlConstructorServiceProviderTransformer(),
new AvaloniaXamlIlTransitionsTypeMetadataTransformer(),
new AvaloniaXamlIlResolveByNameMarkupExtensionReplacer()
);
+ InsertBefore(
+ new AvaloniaXamlIlOptionMarkupExtensionTransformer());
InsertAfter(
new XDataTypeTransformer());
@@ -87,14 +91,14 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
_contextType = CreateContextType(contextTypeBuilder);
}
-
+
public AvaloniaXamlIlCompiler(TransformerConfiguration configuration,
XamlLanguageEmitMappings emitMappings,
IXamlType contextType) : this(configuration, emitMappings)
{
_contextType = contextType;
}
-
+
public const string PopulateName = "__AvaloniaXamlIlPopulate";
public const string BuildName = "__AvaloniaXamlIlBuild";
@@ -116,7 +120,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
{
{XamlNamespaces.Blend2008, XamlNamespaces.Blend2008}
});
-
+
var rootObject = (XamlAstObjectNode)parsed.Root;
var classDirective = rootObject.Children
@@ -131,8 +135,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
false) :
TypeReferenceResolver.ResolveType(CreateTransformationContext(parsed, true),
(XamlAstXmlTypeReference)rootObject.Type, true);
-
-
+
+
if (overrideRootType != null)
{
if (!rootType.Type.IsAssignableFrom(overrideRootType))
@@ -145,7 +149,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
Transform(parsed);
Compile(parsed, tb, _contextType, PopulateName, BuildName, "__AvaloniaXamlIlNsInfo", baseUri, fileSource);
-
+
}
public void OverrideRootType(XamlDocument doc, IXamlAstTypeReference newType)
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs
index c8fced515d..b4999136a4 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguage.cs
@@ -185,6 +185,15 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
public static bool CustomValueConverter(AstTransformationContext context,
IXamlAstValueNode node, IXamlType type, out IXamlAstValueNode result)
{
+ if (node is AvaloniaXamlIlOptionMarkupExtensionTransformer.OptionsMarkupExtensionNode optionsNode)
+ {
+ if (optionsNode.ConvertToReturnType(context, type, out var newOptionsNode))
+ {
+ result = newOptionsNode;
+ return true;
+ }
+ }
+
if (!(node is XamlAstTextNode textNode))
{
result = null;
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs
index d907bcbef9..64fdfe155d 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/AvaloniaXamlIlLanguageParseIntrinsics.cs
@@ -48,10 +48,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
try
{
var thickness = Thickness.Parse(text);
-
+
result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Thickness, types.ThicknessFullConstructor,
new[] { thickness.Left, thickness.Top, thickness.Right, thickness.Bottom });
-
+
return true;
}
catch
@@ -65,10 +65,10 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
try
{
var point = Point.Parse(text);
-
+
result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Point, types.PointFullConstructor,
new[] { point.X, point.Y });
-
+
return true;
}
catch
@@ -76,16 +76,16 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a point", node);
}
}
-
+
if (type.Equals(types.Vector))
{
try
{
var vector = Vector.Parse(text);
-
+
result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Vector, types.VectorFullConstructor,
new[] { vector.X, vector.Y });
-
+
return true;
}
catch
@@ -93,16 +93,16 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a vector", node);
}
}
-
+
if (type.Equals(types.Size))
{
try
{
var size = Size.Parse(text);
-
+
result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Size, types.SizeFullConstructor,
new[] { size.Width, size.Height });
-
+
return true;
}
catch
@@ -110,16 +110,16 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a size", node);
}
}
-
+
if (type.Equals(types.Matrix))
{
try
{
var matrix = Matrix.Parse(text);
-
+
result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.Matrix, types.MatrixFullConstructor,
new[] { matrix.M11, matrix.M12, matrix.M21, matrix.M22, matrix.M31, matrix.M32 });
-
+
return true;
}
catch
@@ -127,16 +127,16 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a matrix", node);
}
}
-
+
if (type.Equals(types.CornerRadius))
{
try
{
var cornerRadius = CornerRadius.Parse(text);
-
+
result = new AvaloniaXamlIlVectorLikeConstantAstNode(node, types, types.CornerRadius, types.CornerRadiusFullConstructor,
new[] { cornerRadius.TopLeft, cornerRadius.TopRight, cornerRadius.BottomRight, cornerRadius.BottomLeft });
-
+
return true;
}
catch
@@ -144,7 +144,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
throw new XamlX.XamlLoadException($"Unable to parse \"{text}\" as a corner radius", node);
}
}
-
+
if (type.Equals(types.Color))
{
if (!Color.TryParse(text, out Color color))
@@ -165,9 +165,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
try
{
var relativePoint = RelativePoint.Parse(text);
-
+
var relativePointTypeRef = new XamlAstClrTypeReference(node, types.RelativePoint, false);
-
+
result = new XamlAstNewClrObjectNode(node, relativePointTypeRef, types.RelativePointFullConstructor, new List
{
new XamlConstantNode(node, types.XamlIlTypes.Double, relativePoint.Point.X),
@@ -188,9 +188,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
try
{
var gridLength = GridLength.Parse(text);
-
+
result = new AvaloniaXamlIlGridLengthAstNode(node, types, gridLength);
-
+
return true;
}
catch
@@ -201,12 +201,12 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
if (type.Equals(types.Cursor))
{
- if (TypeSystemHelpers.TryGetEnumValueNode(types.StandardCursorType, text, node, out var enumConstantNode))
+ if (TypeSystemHelpers.TryGetEnumValueNode(types.StandardCursorType, text, node, false, out var enumConstantNode))
{
var cursorTypeRef = new XamlAstClrTypeReference(node, types.Cursor, false);
result = new XamlAstNewClrObjectNode(node, cursorTypeRef, types.CursorTypeConstructor, new List { enumConstantNode });
-
+
return true;
}
}
@@ -270,12 +270,33 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions
}
}
+ if (type.Equals(types.Uri))
+ {
+ var uriText = text.Trim();
+
+ var kind = ((!uriText?.StartsWith("/") == true) ? UriKind.Absolute : UriKind.Relative);
+
+ if (string.IsNullOrWhiteSpace(uriText) || !Uri.TryCreate(uriText, kind, out var _))
+ {
+ throw new XamlX.XamlLoadException($"Unable to parse text {uriText} as a {kind} uri.", node);
+ }
+ result = new XamlAstNewClrObjectNode(node
+ , new(node, types.Uri, false)
+ , types.UriConstructor
+ , new List()
+ {
+ new XamlConstantNode(node, context.Configuration.WellKnownTypes.String, uriText),
+ new XamlConstantNode(node, types.UriKind, (int)kind),
+ });
+ return true;
+ }
+
result = null;
return false;
}
private static bool ConvertDefinitionList(
- IXamlAstValueNode node,
+ IXamlAstValueNode node,
string text,
AvaloniaXamlIlWellKnownTypes types,
IXamlType listType,
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs
index a09e13389a..288574d116 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlMetadataRemover.cs
@@ -1,4 +1,3 @@
-using System.Linq;
using XamlX.Ast;
using XamlX.Transform;
@@ -8,8 +7,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
- if (node is AvaloniaXamlIlTargetTypeMetadataNode targetType)
- return targetType.Value;
+ while (node is AvaloniaXamlIlTargetTypeMetadataNode targetType)
+ node = targetType.Value;
return node;
}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlOptionMarkupExtensionTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlOptionMarkupExtensionTransformer.cs
new file mode 100644
index 0000000000..5004e594f7
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlOptionMarkupExtensionTransformer.cs
@@ -0,0 +1,403 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Reflection.Emit;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Emit;
+using XamlX.IL;
+using XamlX.Transform;
+using XamlX.TypeSystem;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
+
+internal class AvaloniaXamlIlOptionMarkupExtensionTransformer : IXamlAstTransformer
+{
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+ {
+ if (node is XamlMarkupExtensionNode
+ {
+ Value: XamlAstObjectNode { Type: XamlAstClrTypeReference { Type: { } type } } objectNode
+ } markupExtensionNode
+ && type.FindMethods(m => m.IsPublic && m.Parameters.Count is 1 or 2 && m.ReturnType == context.Configuration.WellKnownTypes.Boolean && m.Name == "ShouldProvideOption").ToArray() is { } methods
+ && methods.Any())
+ {
+ var optionAttribute = context.GetAvaloniaTypes().MarkupExtensionOptionAttribute;
+ var defaultOptionAttribute = context.GetAvaloniaTypes().MarkupExtensionDefaultOptionAttribute;
+
+ var typeArgument = type.GenericArguments?.FirstOrDefault();
+
+ IXamlAstValueNode defaultValue = null;
+ var values = new List();
+
+ if (objectNode.Arguments.FirstOrDefault() is { } argument)
+ {
+ var hasDefaultProp = objectNode.Type.GetClrType().GetAllProperties().Any(p =>
+ p.CustomAttributes.Any(a => a.Type == defaultOptionAttribute));
+ if (hasDefaultProp)
+ {
+ if (objectNode.Arguments.Count > 1)
+ {
+ throw new XamlParseException("Options MarkupExtensions allow only single argument", objectNode);
+ }
+
+ defaultValue = TransformNode(new[] { argument }, typeArgument, objectNode);
+ objectNode.Arguments.Remove(argument);
+ }
+ }
+
+ foreach (var extProp in objectNode.Children.OfType().ToArray())
+ {
+ if (!extProp.Values.Any())
+ {
+ continue;
+ }
+
+ var shouldRemoveProp = false;
+ var onObjs = extProp.Values.OfType()
+ .Where(o => o.Type.GetClrType() == context.GetAvaloniaTypes().OnExtensionType).ToArray();
+ if (onObjs.Any())
+ {
+ shouldRemoveProp = true;
+ foreach (var onObj in onObjs)
+ {
+ var optionsPropNode = onObj.Children.OfType()
+ .SingleOrDefault(v => v.Property.GetClrProperty().Name == "Options")
+ ?.Values.Single();
+ var options = (optionsPropNode as XamlAstTextNode)?.Text?.Split(new[] { ',', ' ' }, StringSplitOptions.RemoveEmptyEntries)
+ ?? Array.Empty();
+ if (options.Length == 0)
+ {
+ throw new XamlParseException("On.Options string must be set", onObj);
+ }
+
+ var content = onObj.Children.OfType()
+ .SingleOrDefault(v => v.Property.GetClrProperty().Name == "Content");
+ if (content is null)
+ {
+ throw new XamlParseException("On content object must be set", onObj);
+ }
+
+ var propertiesSet = options
+ .Select(o => type.GetAllProperties()
+ .FirstOrDefault(p => o.Equals(p.Name, StringComparison.Ordinal))
+ ?? throw new XamlParseException($"Property \"{o}\" wasn't found on the \"{type.Name}\" type", onObj))
+ .ToArray();
+ foreach (var propertySet in propertiesSet)
+ {
+ AddBranchNode(content.Values, propertySet.CustomAttributes, content);
+ }
+ }
+ }
+ else
+ {
+ shouldRemoveProp = AddBranchNode(extProp.Values, extProp.Property.GetClrProperty().CustomAttributes, extProp);
+ }
+
+ if (shouldRemoveProp)
+ {
+ objectNode.Children.Remove(extProp);
+ }
+ }
+
+ if (defaultValue is null && !values.Any())
+ {
+ throw new XamlParseException("Options markup extension requires at least one option to be set", objectNode);
+ }
+
+ return new OptionsMarkupExtensionNode(
+ markupExtensionNode, values.ToArray(), defaultValue,
+ context.Configuration.TypeMappings.ServiceProvider);
+
+ bool AddBranchNode(
+ IReadOnlyCollection valueNodes,
+ IReadOnlyCollection propAttributes,
+ IXamlLineInfo li)
+ {
+ var transformed = TransformNode(valueNodes, typeArgument, li);
+ if (propAttributes.FirstOrDefault(a => a.Type == defaultOptionAttribute) is { } defAttr)
+ {
+ defaultValue = transformed;
+ return true;
+ }
+ else if (propAttributes.FirstOrDefault(a => a.Type == optionAttribute) is { } optAttr)
+ {
+ var option = optAttr.Parameters.Single();
+ if (option is null)
+ {
+ throw new XamlParseException("MarkupExtension option must not be null", li);
+ }
+
+ var optionAsString = option.ToString();
+ IXamlAstValueNode optionNode = null;
+ foreach (var method in methods)
+ {
+ try
+ {
+ var targetType = method.Parameters.Last();
+ if (targetType.FullName == "System.Type")
+ {
+ if (option is IXamlType typeOption)
+ {
+ optionNode = new XamlTypeExtensionNode(li,
+ new XamlAstClrTypeReference(li, typeOption, false), targetType);
+ }
+ }
+ else if (targetType == context.Configuration.WellKnownTypes.String)
+ {
+ optionNode = new XamlConstantNode(li, targetType, optionAsString);
+ }
+ else if (targetType.IsEnum)
+ {
+ if (TypeSystemHelpers.TryGetEnumValueNode(targetType, optionAsString, li, false,
+ out var enumConstantNode))
+ {
+ optionNode = enumConstantNode;
+ }
+ }
+ else if (TypeSystemHelpers.ParseConstantIfTypeAllows(optionAsString, targetType, li,
+ out var constantNode))
+ {
+ optionNode = constantNode;
+ }
+ }
+ catch (FormatException)
+ {
+ // try next method overload
+ }
+
+ if (optionNode is not null)
+ {
+ values.Add(new OptionsMarkupExtensionBranch(optionNode, transformed, method));
+ return true;
+ }
+ }
+
+ throw new XamlParseException($"Option value \"{optionAsString}\" is not assignable to any of existing ShouldProvideOption methods", li);
+ }
+
+ return false;
+ }
+ }
+
+ return node;
+
+ IXamlAstValueNode TransformNode(
+ IReadOnlyCollection values,
+ IXamlType suggestedType,
+ IXamlLineInfo line)
+ {
+ if (suggestedType is not null)
+ {
+ values = values
+ .Select(v => XamlTransformHelpers
+ .TryGetCorrectlyTypedValue(context, v, suggestedType, out var converted)
+ ? converted : v)
+ .ToArray();
+ }
+
+ if (values.Count > 1)
+ {
+ throw new XamlParseException("Options markup extension supports only a singular value", line);
+ }
+
+ return values.Single();
+ }
+ }
+
+ internal sealed class OptionsMarkupExtensionNode : XamlMarkupExtensionNode, IXamlAstValueNode
+ {
+ private readonly IXamlType _contextParameter;
+
+ public OptionsMarkupExtensionNode(
+ XamlMarkupExtensionNode original,
+ OptionsMarkupExtensionBranch[] branches,
+ IXamlAstValueNode defaultNode,
+ IXamlType contextParameter)
+ : base(
+ original.Value,
+ new OptionsMarkupExtensionMethod(new OptionsMarkupExtensionNodesContainer(branches, defaultNode), original.Value.Type.GetClrType(), contextParameter),
+ original.Value)
+ {
+ _contextParameter = contextParameter;
+ }
+
+ public new OptionsMarkupExtensionMethod ProvideValue => (OptionsMarkupExtensionMethod)base.ProvideValue;
+
+ IXamlAstTypeReference IXamlAstValueNode.Type => new XamlAstClrTypeReference(this, ProvideValue.ReturnType, false);
+
+ public override void VisitChildren(IXamlAstVisitor visitor)
+ {
+ ProvideValue.ExtensionNodeContainer.Visit(visitor);
+ base.VisitChildren(visitor);
+ }
+
+ public bool ConvertToReturnType(AstTransformationContext context, IXamlType type, out OptionsMarkupExtensionNode res)
+ {
+ IXamlAstValueNode convertedDefaultNode = null;
+
+ if (ProvideValue.ExtensionNodeContainer.DefaultNode is { } defaultNode)
+ {
+ if (!XamlTransformHelpers.TryGetCorrectlyTypedValue(context, defaultNode, type, out convertedDefaultNode))
+ {
+ res = null;
+ return false;
+ }
+ }
+
+ var convertedBranches = ProvideValue.ExtensionNodeContainer.Branches.Select(b => XamlTransformHelpers
+ .TryGetCorrectlyTypedValue(context, b.Value, type, out var convertedValue) ?
+ new OptionsMarkupExtensionBranch(b.Option, convertedValue, b.ConditionMethod) :
+ null).ToArray();
+ if (convertedBranches.Any(b => b is null))
+ {
+ res = null;
+ return false;
+ }
+
+ res = new OptionsMarkupExtensionNode(this, convertedBranches, convertedDefaultNode, _contextParameter);
+ return true;
+ }
+ }
+
+ internal sealed class OptionsMarkupExtensionNodesContainer : XamlAstNode
+ {
+ public OptionsMarkupExtensionNodesContainer(
+ OptionsMarkupExtensionBranch[] branches,
+ IXamlAstValueNode defaultNode) : base(branches.FirstOrDefault()?.Value ?? defaultNode)
+ {
+ Branches = branches;
+ DefaultNode = defaultNode;
+ }
+
+ public OptionsMarkupExtensionBranch[] Branches { get; }
+ public IXamlAstValueNode DefaultNode { get; private set; }
+
+ public override void VisitChildren(IXamlAstVisitor visitor)
+ {
+ VisitList(Branches, visitor);
+ DefaultNode = (IXamlAstValueNode)DefaultNode?.Visit(visitor);
+ }
+
+ public IXamlType GetReturnType()
+ {
+ var types = Branches.Select(b => b.Value.Type);
+ if (DefaultNode?.Type is { } type)
+ {
+ types = types.Concat(new [] { type });
+ }
+ return types.Select(t => t.GetClrType()).ToArray().GetCommonBaseClass();
+ }
+ }
+
+ internal sealed class OptionsMarkupExtensionBranch : XamlAstNode
+ {
+ public OptionsMarkupExtensionBranch(IXamlAstValueNode option, IXamlAstValueNode value, IXamlMethod conditionMethod) : base(value)
+ {
+ Option = option;
+ Value = value;
+ ConditionMethod = conditionMethod;
+ }
+
+ public IXamlAstValueNode Option { get; set; }
+ public IXamlAstValueNode Value { get; set; }
+ public IXamlMethod ConditionMethod { get; }
+
+ public bool HasContext => ConditionMethod.Parameters.Count > 1;
+
+ public override void VisitChildren(IXamlAstVisitor visitor)
+ {
+ Option = (IXamlAstValueNode)Option.Visit(visitor);
+ Value = (IXamlAstValueNode)Value.Visit(visitor);
+ }
+ }
+
+ internal sealed class OptionsMarkupExtensionMethod : IXamlCustomEmitMethodWithContext
+ {
+ public OptionsMarkupExtensionMethod(
+ OptionsMarkupExtensionNodesContainer extensionNodeContainer,
+ IXamlType declaringType,
+ IXamlType contextParameter)
+ {
+ ExtensionNodeContainer = extensionNodeContainer;
+ DeclaringType = declaringType;
+ Parameters = extensionNodeContainer.Branches.Any(c => c.HasContext) ?
+ new[] { contextParameter } :
+ Array.Empty();
+ }
+
+ public OptionsMarkupExtensionNodesContainer ExtensionNodeContainer { get; }
+
+ public string Name => "ProvideValue";
+ public bool IsPublic => true;
+ public bool IsStatic => false;
+ public IXamlType ReturnType => ExtensionNodeContainer.GetReturnType();
+ public IReadOnlyList Parameters { get; }
+ public IXamlType DeclaringType { get; }
+ public IXamlMethod MakeGenericMethod(IReadOnlyList typeArguments) => throw new NotImplementedException();
+ public IReadOnlyList CustomAttributes => Array.Empty();
+
+ public void EmitCall(XamlEmitContext context, IXamlILEmitter codeGen)
+ {
+ // At this point this extension will be called from MarkupExtensionEmitter.
+ // Since it's a "fake" method, we share stack and locals with parent method.
+ // Real ProvideValue method would pop 2 parameters from the stack and return one. This method should do the same.
+ // At this point we will have on stack:
+ // - context (if parameters > 1)
+ // - markup ext "@this" instance (always)
+ // We always pop context from the stack, as this method decide by itself either context is needed.
+ // We store "@this" as a local variable. But only if any conditional method is an instance method.
+ IXamlLocal @this = null;
+ if (Parameters.Count > 0)
+ {
+ codeGen.Pop();
+ }
+ if (ExtensionNodeContainer.Branches.Any(b => !b.ConditionMethod.IsStatic))
+ {
+ codeGen.Stloc(@this = codeGen.DefineLocal(DeclaringType));
+ }
+ else
+ {
+ codeGen.Pop();
+ }
+
+ // Iterate over all branches and push prepared locals into the stack if needed.
+ var ret = codeGen.DefineLabel();
+ foreach (var branch in ExtensionNodeContainer.Branches)
+ {
+ var next = codeGen.DefineLabel();
+ if (branch.HasContext)
+ {
+ codeGen.Ldloc(context.ContextLocal);
+ }
+ if (!branch.ConditionMethod.IsStatic)
+ {
+ codeGen.Ldloc(@this);
+ }
+ context.Emit(branch.Option, codeGen, branch.Option.Type.GetClrType());
+ codeGen.EmitCall(branch.ConditionMethod);
+ codeGen.Brfalse(next);
+
+ context.Emit(branch.Value, codeGen, branch.Value.Type.GetClrType());
+ codeGen.Br(ret);
+ codeGen.MarkLabel(next);
+ }
+
+ if (ExtensionNodeContainer.DefaultNode is {} defaultNode)
+ {
+ // Nop is needed, otherwise Label wouldn't be set on nested CALL op (limitation of our IL validator).
+ codeGen.Emit(OpCodes.Nop);
+ context.Emit(defaultNode, codeGen, defaultNode.Type.GetClrType());
+ }
+ else
+ {
+ codeGen.EmitDefault(ReturnType);
+ }
+
+ codeGen.MarkLabel(ret);
+ }
+
+ public bool Equals(IXamlMethod other) => ReferenceEquals(this, other);
+ }
+}
+
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
index 70209fb3ad..ba0da4fbe0 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSelectorTransformer.cs
@@ -128,13 +128,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
break;
}
case SelectorGrammar.ChildSyntax child:
- result = new XamlIlCombinatorSelector(result, XamlIlCombinatorSelector.SelectorType.Child);
+ result = new XamlIlCombinatorSelector(result, XamlIlCombinatorSelector.CombinatorSelectorType.Child);
break;
case SelectorGrammar.DescendantSyntax descendant:
- result = new XamlIlCombinatorSelector(result, XamlIlCombinatorSelector.SelectorType.Descendant);
+ result = new XamlIlCombinatorSelector(result, XamlIlCombinatorSelector.CombinatorSelectorType.Descendant);
break;
case SelectorGrammar.TemplateSyntax template:
- result = new XamlIlCombinatorSelector(result, XamlIlCombinatorSelector.SelectorType.Template);
+ result = new XamlIlCombinatorSelector(result, XamlIlCombinatorSelector.CombinatorSelectorType.Template);
break;
case SelectorGrammar.NotSyntax not:
result = new XamlIlNotSelector(result, Create(not.Argument, typeResolver));
@@ -186,18 +186,42 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
=> TypeReferenceResolver.ResolveType(context, $"{p}:{n}", true, node, true));
pn.Values[0] = selector;
- return new AvaloniaXamlIlTargetTypeMetadataNode(on,
+ var templateType = GetLastTemplateTypeFromSelector(selector);
+
+ var styleNode = new AvaloniaXamlIlTargetTypeMetadataNode(on,
new XamlAstClrTypeReference(selector, selector.TargetType, false),
AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style);
+
+ return templateType switch
+ {
+ null => styleNode,
+ _ => new AvaloniaXamlIlTargetTypeMetadataNode(styleNode,
+ new XamlAstClrTypeReference(styleNode, templateType, false),
+ AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.ControlTemplate)
+ };
}
- }
+ private static IXamlType GetLastTemplateTypeFromSelector(XamlIlSelectorNode node)
+ {
+ while (node is not null)
+ {
+ if (node is XamlIlCombinatorSelector
+ {
+ SelectorType: XamlIlCombinatorSelector.CombinatorSelectorType.Template
+ })
+ {
+ return node.Previous.TargetType;
+ }
+ node = node.Previous;
+ }
+ return null;
+ }
+ }
-
abstract class XamlIlSelectorNode : XamlAstNode, IXamlAstValueNode, IXamlAstEmitableNode
{
- protected XamlIlSelectorNode Previous { get; }
+ internal XamlIlSelectorNode Previous { get; }
public abstract IXamlType TargetType { get; }
public XamlIlSelectorNode(XamlIlSelectorNode previous,
@@ -289,19 +313,20 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
class XamlIlCombinatorSelector : XamlIlSelectorNode
{
- private readonly SelectorType _type;
+ private readonly CombinatorSelectorType _type;
- public enum SelectorType
+ public enum CombinatorSelectorType
{
Child,
Descendant,
Template
}
- public XamlIlCombinatorSelector(XamlIlSelectorNode previous, SelectorType type) : base(previous)
+ public XamlIlCombinatorSelector(XamlIlSelectorNode previous, CombinatorSelectorType type) : base(previous)
{
_type = type;
}
+ public CombinatorSelectorType SelectorType => _type;
public override IXamlType TargetType => null;
protected override void DoEmit(XamlEmitContext context, IXamlILEmitter codeGen)
{
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTargetTypeMetadataTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTargetTypeMetadataTransformer.cs
new file mode 100644
index 0000000000..ebc6c01ba8
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTargetTypeMetadataTransformer.cs
@@ -0,0 +1,34 @@
+using System.Linq;
+using XamlX;
+using XamlX.Ast;
+using XamlX.Transform;
+using XamlX.Transform.Transformers;
+
+namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
+
+internal class AvaloniaXamlIlSetterTargetTypeMetadataTransformer : IXamlAstTransformer
+{
+ public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
+ {
+ if (node is XamlAstObjectNode on
+ && on.Children.FirstOrDefault(c => c is XamlAstXmlDirective
+ {
+ Namespace: XamlNamespaces.Xaml2006,
+ Name: "SetterTargetType"
+ }) is { } typeDirective)
+ {
+ var value = ((XamlAstXmlDirective)typeDirective).Values.Single();
+ var type = value is XamlTypeExtensionNode typeNode ? typeNode.Value
+ : value is XamlAstTextNode tn ? TypeReferenceResolver.ResolveType(context, tn.Text, false, tn, true)
+ : null;
+ on.Children.Remove(typeDirective);
+
+ if (type is null)
+ {
+ throw new XamlParseException("Unable to resolve SetterTargetType type", typeDirective);
+ }
+ return new AvaloniaXamlIlTargetTypeMetadataNode(on, type, AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style);
+ }
+ return node;
+ }
+}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
index ceaec972f6..5a6fd8246d 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlSetterTransformer.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
+using XamlX;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
@@ -8,7 +9,6 @@ using XamlX.TypeSystem;
namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
- using XamlParseException = XamlX.XamlParseException;
class AvaloniaXamlIlSetterTransformer : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
@@ -17,10 +17,24 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
&& on.Type.GetClrType().FullName == "Avalonia.Styling.Setter"))
return node;
- var targetTypeNode = context.ParentNodes()
+ IXamlType targetType = null;
+ IXamlLineInfo lineInfo = null;
+
+ var styleParent = context.ParentNodes()
.OfType()
- .FirstOrDefault(x => x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style) ??
- throw new XamlParseException("Can not find parent Style Selector or ControlTemplate TargetType", node);
+ .FirstOrDefault(x => x.ScopeType == AvaloniaXamlIlTargetTypeMetadataNode.ScopeTypes.Style);
+
+ if (styleParent != null)
+ {
+ targetType = styleParent.TargetType.GetClrType()
+ ?? throw new XamlParseException("Can not find parent Style Selector or ControlTemplate TargetType. If setter is not part of the style, you can set x:SetterTargetType directive on its parent.", node);
+ lineInfo = on;
+ }
+
+ if (targetType == null)
+ {
+ throw new XamlParseException("Could not determine target type of Setter", node);
+ }
IXamlType propType = null;
var property = @on.Children.OfType()
@@ -31,9 +45,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
if (propertyName == null)
throw new XamlParseException("Setter.Property must be a string", node);
-
var avaloniaPropertyNode = XamlIlAvaloniaPropertyHelper.CreateNode(context, propertyName,
- new XamlAstClrTypeReference(targetTypeNode, targetTypeNode.TargetType.GetClrType(), false), property.Values[0]);
+ new XamlAstClrTypeReference(lineInfo, targetType, false), property.Values[0]);
property.Values = new List {avaloniaPropertyNode};
propType = avaloniaPropertyNode.AvaloniaPropertyType;
}
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
index dbfbe0e070..bf8427a129 100644
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/CompilerExtensions/Transformers/AvaloniaXamlIlWellKnownTypes.cs
@@ -9,6 +9,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlWellKnownTypes
{
+ public IXamlType RuntimeHelpers { get; }
public IXamlType AvaloniaObject { get; }
public IXamlType IAvaloniaObject { get; }
public IXamlType BindingPriority { get; }
@@ -29,6 +30,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType AssignBindingAttribute { get; }
public IXamlType DependsOnAttribute { get; }
public IXamlType DataTypeAttribute { get; }
+ public IXamlType MarkupExtensionOptionAttribute { get; }
+ public IXamlType MarkupExtensionDefaultOptionAttribute { get; }
+ public IXamlType OnExtensionType { get; }
public IXamlType UnsetValueType { get; }
public IXamlType StyledElement { get; }
public IXamlType IStyledElement { get; }
@@ -101,9 +105,13 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
public IXamlType IResourceDictionary { get; }
public IXamlType ResourceDictionary { get; }
public IXamlMethod ResourceDictionaryDeferredAdd { get; }
+ public IXamlType UriKind { get; }
+ public IXamlConstructor UriConstructor { get; }
public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
{
+ RuntimeHelpers = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers");
+
XamlIlTypes = cfg.WellKnownTypes;
AvaloniaObject = cfg.TypeSystem.GetType("Avalonia.AvaloniaObject");
IAvaloniaObject = cfg.TypeSystem.GetType("Avalonia.IAvaloniaObject");
@@ -125,6 +133,9 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
AssignBindingAttribute = cfg.TypeSystem.GetType("Avalonia.Data.AssignBindingAttribute");
DependsOnAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.DependsOnAttribute");
DataTypeAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.DataTypeAttribute");
+ MarkupExtensionOptionAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.MarkupExtensionOptionAttribute");
+ MarkupExtensionDefaultOptionAttribute = cfg.TypeSystem.GetType("Avalonia.Metadata.MarkupExtensionDefaultOptionAttribute");
+ OnExtensionType = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.MarkupExtensions.On");
AvaloniaObjectBindMethod = AvaloniaObjectExtensions.FindMethod("Bind", IDisposable, false, IAvaloniaObject,
AvaloniaProperty,
IBinding, cfg.WellKnownTypes.Object);
@@ -227,6 +238,8 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
cfg.TypeSystem.GetType("System.Func`2").MakeGenericType(
cfg.TypeSystem.GetType("System.IServiceProvider"),
XamlIlTypes.Object));
+ UriKind = cfg.TypeSystem.GetType("System.UriKind");
+ UriConstructor = Uri.GetConstructor(new List() { cfg.WellKnownTypes.String, UriKind });
}
}
@@ -239,7 +252,7 @@ namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
ctx.SetItem(rv = new AvaloniaXamlIlWellKnownTypes(ctx.Configuration));
return rv;
}
-
+
public static AvaloniaXamlIlWellKnownTypes GetAvaloniaTypes(this XamlEmitContext ctx)
{
if (ctx.TryGetItem(out var rv))
diff --git a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
index c1c0594ec2..cd3682c615 160000
--- a/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
+++ b/src/Markup/Avalonia.Markup.Xaml.Loader/xamlil.github
@@ -1 +1 @@
-Subproject commit c1c0594ec2c35b08988183b1a5b3e34dfa19179d
+Subproject commit cd3682c61577a3518de765f7938295d98ff9808c
diff --git a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
index d0c39f0289..070f0c1cc3 100644
--- a/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
+++ b/src/Markup/Avalonia.Markup.Xaml/Avalonia.Markup.Xaml.csproj
@@ -9,6 +9,7 @@
CS1591
+
@@ -28,6 +29,9 @@
+
+
+
@@ -40,6 +44,7 @@
+
@@ -68,4 +73,8 @@
+
+
+
+
diff --git a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
index e5c6b72d12..578af64abb 100644
--- a/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/AvaloniaXamlLoader.cs
@@ -1,8 +1,5 @@
using System;
using System.IO;
-using System.Reflection;
-using System.Text;
-using Avalonia.Markup.Xaml.XamlIl;
using Avalonia.Platform;
namespace Avalonia.Markup.Xaml
@@ -14,7 +11,7 @@ namespace Avalonia.Markup.Xaml
{
public interface IRuntimeXamlLoader
{
- object Load(Stream stream, Assembly localAsm, object o, Uri baseUri, bool designMode);
+ object Load(Stream stream, RuntimeXamlLoaderConfiguration configuration);
}
///
@@ -67,7 +64,11 @@ namespace Avalonia.Markup.Xaml
using (var stream = asset.stream)
{
var absoluteUri = uri.IsAbsoluteUri ? uri : new Uri(baseUri, uri);
- return runtimeLoader.Load(stream, asset.assembly, null, absoluteUri, false);
+ return runtimeLoader.Load(stream, new RuntimeXamlLoaderConfiguration
+ {
+ LocalAssembly = asset.assembly,
+ BaseUri = absoluteUri
+ });
}
}
diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs
index 970cc767f7..8339fe8e38 100644
--- a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs
+++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/CompiledBindings/CommandAccessorPlugin.cs
@@ -124,7 +124,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{
if (_dependsOnProperties is { Count: > 0 } && _reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc)
{
- WeakEventHandlerManager.Unsubscribe(
+ WeakEventHandlerManager.Unsubscribe(
inpc,
nameof(INotifyPropertyChanged.PropertyChanged),
OnNotifyPropertyChanged);
@@ -145,7 +145,7 @@ namespace Avalonia.Markup.Xaml.MarkupExtensions.CompiledBindings
{
if (_dependsOnProperties is { Count:>0 } && _reference.TryGetTarget(out var o) && o is INotifyPropertyChanged inpc)
{
- WeakEventHandlerManager.Subscribe(
+ WeakEventHandlerManager.Subscribe(
inpc,
nameof(INotifyPropertyChanged.PropertyChanged),
OnNotifyPropertyChanged);
diff --git a/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/On.cs b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/On.cs
new file mode 100644
index 0000000000..0752ab43b5
--- /dev/null
+++ b/src/Markup/Avalonia.Markup.Xaml/MarkupExtensions/On.cs
@@ -0,0 +1,15 @@
+#nullable enable
+using System.Collections.Generic;
+using Avalonia.Metadata;
+
+namespace Avalonia.Markup.Xaml.MarkupExtensions;
+
+public class On
+{
+ public IReadOnlyList Options { get; } = new List();
+
+ [Content]
+ public TReturn? Content { get; set; }
+}
+
+public class On : On
/// Image to save
/// Target file.
- public static void SaveImage(SKImage image, string fileName)
+ ///
+ /// The optional quality for PNG compression.
+ /// The quality value is interpreted from 0 - 100. If quality is null
+ /// the encoder applies the default quality value.
+ ///
+ public static void SaveImage(SKImage image, string fileName, int? quality = null)
{
if (image == null) throw new ArgumentNullException(nameof(image));
if (fileName == null) throw new ArgumentNullException(nameof(fileName));
using (var stream = File.Create(fileName))
{
- SaveImage(image, stream);
+ SaveImage(image, stream, quality);
}
}
@@ -29,16 +34,30 @@ namespace Avalonia.Skia.Helpers
/// Save Skia image to a stream.
///
/// Image to save
- /// Target stream.
- public static void SaveImage(SKImage image, Stream stream)
+ ///
+ /// The optional quality for PNG compression.
+ /// The quality value is interpreted from 0 - 100. If quality is null
+ /// the encoder applies the default quality value.
+ ///
+ public static void SaveImage(SKImage image, Stream stream, int? quality = null)
{
if (image == null) throw new ArgumentNullException(nameof(image));
if (stream == null) throw new ArgumentNullException(nameof(stream));
- using (var data = image.Encode())
+ if (quality == null)
{
- data.SaveTo(stream);
+ using (var data = image.Encode())
+ {
+ data.SaveTo(stream);
+ }
+ }
+ else
+ {
+ using (var data = image.Encode(SKEncodedImageFormat.Png, (int)quality))
+ {
+ data.SaveTo(stream);
+ }
}
}
}
-}
\ No newline at end of file
+}
diff --git a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs
index 6400d67fde..e24d805050 100644
--- a/src/Skia/Avalonia.Skia/ImmutableBitmap.cs
+++ b/src/Skia/Avalonia.Skia/ImmutableBitmap.cs
@@ -139,13 +139,13 @@ namespace Avalonia.Skia
}
///
- public void Save(string fileName)
+ public void Save(string fileName, int? quality = null)
{
ImageSavingHelper.SaveImage(_image, fileName);
}
///
- public void Save(Stream stream)
+ public void Save(Stream stream, int? quality = null)
{
ImageSavingHelper.SaveImage(_image, stream);
}
diff --git a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
index a9696efbd4..f34e25299c 100644
--- a/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
+++ b/src/Skia/Avalonia.Skia/PlatformRenderInterface.cs
@@ -12,8 +12,6 @@ using Avalonia.OpenGL.Imaging;
using Avalonia.Platform;
using Avalonia.Media.Imaging;
using SkiaSharp;
-using System.Runtime.InteropServices;
-using System.Drawing;
namespace Avalonia.Skia
{
@@ -79,7 +77,7 @@ namespace Avalonia.Skia
var skFont = new SKFont(glyphTypeface.Typeface, fontRenderingEmSize)
{
Size = fontRenderingEmSize,
- Edging = SKFontEdging.Antialias,
+ Edging = SKFontEdging.Alias,
Hinting = SKFontHinting.None,
LinearMetrics = true
};
@@ -244,85 +242,91 @@ namespace Avalonia.Skia
"Current GPU acceleration backend does not support OpenGL integration");
}
- public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
- => new SKGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
+ public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices,
+ IReadOnlyList glyphAdvances, IReadOnlyList glyphOffsets)
+ {
+ if (glyphTypeface == null)
+ {
+ throw new ArgumentNullException(nameof(glyphTypeface));
+ }
- public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
- => new SKHorizontalGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
+ if (glyphIndices == null)
+ {
+ throw new ArgumentNullException(nameof(glyphIndices));
+ }
- public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
- => new SKPositionedGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
+ var glyphTypefaceImpl = glyphTypeface as GlyphTypefaceImpl;
- private abstract class SKGlyphRunBufferBase : IGlyphRunBuffer
- {
- protected readonly SKTextBlobBuilder _builder;
- protected readonly SKFont _font;
+ var font = new SKFont
+ {
+ LinearMetrics = true,
+ Subpixel = true,
+ Edging = SKFontEdging.SubpixelAntialias,
+ Hinting = SKFontHinting.Full,
+ Size = (float)fontRenderingEmSize,
+ Typeface = glyphTypefaceImpl.Typeface,
+ Embolden = (glyphTypefaceImpl.FontSimulations & FontSimulations.Bold) != 0,
+ SkewX = (glyphTypefaceImpl.FontSimulations & FontSimulations.Oblique) != 0 ? -0.2f : 0
+ };
+
+ var builder = new SKTextBlobBuilder();
- public SKGlyphRunBufferBase(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+ var count = glyphIndices.Count;
+
+ if (glyphOffsets != null && glyphAdvances != null)
{
- _builder = new SKTextBlobBuilder();
+ var runBuffer = builder.AllocatePositionedRun(font, count);
- var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface;
+ var glyphSpan = runBuffer.GetGlyphSpan();
+ var positionSpan = runBuffer.GetPositionSpan();
- _font = new SKFont
- {
- Subpixel = true,
- Edging = SKFontEdging.SubpixelAntialias,
- Hinting = SKFontHinting.Full,
- LinearMetrics = true,
- Size = fontRenderingEmSize,
- Typeface = glyphTypefaceImpl.Typeface,
- Embolden = glyphTypefaceImpl.IsFakeBold,
- SkewX = glyphTypefaceImpl.IsFakeItalic ? -0.2f : 0
- };
- }
+ var currentX = 0.0;
- public abstract Span GlyphIndices { get; }
+ for (int i = 0; i < glyphOffsets.Count; i++)
+ {
+ var offset = glyphOffsets[i];
- public IGlyphRunImpl Build()
- {
- return new GlyphRunImpl(_builder.Build());
- }
- }
+ glyphSpan[i] = glyphIndices[i];
- private sealed class SKGlyphRunBuffer : SKGlyphRunBufferBase
- {
- private readonly SKRunBuffer _buffer;
+ positionSpan[i] = new SKPoint((float)(currentX + offset.X), (float)offset.Y);
- public SKGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length)
- {
- _buffer = _builder.AllocateRun(_font, length, 0, 0);
+ currentX += glyphAdvances[i];
+ }
}
+ else
+ {
+ if (glyphAdvances != null)
+ {
+ var runBuffer = builder.AllocateHorizontalRun(font, count, 0);
- public override Span GlyphIndices => _buffer.GetGlyphSpan();
- }
+ var glyphSpan = runBuffer.GetGlyphSpan();
+ var positionSpan = runBuffer.GetPositionSpan();
- private sealed class SKHorizontalGlyphRunBuffer : SKGlyphRunBufferBase, IHorizontalGlyphRunBuffer
- {
- private readonly SKHorizontalRunBuffer _buffer;
+ var currentX = 0.0;
- public SKHorizontalGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length)
- {
- _buffer = _builder.AllocateHorizontalRun(_font, length, 0);
- }
+ for (int i = 0; i < glyphAdvances.Count; i++)
+ {
+ glyphSpan[i] = glyphIndices[i];
- public override Span GlyphIndices => _buffer.GetGlyphSpan();
+ positionSpan[i] = (float)currentX;
- public Span GlyphPositions => _buffer.GetPositionSpan();
- }
+ currentX += glyphAdvances[i];
+ }
+ }
+ else
+ {
+ var runBuffer = builder.AllocateRun(font, count, 0, 0);
- private sealed class SKPositionedGlyphRunBuffer : SKGlyphRunBufferBase, IPositionedGlyphRunBuffer
- {
- private readonly SKPositionedRunBuffer _buffer;
+ var glyphSpan = runBuffer.GetGlyphSpan();
- public SKPositionedGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length) : base(glyphTypeface, fontRenderingEmSize, length)
- {
- _buffer = _builder.AllocatePositionedRun(_font, length);
+ for (int i = 0; i < glyphIndices.Count; i++)
+ {
+ glyphSpan[i] = glyphIndices[i];
+ }
+ }
}
- public override Span GlyphIndices => _buffer.GetGlyphSpan();
-
- public Span GlyphPositions => MemoryMarshal.Cast(_buffer.GetPositionSpan());
+ return new GlyphRunImpl(builder.Build());
}
}
}
diff --git a/src/Skia/Avalonia.Skia/SKPaintCache.cs b/src/Skia/Avalonia.Skia/SKPaintCache.cs
new file mode 100644
index 0000000000..6588ab8da8
--- /dev/null
+++ b/src/Skia/Avalonia.Skia/SKPaintCache.cs
@@ -0,0 +1,76 @@
+using System.Collections.Concurrent;
+using SkiaSharp;
+
+namespace Avalonia.Skia
+{
+ ///
+ /// Cache for SKPaints.
+ ///
+ internal static class SKPaintCache
+ {
+ private static ConcurrentBag s_cachedPaints;
+
+ static SKPaintCache()
+ {
+ s_cachedPaints = new ConcurrentBag();
+ }
+
+ ///
+ /// Gets a SKPaint for usage.
+ ///
+ ///
+ /// If a SKPaint is in the cache, that existing SKPaint will be returned.
+ /// Otherwise a new SKPaint will be created.
+ ///
+ ///
+ public static SKPaint Get()
+ {
+ if (!s_cachedPaints.TryTake(out var paint))
+ {
+ paint = new SKPaint();
+ }
+
+ return paint;
+ }
+
+ ///
+ /// Returns a SKPaint for reuse later.
+ ///
+ ///
+ /// Do not use the paint further.
+ /// Do not return the same paint multiple times as that will break the cache.
+ ///
+ ///
+ public static void Return(SKPaint paint)
+ {
+ s_cachedPaints.Add(paint);
+ }
+
+ ///
+ /// Returns a SKPaint and resets it for reuse later.
+ ///
+ ///
+ /// Do not use the paint further.
+ /// Do not return the same paint multiple times as that will break the cache.
+ /// Uses SKPaint.Reset() for reuse later.
+ ///
+ ///
+ public static void ReturnReset(SKPaint paint)
+ {
+ paint.Reset();
+ s_cachedPaints.Add(paint);
+ }
+
+ ///
+ /// Clears and disposes all cached paints.
+ ///
+ public static void Clear()
+ {
+ while (s_cachedPaints.TryTake(out var paint))
+ {
+ paint.Dispose();
+ }
+ }
+
+ }
+}
diff --git a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
index 01b7449b64..d3231c92a5 100644
--- a/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
+++ b/src/Skia/Avalonia.Skia/SurfaceRenderTarget.cs
@@ -116,20 +116,20 @@ namespace Avalonia.Skia
public int Version { get; private set; } = 1;
///
- public void Save(string fileName)
+ public void Save(string fileName, int? quality = null)
{
using (var image = SnapshotImage())
{
- ImageSavingHelper.SaveImage(image, fileName);
+ ImageSavingHelper.SaveImage(image, fileName, quality);
}
}
///
- public void Save(Stream stream)
+ public void Save(Stream stream, int? quality = null)
{
using (var image = SnapshotImage())
{
- ImageSavingHelper.SaveImage(image, stream);
+ ImageSavingHelper.SaveImage(image, stream, quality);
}
}
diff --git a/src/Skia/Avalonia.Skia/TextShaperImpl.cs b/src/Skia/Avalonia.Skia/TextShaperImpl.cs
index b07deb1f4d..eaf588c27d 100644
--- a/src/Skia/Avalonia.Skia/TextShaperImpl.cs
+++ b/src/Skia/Avalonia.Skia/TextShaperImpl.cs
@@ -60,11 +60,11 @@ namespace Avalonia.Skia
var glyphCluster = (int)(sourceInfo.Cluster);
- var glyphAdvance = GetGlyphAdvance(glyphPositions, i, textScale);
+ var glyphAdvance = GetGlyphAdvance(glyphPositions, i, textScale) + options.LetterSpacing;
var glyphOffset = GetGlyphOffset(glyphPositions, i, textScale);
- if(glyphIndex == 0 && text.Buffer.Span[glyphCluster] == '\t')
+ if(text.Buffer.Span[glyphCluster] == '\t')
{
glyphIndex = typeface.GetGlyph(' ');
diff --git a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
index 80bfcc5973..d437f514bb 100644
--- a/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
+++ b/src/Skia/Avalonia.Skia/WriteableBitmapImpl.cs
@@ -137,20 +137,20 @@ namespace Avalonia.Skia
}
///
- public void Save(Stream stream)
+ public void Save(Stream stream, int? quality = null)
{
using (var image = GetSnapshot())
{
- ImageSavingHelper.SaveImage(image, stream);
+ ImageSavingHelper.SaveImage(image, stream, quality);
}
}
///
- public void Save(string fileName)
+ public void Save(string fileName, int? quality = null)
{
using (var image = GetSnapshot())
{
- ImageSavingHelper.SaveImage(image, fileName);
+ ImageSavingHelper.SaveImage(image, fileName, quality);
}
}
diff --git a/src/Web/Avalonia.Web/Avalonia.Web.csproj b/src/Web/Avalonia.Web/Avalonia.Web.csproj
index cdfa095865..88b23cdad2 100644
--- a/src/Web/Avalonia.Web/Avalonia.Web.csproj
+++ b/src/Web/Avalonia.Web/Avalonia.Web.csproj
@@ -7,6 +7,7 @@
+
diff --git a/src/Web/Avalonia.Web/Avalonia.Web.props b/src/Web/Avalonia.Web/Avalonia.Web.props
index 6c975cd284..668dd20789 100644
--- a/src/Web/Avalonia.Web/Avalonia.Web.props
+++ b/src/Web/Avalonia.Web/Avalonia.Web.props
@@ -1,5 +1,5 @@
-
+
- $(EmccExtraLDFlags) --js-library="$(MSBuildThisFileDirectory)\interop.js"
+ 16384000
diff --git a/src/Web/Avalonia.Web/Avalonia.Web.targets b/src/Web/Avalonia.Web/Avalonia.Web.targets
index d1bec2aa93..22363b33d8 100644
--- a/src/Web/Avalonia.Web/Avalonia.Web.targets
+++ b/src/Web/Avalonia.Web/Avalonia.Web.targets
@@ -4,4 +4,34 @@
+
+
+ True
+ $(EmccExtraLDFlags) --js-library="$(MSBuildThisFileDirectory)\interop.js"
+ $(EmccExtraLDFlags) -sERROR_ON_UNDEFINED_SYMBOLS=0
+ true
+
+
+
+ true
+ full
+ true
+ -Oz
+ -Oz
+ false
+ false
+ 0
+ false
+ true
+ false
+ false
+ false
+ false
+ false
+ false
+ true
+ true
+ en
+ false
+
diff --git a/src/Web/Avalonia.Web/AvaloniaView.cs b/src/Web/Avalonia.Web/AvaloniaView.cs
index 12d31258b5..37614399ee 100644
--- a/src/Web/Avalonia.Web/AvaloniaView.cs
+++ b/src/Web/Avalonia.Web/AvaloniaView.cs
@@ -1,5 +1,9 @@
using System;
+using System.Collections.Generic;
+using System.Reflection;
using System.Runtime.InteropServices.JavaScript;
+
+using Avalonia.Collections.Pooled;
using Avalonia.Controls;
using Avalonia.Controls.Embedding;
using Avalonia.Controls.Platform;
@@ -18,6 +22,7 @@ namespace Avalonia.Web
[System.Runtime.Versioning.SupportedOSPlatform("browser")] // gets rid of callsite warnings
public partial class AvaloniaView : ITextInputMethodImpl
{
+ private static readonly PooledList s_intermediatePointsPooledList = new(ClearMode.Never);
private readonly BrowserTopLevelImpl _topLevelImpl;
private EmbeddableControlRoot _topLevel;
@@ -52,13 +57,13 @@ namespace Avalonia.Web
}
_containerElement = hostContent.GetPropertyAsJSObject("host")
- ?? throw new InvalidOperationException("Host cannot be null");
+ ?? throw new InvalidOperationException("Host cannot be null");
_canvas = hostContent.GetPropertyAsJSObject("canvas")
- ?? throw new InvalidOperationException("Canvas cannot be null");
+ ?? throw new InvalidOperationException("Canvas cannot be null");
_nativeControlsContainer = hostContent.GetPropertyAsJSObject("nativeHost")
- ?? throw new InvalidOperationException("NativeHost cannot be null");
+ ?? throw new InvalidOperationException("NativeHost cannot be null");
_inputElement = hostContent.GetPropertyAsJSObject("inputElement")
- ?? throw new InvalidOperationException("InputElement cannot be null");
+ ?? throw new InvalidOperationException("InputElement cannot be null");
_splash = DomHelper.GetElementById("avalonia-splash");
@@ -77,8 +82,7 @@ namespace Avalonia.Web
_topLevelImpl.SetCssCursor = (cursor) =>
{
- InputHelper.SetCursor(_containerElement, cursor); // macOS
- InputHelper.SetCursor(_canvas, cursor); // windows
+ InputHelper.SetCursor(_containerElement, cursor);
};
_topLevel.Prepare();
@@ -97,7 +101,8 @@ namespace Avalonia.Web
OnCompositionUpdate,
OnCompositionEnd);
- InputHelper.SubscribePointerEvents(_containerElement, OnPointerMove, OnPointerDown, OnPointerUp, OnWheel);
+ InputHelper.SubscribePointerEvents(_containerElement, OnPointerMove, OnPointerDown, OnPointerUp,
+ OnPointerCancel, OnWheel);
var skiaOptions = AvaloniaLocator.Current.GetService();
@@ -118,7 +123,12 @@ namespace Avalonia.Web
_context.SetResourceCacheLimit(skiaOptions?.MaxGpuResourceSizeBytes ?? 32 * 1024 * 1024);
}
- _topLevelImpl.Surfaces = new[] { new BrowserSkiaSurface(_context, _jsGlInfo, ColorType, new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi, GRSurfaceOrigin.BottomLeft) };
+ _topLevelImpl.Surfaces = new[]
+ {
+ new BrowserSkiaSurface(_context, _jsGlInfo, ColorType,
+ new PixelSize((int)_canvasSize.Width, (int)_canvasSize.Height), _dpi,
+ GRSurfaceOrigin.BottomLeft)
+ };
}
else
{
@@ -136,7 +146,7 @@ namespace Avalonia.Web
DomHelper.ObserveSize(host, null, OnSizeChanged);
CanvasHelper.RequestAnimationFrame(_canvas, true);
-
+
InputHelper.FocusElement(_containerElement);
}
@@ -156,17 +166,36 @@ namespace Avalonia.Web
private bool OnPointerMove(JSObject args)
{
- var type = args.GetPropertyAsString("pointertype");
-
+ var pointerType = args.GetPropertyAsString("pointerType");
var point = ExtractRawPointerFromJSArgs(args);
+ var type = pointerType switch
+ {
+ "touch" => RawPointerEventType.TouchUpdate,
+ _ => RawPointerEventType.Move
+ };
- return _topLevelImpl.RawPointerEvent(RawPointerEventType.Move, type!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"));
+ var coalescedEvents = new Lazy?>(() =>
+ {
+ var points = InputHelper.GetCoalescedEvents(args);
+ s_intermediatePointsPooledList.Clear();
+ s_intermediatePointsPooledList.Capacity = points.Length - 1;
+
+ // Skip the last one, as it is already processed point.
+ for (var i = 0; i < points.Length - 1; i++)
+ {
+ var point = points[i];
+ s_intermediatePointsPooledList.Add(ExtractRawPointerFromJSArgs(point));
+ }
+
+ return s_intermediatePointsPooledList;
+ });
+
+ return _topLevelImpl.RawPointerEvent(type, pointerType!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"), coalescedEvents);
}
private bool OnPointerDown(JSObject args)
{
- var pointerType = args.GetPropertyAsString("pointerType");
-
+ var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse";
var type = pointerType switch
{
"touch" => RawPointerEventType.TouchBegin,
@@ -177,20 +206,18 @@ namespace Avalonia.Web
2 => RawPointerEventType.RightButtonDown,
3 => RawPointerEventType.XButton1Down,
4 => RawPointerEventType.XButton2Down,
- // 5 => Pen eraser button,
+ 5 => RawPointerEventType.XButton1Down, // should be pen eraser button,
_ => RawPointerEventType.Move
}
};
var point = ExtractRawPointerFromJSArgs(args);
-
- return _topLevelImpl.RawPointerEvent(type, pointerType!, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"));
+ return _topLevelImpl.RawPointerEvent(type, pointerType, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"));
}
private bool OnPointerUp(JSObject args)
{
var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse";
-
var type = pointerType switch
{
"touch" => RawPointerEventType.TouchEnd,
@@ -201,15 +228,27 @@ namespace Avalonia.Web
2 => RawPointerEventType.RightButtonUp,
3 => RawPointerEventType.XButton1Up,
4 => RawPointerEventType.XButton2Up,
- // 5 => Pen eraser button,
+ 5 => RawPointerEventType.XButton1Up, // should be pen eraser button,
_ => RawPointerEventType.Move
}
};
var point = ExtractRawPointerFromJSArgs(args);
-
return _topLevelImpl.RawPointerEvent(type, pointerType, point, GetModifiers(args), args.GetPropertyAsInt32("pointerId"));
}
+
+ private bool OnPointerCancel(JSObject args)
+ {
+ var pointerType = args.GetPropertyAsString("pointerType") ?? "mouse";
+ if (pointerType == "touch")
+ {
+ var point = ExtractRawPointerFromJSArgs(args);
+ _topLevelImpl.RawPointerEvent(RawPointerEventType.TouchCancel, pointerType, point,
+ GetModifiers(args), args.GetPropertyAsInt32("pointerId"));
+ }
+
+ return false;
+ }
private bool OnWheel(JSObject args)
{
@@ -254,7 +293,14 @@ namespace Avalonia.Web
private bool OnKeyDown (string code, string key, int modifier)
{
- return _topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyDown, code, key, (RawInputModifiers)modifier);
+ var handled = _topLevelImpl.RawKeyboardEvent(RawKeyEventType.KeyDown, code, key, (RawInputModifiers)modifier);
+
+ if (!handled && key.Length == 1)
+ {
+ handled = _topLevelImpl.RawTextEvent(key);
+ }
+
+ return handled;
}
private bool OnKeyUp(string code, string key, int modifier)
diff --git a/src/Web/Avalonia.Web/BrowserRuntimePlatform.cs b/src/Web/Avalonia.Web/BrowserRuntimePlatform.cs
new file mode 100644
index 0000000000..ebcd3a9921
--- /dev/null
+++ b/src/Web/Avalonia.Web/BrowserRuntimePlatform.cs
@@ -0,0 +1,26 @@
+using System;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.JavaScript;
+using System.Text.RegularExpressions;
+using Avalonia.Platform;
+using Avalonia.Web.Interop;
+
+namespace Avalonia.Web;
+
+internal class BrowserRuntimePlatform : StandardRuntimePlatform
+{
+ private static readonly Lazy Info = new(() =>
+ {
+ var result = new RuntimePlatformInfo
+ {
+ IsCoreClr = true, // WASM browser is always CoreCLR
+ IsBrowser = true, // BrowserRuntimePlatform only runs on Browser.
+ OperatingSystem = OperatingSystemType.Browser,
+ IsMobile = AvaloniaModule.IsMobile()
+ };
+
+ return result;
+ });
+
+ public override RuntimePlatformInfo GetRuntimeInfo() => Info.Value;
+}
diff --git a/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs b/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs
index 00ed961fbe..0dcc474f76 100644
--- a/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs
+++ b/src/Web/Avalonia.Web/BrowserSingleViewLifetime.cs
@@ -3,6 +3,7 @@ using Avalonia.Controls;
using Avalonia.Controls.ApplicationLifetimes;
using Avalonia.Web.Skia;
using System.Runtime.Versioning;
+using Avalonia.Platform;
namespace Avalonia.Web;
@@ -23,7 +24,6 @@ public class BrowserPlatformOptions
public Func FrameworkAssetPathResolver { get; set; } = new(fileName => $"./{fileName}");
}
-
[SupportedOSPlatform("browser")]
public static class WebAppBuilder
{
diff --git a/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs b/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs
index b955da6df2..ed8f417870 100644
--- a/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs
+++ b/src/Web/Avalonia.Web/BrowserTopLevelImpl.cs
@@ -67,17 +67,22 @@ namespace Avalonia.Web
public bool RawPointerEvent(
RawPointerEventType eventType, string pointerType,
- RawPointerPoint p, RawInputModifiers modifiers, long touchPointId)
+ RawPointerPoint p, RawInputModifiers modifiers, long touchPointId,
+ Lazy?>? intermediatePoints = null)
{
if (_inputRoot is { }
&& Input is { } input)
{
var device = GetPointerDevice(pointerType);
var args = device is TouchDevice ?
- new RawTouchEventArgs(device, Timestamp, _inputRoot, eventType, p, modifiers, touchPointId) :
+ new RawTouchEventArgs(device, Timestamp, _inputRoot, eventType, p, modifiers, touchPointId)
+ {
+ IntermediatePoints = intermediatePoints
+ } :
new RawPointerEventArgs(device, Timestamp, _inputRoot, eventType, p, modifiers)
{
- RawPointerId = touchPointId
+ RawPointerId = touchPointId,
+ IntermediatePoints = intermediatePoints
};
input.Invoke(args);
diff --git a/src/Web/Avalonia.Web/Interop/AvaloniaModule.cs b/src/Web/Avalonia.Web/Interop/AvaloniaModule.cs
index 176b8d60fc..0e54deb515 100644
--- a/src/Web/Avalonia.Web/Interop/AvaloniaModule.cs
+++ b/src/Web/Avalonia.Web/Interop/AvaloniaModule.cs
@@ -3,7 +3,7 @@ using System.Threading.Tasks;
namespace Avalonia.Web.Interop;
-internal static class AvaloniaModule
+internal static partial class AvaloniaModule
{
public const string MainModuleName = "avalonia";
public const string StorageModuleName = "storage";
@@ -19,4 +19,7 @@ internal static class AvaloniaModule
var options = AvaloniaLocator.Current.GetService() ?? new BrowserPlatformOptions();
return JSHost.ImportAsync(StorageModuleName, options.FrameworkAssetPathResolver("storage.js"));
}
+
+ [JSImport("Caniuse.isMobile", AvaloniaModule.MainModuleName)]
+ public static partial bool IsMobile();
}
diff --git a/src/Web/Avalonia.Web/Interop/CanvasHelper.cs b/src/Web/Avalonia.Web/Interop/CanvasHelper.cs
index efa94916fa..5bbe503bc1 100644
--- a/src/Web/Avalonia.Web/Interop/CanvasHelper.cs
+++ b/src/Web/Avalonia.Web/Interop/CanvasHelper.cs
@@ -33,7 +33,7 @@ internal static partial class CanvasHelper
public static partial void RequestAnimationFrame(JSObject canvas, bool renderLoop);
[JSImport("Canvas.setCanvasSize", AvaloniaModule.MainModuleName)]
- public static partial void SetCanvasSize(JSObject canvas, int height, int width);
+ public static partial void SetCanvasSize(JSObject canvas, int width, int height);
[JSImport("Canvas.initGL", AvaloniaModule.MainModuleName)]
private static partial JSObject InitGL(
diff --git a/src/Web/Avalonia.Web/Interop/InputHelper.cs b/src/Web/Avalonia.Web/Interop/InputHelper.cs
index cfec9f30dc..904fa915a8 100644
--- a/src/Web/Avalonia.Web/Interop/InputHelper.cs
+++ b/src/Web/Avalonia.Web/Interop/InputHelper.cs
@@ -1,4 +1,5 @@
using System;
+using System.Runtime.InteropServices;
using System.Runtime.InteropServices.JavaScript;
using System.Threading.Tasks;
@@ -36,6 +37,8 @@ internal static partial class InputHelper
[JSMarshalAs>]
Func pointerUp,
[JSMarshalAs>]
+ Func pointerCancel,
+ [JSMarshalAs>]
Func wheel);
@@ -45,6 +48,9 @@ internal static partial class InputHelper
[JSMarshalAs>]
Func input);
+ [JSImport("InputHelper.getCoalescedEvents", AvaloniaModule.MainModuleName)]
+ [return: JSMarshalAs>]
+ public static partial JSObject[] GetCoalescedEvents(JSObject pointerEvent);
[JSImport("InputHelper.clearInput", AvaloniaModule.MainModuleName)]
public static partial void ClearInputElement(JSObject htmlElement);
diff --git a/src/Web/Avalonia.Web/WindowingPlatform.cs b/src/Web/Avalonia.Web/WindowingPlatform.cs
index 7c2a84516b..828964afa7 100644
--- a/src/Web/Avalonia.Web/WindowingPlatform.cs
+++ b/src/Web/Avalonia.Web/WindowingPlatform.cs
@@ -8,7 +8,7 @@ using Avalonia.Threading;
namespace Avalonia.Web
{
- internal class BrowserWindowingPlatform : IWindowingPlatform, IPlatformSettings, IPlatformThreadingInterface
+ internal class BrowserWindowingPlatform : IWindowingPlatform, IPlatformThreadingInterface
{
private bool _signaled;
private static KeyboardDevice? s_keyboard;
@@ -31,12 +31,14 @@ namespace Avalonia.Web
public static void Register()
{
var instance = new BrowserWindowingPlatform();
+
s_keyboard = new KeyboardDevice();
AvaloniaLocator.CurrentMutable
+ .Bind().ToSingleton()
.Bind().ToSingleton()
.Bind().ToSingleton()
.Bind().ToConstant(s_keyboard)
- .Bind().ToConstant(instance)
+ .Bind().ToSingleton()
.Bind().ToConstant(instance)
.Bind().ToConstant(new RenderLoop())
.Bind().ToConstant(ManualTriggerRenderTimer.Instance)
@@ -45,13 +47,6 @@ namespace Avalonia.Web
.Bind().ToSingleton();
}
- public Size DoubleClickSize { get; } = new Size(2, 2);
-
- public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500);
-
- public Size TouchDoubleClickSize => new Size(16, 16);
-
- public TimeSpan TouchDoubleClickTime => DoubleClickTime;
public void RunLoop(CancellationToken cancellationToken)
{
throw new NotSupportedException();
@@ -71,11 +66,11 @@ namespace Avalonia.Web
{
if (_signaled)
return;
-
+
_signaled = true;
-
+
IDisposable? disp = null;
-
+
disp = GetRuntimePlatform()
.StartSystemTimer(TimeSpan.FromMilliseconds(1),
() =>
diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/caniuse.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/caniuse.ts
index 6dedcb724f..e019f92113 100644
--- a/src/Web/Avalonia.Web/webapp/modules/avalonia/caniuse.ts
+++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/caniuse.ts
@@ -1,13 +1,25 @@
export class Caniuse {
public static canShowOpenFilePicker(): boolean {
- return typeof window.showOpenFilePicker !== "undefined";
+ return typeof globalThis.showOpenFilePicker !== "undefined";
}
public static canShowSaveFilePicker(): boolean {
- return typeof window.showSaveFilePicker !== "undefined";
+ return typeof globalThis.showSaveFilePicker !== "undefined";
}
public static canShowDirectoryPicker(): boolean {
- return typeof window.showDirectoryPicker !== "undefined";
+ return typeof globalThis.showDirectoryPicker !== "undefined";
+ }
+
+ public static isMobile(): boolean {
+ const userAgentData = (globalThis.navigator as any)?.userAgentData;
+ if (userAgentData) {
+ return userAgentData.mobile;
+ }
+
+ const userAgent = navigator.userAgent;
+ const regex1 = /(android|bb\d+|meego).+mobile|avantgo|bada\/|blackberry|blazer|compal|elaine|fennec|hiptop|iemobile|ip(hone|od)|iris|kindle|lge |maemo|midp|mmp|mobile.+firefox|netfront|opera m(ob|in)i|palm( os)?|phone|p(ixi|re)\/|plucker|pocket|psp|series(4|6)0|symbian|treo|up\.(browser|link)|vodafone|wap|windows ce|xda|xiino/i;
+ const regex2 = /1207|6310|6590|3gso|4thp|50[1-6]i|770s|802s|a wa|abac|ac(er|oo|s)|ai(ko|rn)|al(av|ca|co)|amoi|an(ex|ny|yw)|aptu|ar(ch|go)|as(te|us)|attw|au(di|m|r |s )|avan|be(ck|ll|nq)|bi(lb|rd)|bl(ac|az)|br(e|v)w|bumb|bw(n|u)|c55\/|capi|ccwa|cdm|cell|chtm|cldc|cmd|co(mp|nd)|craw|da(it|ll|ng)|dbte|dcs|devi|dica|dmob|do(c|p)o|ds(12|d)|el(49|ai)|em(l2|ul)|er(ic|k0)|esl8|ez([4-7]0|os|wa|ze)|fetc|fly(|_)|g1 u|g560|gene|gf5|gmo|go(\.w|od)|gr(ad|un)|haie|hcit|hd(m|p|t)|hei|hi(pt|ta)|hp( i|ip)|hsc|ht(c(| |_|a|g|p|s|t)|tp)|hu(aw|tc)|i(20|go|ma)|i230|iac( ||\/)|ibro|idea|ig01|ikom|im1k|inno|ipaq|iris|ja(t|v)a|jbro|jemu|jigs|kddi|keji|kgt( |\/)|klon|kpt |kwc|kyo(c|k)|le(no|xi)|lg( g|\/(k|l|u)|50|54|[a-w])|libw|lynx|m1w|m3ga|m50\/|ma(te|ui|xo)|mc(01|21|ca)|mcr|me(rc|ri)|mi(o8|oa|ts)|mmef|mo(01|02|bi|de|do|t(| |o|v)|zz)|mt(50|p1|v )|mwbp|mywa|n10[0-2]|n20[2-3]|n30(0|2)|n50(0|2|5)|n7(0(0|1)|10)|ne((c|m)|on|tf|wf|wg|wt)|nok(6|i)|nzph|o2im|op(ti|wv)|oran|owg1|p800|pan(a|d|t)|pdxg|pg(13|([1-8]|c))|phil|pire|pl(ay|uc)|pn2|po(ck|rt|se)|prox|psio|ptg|qaa|qc(07|12|21|32|60|[2-7]|i)|qtek|r380|r600|raks|rim9|ro(ve|zo)|s55\/|sa(ge|ma|mm|ms|ny|va)|sc(01|h|oo|p)|sdk\/|se(c(|0|1)|47|mc|nd|ri)|sgh|shar|sie(|m)|sk0|sl(45|id)|sm(al|ar|b3|it|t5)|so(ft|ny)|sp(01|h|v|v )|sy(01|mb)|t2(18|50)|t6(00|10|18)|ta(gt|lk)|tcl|tdg|tel(i|m)|tim|tmo|to(pl|sh)|ts(70|m|m3|m5)|tx9|up(\.b|g1|si)|utst|v400|v750|veri|vi(rg|te)|vk(40|5[0-3]|v)|vm40|voda|vulc|vx(52|53|60|61|70|80|81|83|85|98)|w3c(| )|webc|whit|wi(g |nc|nw)|wmlb|wonu|x700|yas|your|zeto|zte/i;
+ return regex1.test(userAgent) || regex2.test(userAgent.substr(0, 4));
}
}
diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts
index 9ae9b3d2a8..47c501cbb7 100644
--- a/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts
+++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/canvas.ts
@@ -106,12 +106,6 @@ export class Canvas {
// add the draw to the next frame
this.renderLoopRequest = window.requestAnimationFrame(() => {
- if (this.glInfo) {
- const GL = (globalThis as any).AvaloniaGL;
- // make current
- GL.makeContextCurrent(this.glInfo.context);
- }
-
if (this.htmlCanvas.width !== this.newWidth) {
this.htmlCanvas.width = this.newWidth ?? 0;
}
@@ -131,6 +125,11 @@ export class Canvas {
}
public setCanvasSize(width: number, height: number): void {
+ if (this.renderLoopRequest !== 0) {
+ window.cancelAnimationFrame(this.renderLoopRequest);
+ this.renderLoopRequest = 0;
+ }
+
this.newWidth = width;
this.newHeight = height;
@@ -142,11 +141,7 @@ export class Canvas {
this.htmlCanvas.height = this.newHeight;
}
- if (this.glInfo) {
- const GL = (globalThis as any).AvaloniaGL;
- // make current
- GL.makeContextCurrent(this.glInfo.context);
- }
+ this.requestAnimationFrame();
}
public static setCanvasSize(element: HTMLCanvasElement, width: number, height: number): void {
@@ -210,23 +205,25 @@ interface SizeWatcherInstance {
export class SizeWatcher {
static observer: ResizeObserver;
static elements: Map;
+ private static lastMove: number;
public static observe(element: HTMLElement, elementId: string | undefined, callback: (width: number, height: number) => void): void {
if (!element || !callback) {
return;
}
- SizeWatcher.init();
+ SizeWatcher.lastMove = Date.now();
- const watcherElement = element as SizeWatcherElement;
- watcherElement.SizeWatcher = {
- callback
- };
+ callback(element.clientWidth, element.clientHeight);
- SizeWatcher.elements.set(elementId ?? element.id, element);
- SizeWatcher.observer.observe(element);
+ const handleResize = (args: UIEvent) => {
+ if (Date.now() - SizeWatcher.lastMove > 33) {
+ callback(element.clientWidth, element.clientHeight);
+ SizeWatcher.lastMove = Date.now();
+ }
+ };
- SizeWatcher.invoke(element);
+ window.addEventListener("resize", handleResize);
}
public static unobserve(elementId: string): void {
diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts
index 783710bb3e..385cdd4c41 100644
--- a/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts
+++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/dom.ts
@@ -19,12 +19,11 @@ export class AvaloniaDOM {
canvas.classList.add("avalonia-canvas");
canvas.style.backgroundColor = "#ccc";
canvas.style.width = "100%";
- canvas.style.height = "100%";
canvas.style.position = "absolute";
// Native controls host
const nativeHost = document.createElement("div");
- canvas.id = `nativeHost${randomIdPart}`;
+ nativeHost.id = `nativeHost${randomIdPart}`;
nativeHost.classList.add("avalonia-native-host");
nativeHost.style.left = "0px";
nativeHost.style.top = "0px";
@@ -34,7 +33,7 @@ export class AvaloniaDOM {
// IME
const inputElement = document.createElement("input");
- canvas.id = `input${randomIdPart}`;
+ inputElement.id = `inputElement${randomIdPart}`;
inputElement.classList.add("avalonia-input-element");
inputElement.autocapitalize = "none";
inputElement.type = "text";
diff --git a/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts b/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts
index 768414ccab..83e8ee7f1c 100644
--- a/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts
+++ b/src/Web/Avalonia.Web/webapp/modules/avalonia/input.ts
@@ -95,41 +95,45 @@ export class InputHelper {
pointerMoveCallback: (args: PointerEvent) => boolean,
pointerDownCallback: (args: PointerEvent) => boolean,
pointerUpCallback: (args: PointerEvent) => boolean,
+ pointerCancelCallback: (args: PointerEvent) => boolean,
wheelCallback: (args: WheelEvent) => boolean
) {
const pointerMoveHandler = (args: PointerEvent) => {
- if (pointerMoveCallback(args)) {
- args.preventDefault();
- }
+ pointerMoveCallback(args);
+ args.preventDefault();
};
const pointerDownHandler = (args: PointerEvent) => {
- if (pointerDownCallback(args)) {
- args.preventDefault();
- }
+ pointerDownCallback(args);
+ args.preventDefault();
};
const pointerUpHandler = (args: PointerEvent) => {
- if (pointerUpCallback(args)) {
- args.preventDefault();
- }
+ pointerUpCallback(args);
+ args.preventDefault();
+ };
+
+ const pointerCancelHandler = (args: PointerEvent) => {
+ pointerCancelCallback(args);
+ args.preventDefault();
};
const wheelHandler = (args: WheelEvent) => {
- if (wheelCallback(args)) {
- args.preventDefault();
- }
+ wheelCallback(args);
+ args.preventDefault();
};
element.addEventListener("pointermove", pointerMoveHandler);
element.addEventListener("pointerdown", pointerDownHandler);
element.addEventListener("pointerup", pointerUpHandler);
element.addEventListener("wheel", wheelHandler);
+ element.addEventListener("pointercancel", pointerCancelHandler);
return () => {
element.removeEventListener("pointerover", pointerMoveHandler);
element.removeEventListener("pointerdown", pointerDownHandler);
element.removeEventListener("pointerup", pointerUpHandler);
+ element.removeEventListener("pointercancel", pointerCancelHandler);
element.removeEventListener("wheel", wheelHandler);
};
}
@@ -150,6 +154,10 @@ export class InputHelper {
};
}
+ public static getCoalescedEvents(pointerEvent: PointerEvent): PointerEvent[] {
+ return pointerEvent.getCoalescedEvents();
+ }
+
public static clearInput(inputElement: HTMLInputElement) {
inputElement.value = "";
}
@@ -159,7 +167,11 @@ export class InputHelper {
}
public static setCursor(inputElement: HTMLInputElement, kind: string) {
- inputElement.style.cursor = kind;
+ if (kind === "pointer") {
+ inputElement.style.removeProperty("cursor");
+ } else {
+ inputElement.style.cursor = kind;
+ }
}
public static setBounds(inputElement: HTMLInputElement, x: number, y: number, caretWidth: number, caretHeight: number, caret: number) {
diff --git a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
index 8103f89dad..4d307c9762 100644
--- a/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
+++ b/src/Windows/Avalonia.Direct2D1/Direct2D1Platform.cs
@@ -10,10 +10,7 @@ using Avalonia.Media.Imaging;
using Avalonia.Platform;
using SharpDX.DirectWrite;
using GlyphRun = Avalonia.Media.GlyphRun;
-using TextAlignment = Avalonia.Media.TextAlignment;
using SharpDX.Mathematics.Interop;
-using System.Runtime.InteropServices;
-using System.Drawing;
namespace Avalonia
{
@@ -160,6 +157,72 @@ namespace Avalonia.Direct2D1
public IGeometryImpl CreateGeometryGroup(FillRule fillRule, IReadOnlyList children) => new GeometryGroupImpl(fillRule, children);
public IGeometryImpl CreateCombinedGeometry(GeometryCombineMode combineMode, Geometry g1, Geometry g2) => new CombinedGeometryImpl(combineMode, g1, g2);
+ public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices,
+ IReadOnlyList glyphAdvances, IReadOnlyList glyphOffsets)
+ {
+ var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface;
+
+ var glyphCount = glyphIndices.Count;
+
+ var run = new SharpDX.DirectWrite.GlyphRun
+ {
+ FontFace = glyphTypefaceImpl.FontFace,
+ FontSize = (float)fontRenderingEmSize
+ };
+
+ var indices = new short[glyphCount];
+
+ for (var i = 0; i < glyphCount; i++)
+ {
+ indices[i] = (short)glyphIndices[i];
+ }
+
+ run.Indices = indices;
+
+ run.Advances = new float[glyphCount];
+
+ var scale = (float)(fontRenderingEmSize / glyphTypeface.Metrics.DesignEmHeight);
+
+ if (glyphAdvances == null)
+ {
+ for (var i = 0; i < glyphCount; i++)
+ {
+ var advance = glyphTypeface.GetGlyphAdvance(glyphIndices[i]) * scale;
+
+ run.Advances[i] = advance;
+ }
+ }
+ else
+ {
+ for (var i = 0; i < glyphCount; i++)
+ {
+ var advance = (float)glyphAdvances[i];
+
+ run.Advances[i] = advance;
+ }
+ }
+
+ if (glyphOffsets == null)
+ {
+ return new GlyphRunImpl(run);
+ }
+
+ run.Offsets = new GlyphOffset[glyphCount];
+
+ for (var i = 0; i < glyphCount; i++)
+ {
+ var (x, y) = glyphOffsets[i];
+
+ run.Offsets[i] = new GlyphOffset
+ {
+ AdvanceOffset = (float)x,
+ AscenderOffset = (float)y
+ };
+ }
+
+ return new GlyphRunImpl(run);
+ }
+
public IGeometryImpl BuildGlyphRunGeometry(GlyphRun glyphRun)
{
if (glyphRun.GlyphTypeface is not GlyphTypefaceImpl glyphTypeface)
@@ -260,68 +323,6 @@ namespace Avalonia.Direct2D1
return new WicBitmapImpl(format, alphaFormat, data, size, dpi, stride);
}
- private class DWGlyphRunBuffer : IGlyphRunBuffer
- {
- protected readonly SharpDX.DirectWrite.GlyphRun _dwRun;
-
- public DWGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
- {
- var glyphTypefaceImpl = (GlyphTypefaceImpl)glyphTypeface;
-
- _dwRun = new SharpDX.DirectWrite.GlyphRun
- {
- FontFace = glyphTypefaceImpl.FontFace,
- FontSize = fontRenderingEmSize,
- Indices = new short[length]
- };
- }
-
- public Span GlyphIndices => MemoryMarshal.Cast(_dwRun.Indices.AsSpan());
-
- public IGlyphRunImpl Build()
- {
- return new GlyphRunImpl(_dwRun);
- }
- }
-
- private class DWHorizontalGlyphRunBuffer : DWGlyphRunBuffer, IHorizontalGlyphRunBuffer
- {
- public DWHorizontalGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
- : base(glyphTypeface, fontRenderingEmSize, length)
- {
- _dwRun.Advances = new float[length];
- }
-
- public Span GlyphPositions => _dwRun.Advances.AsSpan();
- }
-
- private class DWPositionedGlyphRunBuffer : DWGlyphRunBuffer, IPositionedGlyphRunBuffer
- {
- public DWPositionedGlyphRunBuffer(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
- : base(glyphTypeface, fontRenderingEmSize, length)
- {
- _dwRun.Advances = new float[length];
- _dwRun.Offsets = new GlyphOffset[length];
- }
-
- public Span GlyphPositions => MemoryMarshal.Cast(_dwRun.Offsets.AsSpan());
- }
-
- public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
- {
- return new DWGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
- }
-
- public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
- {
- return new DWHorizontalGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
- }
-
- public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
- {
- return new DWPositionedGlyphRunBuffer(glyphTypeface, fontRenderingEmSize, length);
- }
-
public bool SupportsIndividualRoundRects => false;
public AlphaFormat DefaultAlphaFormat => AlphaFormat.Premul;
diff --git a/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs
index 77d0e58d3d..705c715455 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/GlyphTypefaceImpl.cs
@@ -1,10 +1,11 @@
using System;
-using System.Drawing.Drawing2D;
using Avalonia.Media;
using Avalonia.Metadata;
using HarfBuzzSharp;
using SharpDX.DirectWrite;
using FontMetrics = Avalonia.Media.FontMetrics;
+using FontSimulations = Avalonia.Media.FontSimulations;
+using GlyphMetrics = Avalonia.Media.GlyphMetrics;
namespace Avalonia.Direct2D1.Media
{
@@ -82,6 +83,8 @@ namespace Avalonia.Direct2D1.Media
public int GlyphCount { get; set; }
+ public FontSimulations FontSimulations => FontSimulations.None;
+
///
public ushort GetGlyph(uint codepoint)
{
@@ -135,6 +138,26 @@ namespace Avalonia.Direct2D1.Media
return Font.GetHorizontalGlyphAdvances(glyphIndices);
}
+ public bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics)
+ {
+ metrics = default;
+
+ if (!Font.TryGetGlyphExtents(glyph, out var extents))
+ {
+ return false;
+ }
+
+ metrics = new GlyphMetrics
+ {
+ XBearing = extents.XBearing,
+ YBearing = extents.YBearing,
+ Width = extents.Width,
+ Height = extents.Height
+ };
+
+ return true;
+ }
+
private void Dispose(bool disposing)
{
if (_isDisposed)
diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs
index 843efe2cc4..059105c112 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/BitmapImpl.cs
@@ -15,7 +15,7 @@ namespace Avalonia.Direct2D1.Media
public abstract OptionalDispose GetDirect2DBitmap(SharpDX.Direct2D1.RenderTarget target);
- public void Save(string fileName)
+ public void Save(string fileName, int? quality = null)
{
if (Path.GetExtension(fileName) != ".png")
{
@@ -25,11 +25,11 @@ namespace Avalonia.Direct2D1.Media
using (FileStream s = new FileStream(fileName, FileMode.Create))
{
- Save(s);
+ Save(s, quality);
}
}
- public abstract void Save(Stream stream);
+ public abstract void Save(Stream stream, int? quality = null);
public virtual void Dispose()
{
diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs
index 2656ab4c58..a321b225a0 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DBitmapImpl.cs
@@ -43,7 +43,7 @@ namespace Avalonia.Direct2D1.Media
return new OptionalDispose(_direct2DBitmap, false);
}
- public override void Save(Stream stream)
+ public override void Save(Stream stream, int? quality = null)
{
using (var encoder = new PngBitmapEncoder(Direct2D1Platform.ImagingFactory, stream))
using (var frame = new BitmapFrameEncode(encoder))
diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs
index 357e472d34..c5f8e837ce 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/D2DRenderTargetBitmapImpl.cs
@@ -56,7 +56,7 @@ namespace Avalonia.Direct2D1.Media.Imaging
return new OptionalDispose(_renderTarget.Bitmap, false);
}
- public override void Save(Stream stream)
+ public override void Save(Stream stream, int? quality = null)
{
using (var wic = new WicRenderTargetBitmapImpl(PixelSize, Dpi))
{
diff --git a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
index 1156246b29..051790ef03 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/Imaging/WicBitmapImpl.cs
@@ -186,7 +186,7 @@ namespace Avalonia.Direct2D1.Media
return new OptionalDispose(D2DBitmap.FromWicBitmap(renderTarget, converter), true);
}
- public override void Save(Stream stream)
+ public override void Save(Stream stream, int? quality = null)
{
using (var encoder = new PngBitmapEncoder(Direct2D1Platform.ImagingFactory, stream))
using (var frame = new BitmapFrameEncode(encoder))
diff --git a/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs b/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs
index 064320f809..7f2cbc6182 100644
--- a/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs
+++ b/src/Windows/Avalonia.Direct2D1/Media/TextShaperImpl.cs
@@ -64,7 +64,7 @@ namespace Avalonia.Direct2D1.Media
var glyphOffset = GetGlyphOffset(glyphPositions, i, textScale);
- if (glyphIndex == 0 && text.Buffer.Span[glyphCluster] == '\t')
+ if (text.Buffer.Span[glyphCluster] == '\t')
{
glyphIndex = typeface.GetGlyph(' ');
diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
index 0d29bb91ea..308e22b4e2 100644
--- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
+++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
@@ -11,7 +11,7 @@
-
+
diff --git a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
index 91e79c9670..9b86154043 100644
--- a/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
+++ b/src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
@@ -1083,6 +1083,20 @@ namespace Avalonia.Win32.Interop
public const int SizeOf_BITMAPINFOHEADER = 40;
+ [DllImport("user32.dll", ExactSpelling = true, CharSet = CharSet.Auto, SetLastError = true)]
+ unsafe internal static extern int GetMouseMovePointsEx(
+ uint cbSize, MOUSEMOVEPOINT* pointsIn,
+ MOUSEMOVEPOINT* pointsBufferOut, int nBufPoints, uint resolution);
+
+ [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)] // For GetMouseMovePointsEx
+ public struct MOUSEMOVEPOINT
+ {
+ public int x; //Specifies the x-coordinate of the mouse
+ public int y; //Specifies the x-coordinate of the mouse
+ public int time; //Specifies the time stamp of the mouse coordinate
+ public IntPtr dwExtraInfo; //Specifies extra information associated with this coordinate.
+ }
+
[DllImport("user32.dll", SetLastError = true)]
public static extern bool IsMouseInPointerEnabled();
@@ -1878,7 +1892,10 @@ namespace Avalonia.Win32.Interop
public static uint LGID(IntPtr HKL)
{
- return (uint)(HKL.ToInt32() & 0xffff);
+ unchecked
+ {
+ return (uint)((ulong)HKL & 0xffff);
+ }
}
public const int SORT_DEFAULT = 0;
@@ -2101,6 +2118,12 @@ namespace Avalonia.Win32.Interop
public int Y;
}
+ public struct SIZE_F
+ {
+ public float X;
+ public float Y;
+ }
+
public struct RECT
{
public int left;
diff --git a/src/Windows/Avalonia.Win32/TrayIconImpl.cs b/src/Windows/Avalonia.Win32/TrayIconImpl.cs
index 346d6e5adb..8d565d7fef 100644
--- a/src/Windows/Avalonia.Win32/TrayIconImpl.cs
+++ b/src/Windows/Avalonia.Win32/TrayIconImpl.cs
@@ -216,7 +216,7 @@ namespace Avalonia.Win32
{
Anchor = PopupAnchor.TopLeft,
Gravity = PopupGravity.BottomRight,
- AnchorRectangle = new Rect(Position.ToPoint(1) / Screens.Primary.PixelDensity, new Size(1, 1)),
+ AnchorRectangle = new Rect(Position.ToPoint(1) / Screens.Primary.Scaling, new Size(1, 1)),
Size = finalRect.Size,
ConstraintAdjustment = PopupPositionerConstraintAdjustment.FlipX | PopupPositionerConstraintAdjustment.FlipY,
});
@@ -244,16 +244,16 @@ namespace Avalonia.Win32
{
var point = _hiddenWindow.Screens.Primary.Bounds.TopLeft;
var size = _hiddenWindow.Screens.Primary.Bounds.Size;
- return new Rect(point.X, point.Y, size.Width * _hiddenWindow.Screens.Primary.PixelDensity, size.Height * _hiddenWindow.Screens.Primary.PixelDensity);
+ return new Rect(point.X, point.Y, size.Width * _hiddenWindow.Screens.Primary.Scaling, size.Height * _hiddenWindow.Screens.Primary.Scaling);
}
}
public void MoveAndResize(Point devicePoint, Size virtualSize)
{
- _moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _hiddenWindow.Screens.Primary.PixelDensity);
+ _moveResize(new PixelPoint((int)devicePoint.X, (int)devicePoint.Y), virtualSize, _hiddenWindow.Screens.Primary.Scaling);
}
- public double Scaling => _hiddenWindow.Screens.Primary.PixelDensity;
+ public double Scaling => _hiddenWindow.Screens.Primary.Scaling;
}
}
diff --git a/src/Windows/Avalonia.Win32/Win32Platform.cs b/src/Windows/Avalonia.Win32/Win32Platform.cs
index 3cdc3586dc..18977ec4c3 100644
--- a/src/Windows/Avalonia.Win32/Win32Platform.cs
+++ b/src/Windows/Avalonia.Win32/Win32Platform.cs
@@ -130,17 +130,6 @@ namespace Avalonia.Win32
internal static Compositor Compositor { get; private set; }
- public Size DoubleClickSize => new Size(
- UnmanagedMethods.GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CXDOUBLECLK),
- UnmanagedMethods.GetSystemMetrics(UnmanagedMethods.SystemMetric.SM_CYDOUBLECLK));
-
- public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(UnmanagedMethods.GetDoubleClickTime());
-
- ///
- public Size TouchDoubleClickSize => new Size(16,16);
-
- ///
- public TimeSpan TouchDoubleClickTime => DoubleClickTime;
public static void Initialize()
{
Initialize(new Win32PlatformOptions());
@@ -398,5 +387,25 @@ namespace Avalonia.Win32
SetProcessDPIAware();
}
+
+ Size IPlatformSettings.GetTapSize(PointerType type)
+ {
+ return type switch
+ {
+ PointerType.Touch => new(10, 10),
+ _ => new(GetSystemMetrics(SystemMetric.SM_CXDRAG), GetSystemMetrics(SystemMetric.SM_CYDRAG)),
+ };
+ }
+
+ Size IPlatformSettings.GetDoubleTapSize(PointerType type)
+ {
+ return type switch
+ {
+ PointerType.Touch => new(16, 16),
+ _ => new(GetSystemMetrics(SystemMetric.SM_CXDOUBLECLK), GetSystemMetrics(SystemMetric.SM_CYDOUBLECLK)),
+ };
+ }
+
+ TimeSpan IPlatformSettings.GetDoubleTapTime(PointerType type) => TimeSpan.FromMilliseconds(GetDoubleClickTime());
}
}
diff --git a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs
index 8a41c00add..6da17b8ea5 100644
--- a/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs
+++ b/src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs
@@ -174,8 +174,9 @@ namespace Avalonia.Win32.WinRT.Composition
using var sc = _syncContext.EnsureLocked();
using var desktopTarget = _compositorDesktopInterop.CreateDesktopWindowTarget(hWnd, 0);
using var target = desktopTarget.QueryInterface();
+ using var device2 = _device.QueryInterface();
- using var drawingSurface = _device.CreateDrawingSurface(new UnmanagedMethods.SIZE(), DirectXPixelFormat.B8G8R8A8UIntNormalized,
+ using var drawingSurface = device2.CreateDrawingSurface2(new UnmanagedMethods.SIZE(), DirectXPixelFormat.B8G8R8A8UIntNormalized,
DirectXAlphaMode.Premultiplied);
using var surface = drawingSurface.QueryInterface();
using var surfaceInterop = drawingSurface.QueryInterface();
diff --git a/src/Windows/Avalonia.Win32/WinRT/winrt.idl b/src/Windows/Avalonia.Win32/WinRT/winrt.idl
index 851df9dae6..ffb98fafc8 100644
--- a/src/Windows/Avalonia.Win32/WinRT/winrt.idl
+++ b/src/Windows/Avalonia.Win32/WinRT/winrt.idl
@@ -8,6 +8,7 @@
@clr-map Matrix4x4 System.Numerics.Matrix4x4
@clr-map RECT Avalonia.Win32.Interop.UnmanagedMethods.RECT
@clr-map SIZE Avalonia.Win32.Interop.UnmanagedMethods.SIZE
+@clr-map SIZE_F Avalonia.Win32.Interop.UnmanagedMethods.SIZE_F
@clr-map POINT Avalonia.Win32.Interop.UnmanagedMethods.POINT
@clr-map HWND IntPtr
@clr-map BOOL int
@@ -442,12 +443,18 @@ interface IDesktopWindowContentBridgeInterop : IUnknown
[uuid(FB22C6E1-80A2-4667-9936-DBEAF6EEFE95)]
interface ICompositionGraphicsDevice : IInspectable
{
- HRESULT CreateDrawingSurface([in] SIZE sizePixels, [in] DirectXPixelFormat pixelFormat,
+ HRESULT CreateDrawingSurface([in] SIZE_F sizePixels, [in] DirectXPixelFormat pixelFormat,
[in] DirectXAlphaMode alphaMode, [out] [retval] ICompositionDrawingSurface** result);
HRESULT AddRenderingDeviceReplaced(void* handler, void* token);
HRESULT RemoveRenderingDeviceReplaced([in] int token);
}
+[uuid(0FB8BDF6-C0F0-4BCC-9FB8-084982490D7D)]
+interface ICompositionGraphicsDevice2 : IInspectable
+{
+ HRESULT CreateDrawingSurface2([in] SIZE sizePixels, [in] DirectXPixelFormat pixelFormat, [in] DirectXAlphaMode alphaMode, [out] [retval] ICompositionDrawingSurface** result);
+}
+
[uuid(1527540D-42C7-47A6-A408-668F79A90DFB)]
interface ICompositionSurface : IInspectable
{
@@ -465,7 +472,7 @@ interface ICompositionDrawingSurface : IInspectable
{
[propget] HRESULT GetAlphaMode([out] [retval] DirectXAlphaMode* value);
[propget] HRESULT GetPixelFormat([out] [retval] DirectXPixelFormat* value);
- [propget] HRESULT GetSize([out] [retval] POINT* value);
+ [propget] HRESULT GetSize([out] [retval] SIZE_F* value);
}
enum CompositionBitmapInterpolationMode
diff --git a/src/Windows/Avalonia.Win32/WinScreen.cs b/src/Windows/Avalonia.Win32/WinScreen.cs
index f103cc3b66..1038f41a17 100644
--- a/src/Windows/Avalonia.Win32/WinScreen.cs
+++ b/src/Windows/Avalonia.Win32/WinScreen.cs
@@ -7,7 +7,8 @@ namespace Avalonia.Win32
{
private readonly IntPtr _hMonitor;
- public WinScreen(double pixelDensity, PixelRect bounds, PixelRect workingArea, bool primary, IntPtr hMonitor) : base(pixelDensity, bounds, workingArea, primary)
+ public WinScreen(double scaling, PixelRect bounds, PixelRect workingArea, bool isPrimary, IntPtr hMonitor)
+ : base(scaling, bounds, workingArea, isPrimary)
{
_hMonitor = hMonitor;
}
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
index 8c94077a0b..4ce13ae0e0 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.AppWndProc.cs
@@ -272,13 +272,34 @@ namespace Avalonia.Win32
TrackMouseEvent(ref tm);
}
+ var point = DipFromLParam(lParam);
+
+ // Prepare points for the IntermediatePoints call.
+ var p = new POINT()
+ {
+ X = (int)(point.X * RenderScaling),
+ Y = (int)(point.Y * RenderScaling)
+ };
+ ClientToScreen(_hwnd, ref p);
+ var currPoint = new MOUSEMOVEPOINT()
+ {
+ x = p.X & 0xFFFF,
+ y = p.Y & 0xFFFF,
+ time = (int)timestamp
+ };
+ var prevPoint = _lastWmMousePoint;
+ _lastWmMousePoint = currPoint;
+
e = new RawPointerEventArgs(
_mouseDevice,
timestamp,
_owner,
RawPointerEventType.Move,
- DipFromLParam(lParam),
- GetMouseModifiers(wParam));
+ point,
+ GetMouseModifiers(wParam))
+ {
+ IntermediatePoints = new Lazy>(() => CreateLazyIntermediatePoints(currPoint, prevPoint))
+ };
break;
}
@@ -655,7 +676,10 @@ namespace Avalonia.Win32
}
case WindowsMessage.WM_IME_SETCONTEXT:
{
- DefWindowProc(Hwnd, msg, wParam, (IntPtr)(lParam.ToInt64() & ~ISC_SHOWUICOMPOSITIONWINDOW));
+ unchecked
+ {
+ DefWindowProc(Hwnd, msg, wParam, lParam & ~(nint)ISC_SHOWUICOMPOSITIONWINDOW);
+ }
UpdateInputMethod(GetKeyboardLayout(0));
@@ -786,6 +810,65 @@ namespace Avalonia.Win32
return null;
}
+ private unsafe IReadOnlyList CreateLazyIntermediatePoints(MOUSEMOVEPOINT movePoint, MOUSEMOVEPOINT prevMovePoint)
+ {
+ // To understand some of this code, please check MS docs:
+ // https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getmousemovepointsex#remarks
+
+ fixed (MOUSEMOVEPOINT* movePoints = s_mouseHistoryInfos)
+ {
+ var movePointCopy = movePoint;
+ movePointCopy.time = 0; // empty "time" as otherwise WinAPI will always fail
+ int pointsCount = GetMouseMovePointsEx(
+ (uint)(Marshal.SizeOf(movePointCopy)),
+ &movePointCopy, movePoints, s_mouseHistoryInfos.Length,
+ 1);
+
+ // GetMouseMovePointsEx can return -1 if point wasn't found or there is so beeg delay that original points were erased from the buffer.
+ if (pointsCount <= 1)
+ {
+ return Array.Empty();
+ }
+
+ s_intermediatePointsPooledList.Clear();
+ s_intermediatePointsPooledList.Capacity = pointsCount;
+ for (int i = pointsCount - 1; i >= 1; i--)
+ {
+ var historyInfo = s_mouseHistoryInfos[i];
+ // Skip points newer than current point.
+ if (historyInfo.time > movePoint.time ||
+ (historyInfo.time == movePoint.time &&
+ historyInfo.x == movePoint.x &&
+ historyInfo.y == movePoint.y))
+ {
+ continue;
+ }
+ // Skip poins older from previous WM_MOUSEMOVE point.
+ if (historyInfo.time < prevMovePoint.time ||
+ (historyInfo.time == prevMovePoint.time &&
+ historyInfo.x == prevMovePoint.x &&
+ historyInfo.y == prevMovePoint.y))
+ {
+ continue;
+ }
+
+ // To support multiple screens.
+ if (historyInfo.x > 32767)
+ historyInfo.x -= 65536;
+
+ if (historyInfo.y > 32767)
+ historyInfo.y -= 65536;
+
+ var point = PointToClient(new PixelPoint(historyInfo.x, historyInfo.y));
+ s_intermediatePointsPooledList.Add(new RawPointerPoint
+ {
+ Position = point
+ });
+ }
+ return s_intermediatePointsPooledList;
+ }
+ }
+
private RawPointerEventArgs CreatePointerArgs(IInputDevice device, ulong timestamp, RawPointerEventType eventType, RawPointerPoint point, RawInputModifiers modifiers, uint rawPointerId)
{
return device is TouchDevice
diff --git a/src/Windows/Avalonia.Win32/WindowImpl.cs b/src/Windows/Avalonia.Win32/WindowImpl.cs
index 7b6e073f97..4569b76d7d 100644
--- a/src/Windows/Avalonia.Win32/WindowImpl.cs
+++ b/src/Windows/Avalonia.Win32/WindowImpl.cs
@@ -65,6 +65,7 @@ namespace Avalonia.Win32
private bool _isUsingComposition;
private IBlurHost _blurHost;
private PlatformResizeReason _resizeReason;
+ private MOUSEMOVEPOINT _lastWmMousePoint;
#if USE_MANAGED_DRAG
private readonly ManagedWindowResizeDragHelper _managedDrag;
@@ -107,6 +108,7 @@ namespace Avalonia.Win32
private static readonly POINTER_TOUCH_INFO[] s_historyTouchInfos = new POINTER_TOUCH_INFO[MaxPointerHistorySize];
private static readonly POINTER_PEN_INFO[] s_historyPenInfos = new POINTER_PEN_INFO[MaxPointerHistorySize];
private static readonly POINTER_INFO[] s_historyInfos = new POINTER_INFO[MaxPointerHistorySize];
+ private static readonly MOUSEMOVEPOINT[] s_mouseHistoryInfos = new MOUSEMOVEPOINT[64];
public WindowImpl()
{
@@ -224,7 +226,7 @@ namespace Avalonia.Win32
}
}
- private double PrimaryScreenRenderScaling => Screen.AllScreens.FirstOrDefault(screen => screen.Primary)?.PixelDensity ?? 1;
+ private double PrimaryScreenRenderScaling => Screen.AllScreens.FirstOrDefault(screen => screen.IsPrimary)?.Scaling ?? 1;
public double RenderScaling => _scaling;
diff --git a/src/iOS/Avalonia.iOS/AvaloniaView.cs b/src/iOS/Avalonia.iOS/AvaloniaView.cs
index 1c79e93f5c..2080352020 100644
--- a/src/iOS/Avalonia.iOS/AvaloniaView.cs
+++ b/src/iOS/Avalonia.iOS/AvaloniaView.cs
@@ -46,7 +46,6 @@ namespace Avalonia.iOS
);
_topLevelImpl.Surfaces = new[] { new EaglLayerSurface(l) };
MultipleTouchEnabled = true;
- AddSubviews(new UIView[] { new UIKit.UIButton(UIButtonType.InfoDark) });
}
public override bool CanBecomeFirstResponder => true;
diff --git a/src/iOS/Avalonia.iOS/Platform.cs b/src/iOS/Avalonia.iOS/Platform.cs
index 67d037f9e5..c2cf8c6f6c 100644
--- a/src/iOS/Avalonia.iOS/Platform.cs
+++ b/src/iOS/Avalonia.iOS/Platform.cs
@@ -28,32 +28,19 @@ namespace Avalonia.iOS
public static EaglFeature GlFeature;
public static DisplayLinkTimer Timer;
internal static Compositor Compositor { get; private set; }
-
- class PlatformSettings : IPlatformSettings
- {
- ///
- public Size TouchDoubleClickSize => new Size(10, 10);
-
- ///
- public TimeSpan TouchDoubleClickTime => TimeSpan.FromMilliseconds(500);
-
- public Size DoubleClickSize => new Size(4, 4);
-
- public TimeSpan DoubleClickTime => TouchDoubleClickTime;
- }
public static void Register()
{
GlFeature ??= new EaglFeature();
Timer ??= new DisplayLinkTimer();
var keyboard = new KeyboardDevice();
-
+
AvaloniaLocator.CurrentMutable
.Bind().ToConstant(GlFeature)
.Bind().ToConstant(new CursorFactoryStub())
.Bind().ToConstant(new WindowingPlatformStub())
.Bind().ToConstant(new ClipboardImpl())
- .Bind().ToConstant(new PlatformSettings())
+ .Bind().ToSingleton()
.Bind().ToConstant(new PlatformIconLoaderStub())
.Bind().ToSingleton()
.Bind().ToSingleton()
diff --git a/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs b/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs
index 7af29a56a1..7009151998 100644
--- a/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs
+++ b/src/tools/Avalonia.Designer.HostApp/DesignXamlLoader.cs
@@ -8,9 +8,11 @@ namespace Avalonia.Designer.HostApp
{
class DesignXamlLoader : AvaloniaXamlLoader.IRuntimeXamlLoader
{
- public object Load(Stream stream, Assembly localAsm, object o, Uri baseUri, bool designMode)
+ public object Load(Stream stream, RuntimeXamlLoaderConfiguration configuration)
{
- return AvaloniaXamlIlRuntimeCompiler.Load(stream, localAsm, o, baseUri, designMode);
+ return AvaloniaXamlIlRuntimeCompiler.Load(stream,
+ configuration.LocalAssembly, configuration.RootInstance, configuration.BaseUri,
+ configuration.DesignMode, configuration.UseCompiledBindingsByDefault);
}
}
}
diff --git a/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs b/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs
index 7b7d547346..c0c0182622 100644
--- a/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs
+++ b/tests/Avalonia.Base.UnitTests/Input/TouchDeviceTests.cs
@@ -209,7 +209,9 @@ namespace Avalonia.Input.UnitTests
var unitTestApp = UnitTestApplication.Start(
new TestServices(inputManager: new InputManager()));
var iSettingsMock = new Mock();
- iSettingsMock.Setup(x => x.TouchDoubleClickTime).Returns(doubleClickTime);
+ iSettingsMock.Setup(x => x.GetDoubleTapTime(It.IsAny())).Returns(doubleClickTime);
+ iSettingsMock.Setup(x => x.GetDoubleTapSize(It.IsAny())).Returns(new Size(16, 16));
+ iSettingsMock.Setup(x => x.GetTapSize(It.IsAny())).Returns(new Size(16, 16));
AvaloniaLocator.CurrentMutable.BindToSelf(this)
.Bind().ToConstant(iSettingsMock.Object);
return unitTestApp;
diff --git a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
index 5f8eb45f71..10db08f302 100644
--- a/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
+++ b/tests/Avalonia.Base.UnitTests/VisualTree/MockRenderInterface.cs
@@ -72,7 +72,7 @@ namespace Avalonia.Base.UnitTests.VisualTree
throw new NotImplementedException();
}
- public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun)
+ public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices, IReadOnlyList glyphAdvances, IReadOnlyList glyphOffsets)
{
throw new NotImplementedException();
}
@@ -126,21 +126,6 @@ namespace Avalonia.Base.UnitTests.VisualTree
throw new NotImplementedException();
}
- public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
- {
- throw new NotImplementedException();
- }
-
- public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
- {
- throw new NotImplementedException();
- }
-
- public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
- {
- throw new NotImplementedException();
- }
-
class MockStreamGeometry : IStreamGeometryImpl
{
private MockStreamGeometryContext _impl = new MockStreamGeometryContext();
diff --git a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
index 34f0dfef11..4170de71e6 100644
--- a/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
+++ b/tests/Avalonia.Benchmarks/NullRenderingPlatform.cs
@@ -5,6 +5,7 @@ using Avalonia.Media;
using Avalonia.Platform;
using Avalonia.UnitTests;
using Avalonia.Media.Imaging;
+using Microsoft.Diagnostics.Runtime;
namespace Avalonia.Benchmarks
{
@@ -117,19 +118,9 @@ namespace Avalonia.Benchmarks
return new MockStreamGeometryImpl();
}
- public IGlyphRunBuffer AllocateGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
+ public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices, IReadOnlyList glyphAdvances, IReadOnlyList glyphOffsets)
{
- throw new NotImplementedException();
- }
-
- public IHorizontalGlyphRunBuffer AllocateHorizontalGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
- {
- throw new NotImplementedException();
- }
-
- public IPositionedGlyphRunBuffer AllocatePositionedGlyphRun(IGlyphTypeface glyphTypeface, float fontRenderingEmSize, int length)
- {
- throw new NotImplementedException();
+ return new MockGlyphRun();
}
public bool SupportsIndividualRoundRects => true;
diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs
index 7c7cdd08db..556959effb 100644
--- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs
+++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Multiple.cs
@@ -36,7 +36,7 @@ namespace Avalonia.Controls.UnitTests
}
[Fact]
- public void Focusing_Item_With_Ctrl_And_Arrow_Key_Should_Add_To_Selection()
+ public void Focusing_Item_With_Ctrl_And_Arrow_Key_Should_Not_Add_To_Selection()
{
var target = new ListBox
{
@@ -56,11 +56,11 @@ namespace Avalonia.Controls.UnitTests
KeyModifiers = KeyModifiers.Control
});
- Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems);
+ Assert.Equal(new[] { "Foo" }, target.SelectedItems);
}
[Fact]
- public void Focusing_Selected_Item_With_Ctrl_And_Arrow_Key_Should_Remove_From_Selection()
+ public void Focusing_Selected_Item_With_Ctrl_And_Arrow_Key_Should_Not_Remove_From_Selection()
{
var target = new ListBox
{
@@ -81,7 +81,7 @@ namespace Avalonia.Controls.UnitTests
KeyModifiers = KeyModifiers.Control
});
- Assert.Equal(new[] { "Bar" }, target.SelectedItems);
+ Assert.Equal(new[] { "Foo", "Bar" }, target.SelectedItems);
}
private Control CreateListBoxTemplate(ITemplatedControl parent, INameScope scope)
diff --git a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
index bf516748cc..726a39ac46 100644
--- a/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
+++ b/tests/Avalonia.Controls.UnitTests/ListBoxTests_Single.cs
@@ -59,6 +59,58 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(0, target.SelectedIndex);
}
+ [Fact]
+ public void Focusing_Item_With_Arrow_Key_And_Ctrl_Pressed_Should_Not_Select_It()
+ {
+ var target = new ListBox
+ {
+ Template = new FuncControlTemplate(CreateListBoxTemplate),
+ Items = new[] { "Foo", "Bar", "Baz " },
+ };
+
+ ApplyTemplate(target);
+
+ target.Presenter.Panel.Children[0].RaiseEvent(new GotFocusEventArgs
+ {
+ RoutedEvent = InputElement.GotFocusEvent,
+ NavigationMethod = NavigationMethod.Directional,
+ KeyModifiers = KeyModifiers.Control
+ });
+
+ Assert.Equal(-1, target.SelectedIndex);
+ }
+
+ [Fact]
+ public void Pressing_Space_On_Focused_Item_With_Ctrl_Pressed_Should_Select_It()
+ {
+ using (UnitTestApplication.Start())
+ {
+ var target = new ListBox
+ {
+ Template = new FuncControlTemplate(CreateListBoxTemplate),
+ Items = new[] { "Foo", "Bar", "Baz " },
+ };
+ AvaloniaLocator.CurrentMutable.Bind().ToConstant(new Mock().Object);
+ ApplyTemplate(target);
+
+ target.Presenter.Panel.Children[0].RaiseEvent(new GotFocusEventArgs
+ {
+ RoutedEvent = InputElement.GotFocusEvent,
+ NavigationMethod = NavigationMethod.Directional,
+ KeyModifiers = KeyModifiers.Control
+ });
+
+ target.Presenter.Panel.Children[0].RaiseEvent(new KeyEventArgs
+ {
+ RoutedEvent = InputElement.KeyDownEvent,
+ Key = Key.Space,
+ KeyModifiers = KeyModifiers.Control
+ });
+
+ Assert.Equal(0, target.SelectedIndex);
+ }
+ }
+
[Fact]
public void Clicking_Item_Should_Select_It()
{
diff --git a/tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs b/tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs
deleted file mode 100644
index 05007e4f2e..0000000000
--- a/tests/Avalonia.Controls.UnitTests/RichTextBlockTests.cs
+++ /dev/null
@@ -1,132 +0,0 @@
-using Avalonia.Controls.Documents;
-using Avalonia.Controls.Presenters;
-using Avalonia.Controls.Templates;
-using Avalonia.Media;
-using Avalonia.UnitTests;
-using Xunit;
-
-namespace Avalonia.Controls.UnitTests
-{
- public class RichTextBlockTests
- {
- [Fact]
- public void Changing_InlinesCollection_Should_Invalidate_Measure()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- var target = new RichTextBlock();
-
- target.Measure(Size.Infinity);
-
- Assert.True(target.IsMeasureValid);
-
- target.Inlines.Add(new Run("Hello"));
-
- Assert.False(target.IsMeasureValid);
-
- target.Measure(Size.Infinity);
-
- Assert.True(target.IsMeasureValid);
- }
- }
-
- [Fact]
- public void Changing_Inlines_Properties_Should_Invalidate_Measure()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- var target = new RichTextBlock();
-
- var inline = new Run("Hello");
-
- target.Inlines.Add(inline);
-
- target.Measure(Size.Infinity);
-
- Assert.True(target.IsMeasureValid);
-
- inline.Foreground = Brushes.Green;
-
- Assert.False(target.IsMeasureValid);
- }
- }
-
- [Fact]
- public void Changing_Inlines_Should_Invalidate_Measure()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- var target = new RichTextBlock();
-
- var inlines = new InlineCollection { new Run("Hello") };
-
- target.Measure(Size.Infinity);
-
- Assert.True(target.IsMeasureValid);
-
- target.Inlines = inlines;
-
- Assert.False(target.IsMeasureValid);
- }
- }
-
- [Fact]
- public void Changing_Inlines_Should_Reset_Inlines_Parent()
- {
- using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
- {
- var target = new RichTextBlock();
-
- var run = new Run("Hello");
-
- target.Inlines.Add(run);
-
- target.Measure(Size.Infinity);
-
- Assert.True(target.IsMeasureValid);
-
- target.Inlines = null;
-
- Assert.Null(run.Parent);
-
- target.Inlines = new InlineCollection { run };
-
- Assert.Equal(target, run.Parent);
- }
- }
-
- [Fact]
- public void InlineUIContainer_Child_Schould_Be_Arranged()
- {
- using (UnitTestApplication.Start(TestServices.StyledWindow))
- {
- var target = new RichTextBlock();
-
- var button = new Button { Content = "12345678" };
-
- button.Template = new FuncControlTemplate((parent, scope) =>
- new TextBlock
- {
- Name = "PART_ContentPresenter",
- [!TextBlock.TextProperty] = parent[!ContentControl.ContentProperty],
- }.RegisterInNameScope(scope)
- );
-
- target.Inlines!.Add("123456");
- target.Inlines.Add(new InlineUIContainer(button));
- target.Inlines.Add("123456");
-
- target.Measure(Size.Infinity);
-
- Assert.True(button.IsMeasureValid);
- Assert.Equal(80, button.DesiredSize.Width);
-
- target.Arrange(new Rect(new Size(200, 50)));
-
- Assert.True(button.IsArrangeValid);
-
- Assert.Equal(60, button.Bounds.Left);
- }
- }
- }
-}
diff --git a/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs b/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs
index 37dde9fbac..de5e5a8ea3 100644
--- a/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/TextBlockTests.cs
@@ -1,7 +1,9 @@
using System;
using Avalonia.Controls.Documents;
+using Avalonia.Controls.Templates;
using Avalonia.Data;
using Avalonia.Media;
+using Avalonia.Metadata;
using Avalonia.Rendering;
using Avalonia.UnitTests;
using Moq;
@@ -60,5 +62,146 @@ namespace Avalonia.Controls.UnitTests
renderer.Verify(x => x.AddDirty(target), Times.Once);
}
+
+ [Fact]
+ public void Changing_InlinesCollection_Should_Invalidate_Measure()
+ {
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
+ {
+ var target = new TextBlock();
+
+ target.Measure(Size.Infinity);
+
+ Assert.True(target.IsMeasureValid);
+
+ target.Inlines.Add(new Run("Hello"));
+
+ Assert.False(target.IsMeasureValid);
+
+ target.Measure(Size.Infinity);
+
+ Assert.True(target.IsMeasureValid);
+ }
+ }
+
+ [Fact]
+ public void Changing_Inlines_Properties_Should_Invalidate_Measure()
+ {
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
+ {
+ var target = new TextBlock();
+
+ var inline = new Run("Hello");
+
+ target.Inlines.Add(inline);
+
+ target.Measure(Size.Infinity);
+
+ Assert.True(target.IsMeasureValid);
+
+ inline.Foreground = Brushes.Green;
+
+ Assert.False(target.IsMeasureValid);
+ }
+ }
+
+ [Fact]
+ public void Changing_Inlines_Should_Invalidate_Measure()
+ {
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
+ {
+ var target = new TextBlock();
+
+ var inlines = new InlineCollection { new Run("Hello") };
+
+ target.Measure(Size.Infinity);
+
+ Assert.True(target.IsMeasureValid);
+
+ target.Inlines = inlines;
+
+ Assert.False(target.IsMeasureValid);
+ }
+ }
+
+ [Fact]
+ public void Changing_Inlines_Should_Reset_Inlines_Parent()
+ {
+ using (UnitTestApplication.Start(TestServices.MockPlatformRenderInterface))
+ {
+ var target = new TextBlock();
+
+ var run = new Run("Hello");
+
+ target.Inlines.Add(run);
+
+ target.Measure(Size.Infinity);
+
+ Assert.True(target.IsMeasureValid);
+
+ target.Inlines = null;
+
+ Assert.Null(run.Parent);
+
+ target.Inlines = new InlineCollection { run };
+
+ Assert.Equal(target, run.Parent);
+ }
+ }
+
+ [Fact]
+ public void InlineUIContainer_Child_Schould_Be_Arranged()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var target = new TextBlock();
+
+ var button = new Button { Content = "12345678" };
+
+ button.Template = new FuncControlTemplate((parent, scope) =>
+ new TextBlock
+ {
+ Name = "PART_ContentPresenter",
+ [!TextBlock.TextProperty] = parent[!ContentControl.ContentProperty],
+ }.RegisterInNameScope(scope)
+ );
+
+ target.Inlines!.Add("123456");
+ target.Inlines.Add(new InlineUIContainer(button));
+ target.Inlines.Add("123456");
+
+ target.Measure(Size.Infinity);
+
+ Assert.True(button.IsMeasureValid);
+ Assert.Equal(80, button.DesiredSize.Width);
+
+ target.Arrange(new Rect(new Size(200, 50)));
+
+ Assert.True(button.IsArrangeValid);
+
+ Assert.Equal(60, button.Bounds.Left);
+ }
+ }
+
+ [Fact]
+ public void Setting_Text_Should_Reset_Inlines()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var target = new TextBlock();
+
+ target.Inlines.Add(new Run("Hello World"));
+
+ Assert.Equal("Hello World", target.Text);
+
+ Assert.Equal(1, target.Inlines.Count);
+
+ target.Text = "1234";
+
+ Assert.Equal("1234", target.Text);
+
+ Assert.Equal(0, target.Inlines.Count);
+ }
+ }
}
}
diff --git a/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs b/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs
index 4ffd314857..629408bcba 100644
--- a/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs
+++ b/tests/Avalonia.Controls.UnitTests/ViewboxTests.cs
@@ -1,4 +1,5 @@
using Avalonia.Controls.Shapes;
+using Avalonia.Data;
using Avalonia.LogicalTree;
using Avalonia.Media;
using Avalonia.UnitTests;
@@ -207,6 +208,26 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Size(200, 200), target.DesiredSize);
}
+ [Fact]
+ public void Child_DataContext_Binding_Works()
+ {
+ var data = new
+ {
+ Foo = "foo",
+ };
+
+ var target = new Viewbox()
+ {
+ DataContext = data,
+ Child = new Canvas
+ {
+ [!Canvas.DataContextProperty] = new Binding("Foo"),
+ },
+ };
+
+ Assert.Equal("foo", target.Child.DataContext);
+ }
+
private bool TryGetScale(Viewbox viewbox, out Vector scale)
{
if (viewbox.InternalTransform is null)
diff --git a/tests/Avalonia.DesignerSupport.TestApp/MainWindow.xaml b/tests/Avalonia.DesignerSupport.TestApp/MainWindow.xaml
index f90e5beaa6..c113c2a9f7 100644
--- a/tests/Avalonia.DesignerSupport.TestApp/MainWindow.xaml
+++ b/tests/Avalonia.DesignerSupport.TestApp/MainWindow.xaml
@@ -1,5 +1,4 @@
diff --git a/tests/Avalonia.IntegrationTests.Appium/GestureTests.cs b/tests/Avalonia.IntegrationTests.Appium/GestureTests.cs
new file mode 100644
index 0000000000..9745f993cb
--- /dev/null
+++ b/tests/Avalonia.IntegrationTests.Appium/GestureTests.cs
@@ -0,0 +1,179 @@
+using System;
+using System.Threading;
+using OpenQA.Selenium.Appium;
+using OpenQA.Selenium.Interactions;
+using Xunit;
+
+namespace Avalonia.IntegrationTests.Appium
+{
+ [Collection("Default")]
+ public class GestureTests
+ {
+ private readonly AppiumDriver _session;
+
+ public GestureTests(TestAppFixture fixture)
+ {
+ _session = fixture.Session;
+
+ var tabs = _session.FindElementByAccessibilityId("MainTabs");
+ var tab = tabs.FindElementByName("Gestures");
+ tab.Click();
+ var clear = _session.FindElementByAccessibilityId("ResetGestures");
+ clear.Click();
+ }
+
+ [Fact]
+ public void Tapped_Is_Raised()
+ {
+ var border = _session.FindElementByAccessibilityId("GestureBorder");
+ var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
+
+ new Actions(_session).Click(border).Perform();
+
+ Assert.Equal("Tapped", lastGesture.Text);
+ }
+
+ [Fact]
+ public void Tapped_Is_Raised_Slow()
+ {
+ var border = _session.FindElementByAccessibilityId("GestureBorder");
+ var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
+
+ new Actions(_session).ClickAndHold(border).Perform();
+
+ Thread.Sleep(2000);
+
+ new Actions(_session).Release(border).Perform();
+
+ Assert.Equal("Tapped", lastGesture.Text);
+ }
+
+ [Fact]
+ public void Tapped_Is_Not_Raised_For_Drag()
+ {
+ var border = _session.FindElementByAccessibilityId("GestureBorder");
+ var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
+
+ new Actions(_session)
+ .ClickAndHold(border)
+ .MoveByOffset(50, 50)
+ .Release()
+ .Perform();
+
+ Assert.Equal(string.Empty, lastGesture.Text);
+ }
+
+ [Fact]
+ public void DoubleTapped_Is_Raised()
+ {
+ var border = _session.FindElementByAccessibilityId("GestureBorder");
+ var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
+
+ new Actions(_session).DoubleClick(border).Perform();
+
+ Assert.Equal("DoubleTapped", lastGesture.Text);
+ }
+
+ [Fact]
+ public void DoubleTapped_Is_Raised_2()
+ {
+ var border = _session.FindElementByAccessibilityId("GestureBorder");
+ var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
+
+ new Actions(_session).ClickAndHold(border).Release().Perform();
+
+ Thread.Sleep(50);
+
+ // DoubleTapped is raised on second pointer press, not release.
+ new Actions(_session).ClickAndHold(border).Perform();
+
+ try
+ {
+ Assert.Equal("DoubleTapped", lastGesture.Text);
+ }
+ finally
+ {
+
+ new Actions(_session).MoveToElement(lastGesture).Release().Perform();
+ }
+ }
+
+ [Fact]
+ public void DoubleTapped_Is_Raised_Not_Raised_If_Too_Slow()
+ {
+ var border = _session.FindElementByAccessibilityId("GestureBorder");
+ var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
+
+ new Actions(_session).ClickAndHold(border).Release().Perform();
+
+ Thread.Sleep(2000);
+
+ new Actions(_session).ClickAndHold(border).Release().Perform();
+
+ Assert.Equal("Tapped", lastGesture.Text);
+ }
+
+ [Fact]
+ public void DoubleTapped_Is_Raised_After_Control_Changes()
+ {
+ // #8733
+ var border = _session.FindElementByAccessibilityId("GestureBorder");
+ var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
+
+ new Actions(_session)
+ .MoveToElement(border)
+ .DoubleClick()
+ .Perform();
+
+ Thread.Sleep(100);
+
+ new Actions(_session).MoveToElement(lastGesture, 200, 200).DoubleClick().Perform();
+
+ Assert.Equal("DoubleTapped2", lastGesture.Text);
+ }
+
+ [Fact]
+ public void RightTapped_Is_Raised()
+ {
+ var border = _session.FindElementByAccessibilityId("GestureBorder");
+ var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
+
+ new Actions(_session).ContextClick(border).Perform();
+
+ Assert.Equal("RightTapped", lastGesture.Text);
+ }
+
+ [PlatformFact(TestPlatforms.MacOS)]
+ public void RightTapped_Is_Raised_2()
+ {
+ var border = _session.FindElementByAccessibilityId("GestureBorder");
+ var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
+ var device = new PointerInputDevice(PointerKind.Mouse);
+ var b = new ActionBuilder();
+
+ b.AddAction(device.CreatePointerMove(border, 50, 50, TimeSpan.FromMilliseconds(50)));
+ b.AddAction(device.CreatePointerDown(MouseButton.Right));
+ b.AddAction(device.CreatePointerMove(border, 52, 52, TimeSpan.FromMilliseconds(50)));
+ b.AddAction(device.CreatePointerUp(MouseButton.Right));
+ _session.PerformActions(b.ToActionSequenceList());
+
+ Assert.Equal("RightTapped", lastGesture.Text);
+ }
+
+ [PlatformFact(TestPlatforms.MacOS)]
+ public void RightTapped_Is_Not_Raised_For_Drag()
+ {
+ var border = _session.FindElementByAccessibilityId("GestureBorder");
+ var lastGesture = _session.FindElementByAccessibilityId("LastGesture");
+ var device = new PointerInputDevice(PointerKind.Mouse);
+ var b = new ActionBuilder();
+
+ b.AddAction(device.CreatePointerMove(border, 50, 50, TimeSpan.FromMilliseconds(100)));
+ b.AddAction(device.CreatePointerDown(MouseButton.Right));
+ b.AddAction(device.CreatePointerMove(CoordinateOrigin.Pointer, 50, 50, TimeSpan.FromMilliseconds(100)));
+ b.AddAction(device.CreatePointerUp(MouseButton.Right));
+
+ Assert.Equal(string.Empty, lastGesture.Text);
+ }
+ }
+}
diff --git a/tests/Avalonia.IntegrationTests.Appium/PlatformFactAttribute.cs b/tests/Avalonia.IntegrationTests.Appium/PlatformFactAttribute.cs
index 53ae5d924f..3acf9fd5e2 100644
--- a/tests/Avalonia.IntegrationTests.Appium/PlatformFactAttribute.cs
+++ b/tests/Avalonia.IntegrationTests.Appium/PlatformFactAttribute.cs
@@ -1,27 +1,34 @@
+#nullable enable
using System;
-using System.Linq;
using System.Runtime.InteropServices;
using Xunit;
-namespace Avalonia.IntegrationTests.Appium
+namespace Avalonia
{
[Flags]
internal enum TestPlatforms
{
Windows = 0x01,
MacOS = 0x02,
- All = Windows | MacOS,
+ Linux = 0x04,
+ All = Windows | MacOS | Linux,
}
-
+
internal class PlatformFactAttribute : FactAttribute
{
- public PlatformFactAttribute(TestPlatforms platforms = TestPlatforms.All) => Platforms = platforms;
-
+ private readonly string? _reason;
+
+ public PlatformFactAttribute(TestPlatforms platforms, string? reason = null)
+ {
+ _reason = reason;
+ Platforms = platforms;
+ }
+
public TestPlatforms Platforms { get; }
-
+
public override string? Skip
{
- get => IsSupported() ? null : $"Ignored on {RuntimeInformation.OSDescription}";
+ get => IsSupported() ? null : $"Ignored on {RuntimeInformation.OSDescription}" + (_reason is not null ? $" reason: \"{_reason}\"" : "");
set => throw new NotSupportedException();
}
@@ -31,6 +38,8 @@ namespace Avalonia.IntegrationTests.Appium
return Platforms.HasAnyFlag(TestPlatforms.Windows);
if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
return Platforms.HasAnyFlag(TestPlatforms.MacOS);
+ if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
+ return Platforms.HasAnyFlag(TestPlatforms.Linux);
return false;
}
}
diff --git a/tests/Avalonia.IntegrationTests.Appium/TestAppFixture.cs b/tests/Avalonia.IntegrationTests.Appium/TestAppFixture.cs
index b3385d8ee7..d71f9e9bcc 100644
--- a/tests/Avalonia.IntegrationTests.Appium/TestAppFixture.cs
+++ b/tests/Avalonia.IntegrationTests.Appium/TestAppFixture.cs
@@ -11,7 +11,7 @@ namespace Avalonia.IntegrationTests.Appium
{
public class TestAppFixture : IDisposable
{
- private const string TestAppPath = @"..\..\..\..\..\samples\IntegrationTestApp\bin\Debug\net6.0\IntegrationTestApp.exe";
+ private const string TestAppPath = @"..\..\..\..\..\samples\IntegrationTestApp\bin\Debug\net7.0\IntegrationTestApp.exe";
private const string TestAppBundleId = "net.avaloniaui.avalonia.integrationtestapp";
public TestAppFixture()
diff --git a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs
index 2dd849bee1..05ed0616a8 100644
--- a/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs
+++ b/tests/Avalonia.IntegrationTests.Appium/WindowTests_MacOS.cs
@@ -180,10 +180,23 @@ namespace Avalonia.IntegrationTests.Appium
Assert.False(miniaturizeButton.Enabled);
}
}
+
+ [PlatformTheory(TestPlatforms.MacOS)]
+ [InlineData(ShowWindowMode.Owned)]
+ public void Minimize_Button_Disabled_Owned_Window(ShowWindowMode mode)
+ {
+ using (OpenWindow(new PixelSize(200, 100), mode, WindowStartupLocation.Manual))
+ {
+ var secondaryWindow = GetWindow("SecondaryWindow");
+ var (_, miniaturizeButton, _) = secondaryWindow.GetChromeButtons();
+
+ Assert.Equal(false, miniaturizeButton.Enabled);
+ }
+ }
+
[PlatformTheory(TestPlatforms.MacOS)]
[InlineData(ShowWindowMode.NonOwned)]
- [InlineData(ShowWindowMode.Owned)]
public void Minimize_Button_Minimizes_Window(ShowWindowMode mode)
{
using (OpenWindow(new PixelSize(200, 100), mode, WindowStartupLocation.Manual))
diff --git a/tests/Avalonia.IntegrationTests.Appium/macos-clean-build-test.sh b/tests/Avalonia.IntegrationTests.Appium/macos-clean-build-test.sh
index 14e765d16a..dc4619f35c 100755
--- a/tests/Avalonia.IntegrationTests.Appium/macos-clean-build-test.sh
+++ b/tests/Avalonia.IntegrationTests.Appium/macos-clean-build-test.sh
@@ -10,7 +10,7 @@ pkill IntegrationTestApp
rm -rf $(osascript -e "POSIX path of (path to application id \"net.avaloniaui.avalonia.integrationtestapp\")")
pkill IntegrationTestApp
./samples/IntegrationTestApp/bundle.sh
-open -n ./samples/IntegrationTestApp/bin/Debug/net6.0/osx-arm64/publish/IntegrationTestApp.app
+open -n ./samples/IntegrationTestApp/bin/Debug/net7.0/osx-arm64/publish/IntegrationTestApp.app
pkill IntegrationTestApp
open -b net.avaloniaui.avalonia.integrationtestapp
dotnet test tests/Avalonia.IntegrationTests.Appium/ -l "console;verbosity=detailed"
diff --git a/tests/Avalonia.IntegrationTests.Appium/readme.md b/tests/Avalonia.IntegrationTests.Appium/readme.md
index ee630a31fd..f54720920d 100644
--- a/tests/Avalonia.IntegrationTests.Appium/readme.md
+++ b/tests/Avalonia.IntegrationTests.Appium/readme.md
@@ -18,7 +18,7 @@
- Install Appium: https://appium.io/
- Give [Xcode helper the required permissions](https://apple.stackexchange.com/questions/334008)
- `cd samples/IntegrationTestApp` then `./bundle.sh` to create an app bundle for `IntegrationTestApp`
-- Register the app bundle by running `open -n ./bin/Debug/net6.0/osx-arm64/publish/IntegrationTestApp.app`
+- Register the app bundle by running `open -n ./bin/Debug/net7.0/osx-arm64/publish/IntegrationTestApp.app`
### Running
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
index f562529cb8..7ff19e1049 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/Avalonia.Markup.Xaml.UnitTests.csproj
@@ -31,5 +31,10 @@
+
+
+ PlatformFactAttribute.cs
+
+
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
index 215ae4d54f..77067fa517 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/CompiledBindingExtensionTests.cs
@@ -754,6 +754,40 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
}
}
+ [Fact]
+ public void ResolvesRelativeSourceBindingFromStyleSelector()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+
+
+
+";
+
+ var textBox = AvaloniaRuntimeXamlLoader.Parse(xaml);
+ textBox.Measure(new Size(10, 10));
+
+ var result = textBox.GetTemplateChildren().OfType().First();
+ Assert.Equal(textBox.InnerLeftContent, result.Content);
+ }
+ }
+
[Fact]
public void ResolvesElementNameInTemplate()
{
@@ -1569,6 +1603,27 @@ namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions
}
}
+ [Fact]
+ public void Uses_RuntimeLoader_Configuration_To_Enabled_Compiled()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+";
+ var control = (AssignBindingControl)AvaloniaRuntimeXamlLoader.Load(xaml, new RuntimeXamlLoaderConfiguration
+ {
+ UseCompiledBindingsByDefault = true
+ });
+ var compiledPath = ((CompiledBindingExtension)control.X).Path;
+
+ var node = Assert.IsType(Assert.Single(compiledPath.Elements));
+ Assert.Equal(typeof(string), node.Property.PropertyType);
+ }
+ }
+
void Throws(string type, Action cb)
{
try
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OnFormFactorExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OnFormFactorExtensionTests.cs
new file mode 100644
index 0000000000..783e647738
--- /dev/null
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OnFormFactorExtensionTests.cs
@@ -0,0 +1,71 @@
+using Avalonia.Controls;
+using Avalonia.Platform;
+using Xunit;
+
+namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;
+
+public class OnFormFactorExtensionTests : XamlTestBase
+{
+ [Fact]
+ public void Should_Resolve_Default_Value()
+ {
+ using (AvaloniaLocator.EnterScope())
+ {
+ AvaloniaLocator.CurrentMutable.Bind()
+ .ToConstant(new TestRuntimePlatform(false, false));
+
+ var xaml = @"
+
+
+";
+
+ var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
+ var textBlock = (TextBlock)userControl.Content!;
+
+ Assert.Equal("Hello World", textBlock.Text);
+ }
+ }
+
+ [Theory]
+ [InlineData(false, true, "Im Mobile")]
+ [InlineData(true, false, "Im Desktop")]
+ [InlineData(false, false, "Default value")]
+ public void Should_Resolve_Expected_Value_Per_Platform(bool isDesktop, bool isMobile, string expectedResult)
+ {
+ using (AvaloniaLocator.EnterScope())
+ {
+ AvaloniaLocator.CurrentMutable.Bind()
+ .ToConstant(new TestRuntimePlatform(isDesktop, isMobile));
+
+ var xaml = @"
+
+
+";
+
+ var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
+ var textBlock = (TextBlock)userControl.Content!;
+
+ Assert.Equal(expectedResult, textBlock.Text);
+ }
+ }
+
+ private class TestRuntimePlatform : StandardRuntimePlatform
+ {
+ private readonly bool _isDesktop;
+ private readonly bool _isMobile;
+
+ public TestRuntimePlatform(bool isDesktop, bool isMobile)
+ {
+ _isDesktop = isDesktop;
+ _isMobile = isMobile;
+ }
+
+ public override RuntimePlatformInfo GetRuntimeInfo()
+ {
+ return new RuntimePlatformInfo() { IsDesktop = _isDesktop, IsMobile = _isMobile };
+ }
+ }
+}
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OnPlatformExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OnPlatformExtensionTests.cs
new file mode 100644
index 0000000000..1d37378010
--- /dev/null
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OnPlatformExtensionTests.cs
@@ -0,0 +1,75 @@
+using Avalonia.Controls;
+using Avalonia.Platform;
+using Xunit;
+
+namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;
+
+public class OnPlatformExtensionTests : XamlTestBase
+{
+ [Fact]
+ public void Should_Resolve_Default_Value()
+ {
+ using (AvaloniaLocator.EnterScope())
+ {
+ AvaloniaLocator.CurrentMutable.Bind()
+ .ToConstant(new TestRuntimePlatform(OperatingSystemType.Unknown));
+
+ var xaml = @"
+
+
+";
+
+ var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
+ var textBlock = (TextBlock)userControl.Content!;
+
+ Assert.Equal("Hello World", textBlock.Text);
+ }
+ }
+
+ [Theory]
+ [InlineData(OperatingSystemType.WinNT, "Im Windows")]
+ [InlineData(OperatingSystemType.OSX, "Im macOS")]
+ [InlineData(OperatingSystemType.Linux, "Im Linux")]
+ [InlineData(OperatingSystemType.Android, "Im Android")]
+ [InlineData(OperatingSystemType.iOS, "Im iOS")]
+ [InlineData(OperatingSystemType.Browser, "Im Browser")]
+ [InlineData(OperatingSystemType.Unknown, "Default value")]
+ public void Should_Resolve_Expected_Value_Per_Platform(OperatingSystemType currentPlatform, string expectedResult)
+ {
+ using (AvaloniaLocator.EnterScope())
+ {
+ AvaloniaLocator.CurrentMutable.Bind()
+ .ToConstant(new TestRuntimePlatform(currentPlatform));
+
+ var xaml = @"
+
+
+";
+
+ var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
+ var textBlock = (TextBlock)userControl.Content!;
+
+ Assert.Equal(expectedResult, textBlock.Text);
+ }
+ }
+
+ private class TestRuntimePlatform : StandardRuntimePlatform
+ {
+ private readonly OperatingSystemType _operatingSystemType;
+
+ public TestRuntimePlatform(OperatingSystemType operatingSystemType)
+ {
+ _operatingSystemType = operatingSystemType;
+ }
+
+ public override RuntimePlatformInfo GetRuntimeInfo()
+ {
+ return new RuntimePlatformInfo() { OperatingSystem = _operatingSystemType };
+ }
+ }
+}
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OptionsMarkupExtensionTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OptionsMarkupExtensionTests.cs
new file mode 100644
index 0000000000..2d1f961743
--- /dev/null
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/MarkupExtensions/OptionsMarkupExtensionTests.cs
@@ -0,0 +1,605 @@
+using System;
+using System.Reactive.Disposables;
+using System.Runtime.InteropServices;
+using Avalonia.Controls;
+using Avalonia.Markup.Xaml.MarkupExtensions;
+using Avalonia.Media;
+using Avalonia.Metadata;
+using Xunit;
+
+namespace Avalonia.Markup.Xaml.UnitTests.MarkupExtensions;
+
+public class OptionsMarkupExtensionTests : XamlTestBase
+{
+ public static Func RaisedOption;
+ public static int? ObjectsCreated;
+
+ [Fact]
+ public void Resolve_Default_Value()
+ {
+ using var _ = SetupTestGlobals("default");
+
+ var xaml = @"
+";
+
+ var textBlock = (TextBlock)AvaloniaRuntimeXamlLoader.Load(xaml);
+
+ Assert.Equal("Hello World", textBlock.Text);
+ }
+
+ [Fact]
+ public void Resolve_Default_Value_From_Ctor()
+ {
+ using var _ = SetupTestGlobals("default");
+
+ var xaml = @"
+";
+
+ var textBlock = (TextBlock)AvaloniaRuntimeXamlLoader.Load(xaml);
+
+ Assert.Equal("Hello World", textBlock.Text);
+ }
+
+ [Fact]
+ public void Resolve_Implicit_Default_Value_Ref_Type()
+ {
+ using var _ = SetupTestGlobals("default");
+
+ var xaml = @"
+";
+
+ var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
+
+ Assert.Equal(null, userControl.Tag);
+ }
+
+ [Fact]
+ public void Resolve_Implicit_Default_Value_Val_Type()
+ {
+ using var _ = SetupTestGlobals("default");
+
+ var xaml = @"
+";
+
+ var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
+
+ Assert.Equal(0d, userControl.Height);
+ }
+
+ [Fact]
+ public void Resolve_Implicit_Default_Value_Avalonia_Val_Type()
+ {
+ using var _ = SetupTestGlobals("default");
+
+ var xaml = @"
+";
+
+ var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
+
+ Assert.Equal(new Thickness(0), userControl.Margin);
+ }
+
+ [Theory]
+ [InlineData("option 1", "Im Option 1")]
+ [InlineData("option 2", "Im Option 2")]
+ [InlineData("3", "Im Option 3")]
+ [InlineData("unknown", "Default value")]
+ public void Resolve_Expected_Value_Per_Option(object option, string expectedResult)
+ {
+ using var _ = SetupTestGlobals(option);
+
+ var xaml = @"
+";
+
+ var textBlock = (TextBlock)AvaloniaRuntimeXamlLoader.Load(xaml);
+
+ Assert.Equal(expectedResult, textBlock.Text);
+ }
+
+ [Theory]
+ [InlineData("option 1", "Im Option 1")]
+ [InlineData("option 2", "Im Option 2")]
+ [InlineData("3", "Im Option 3")]
+ [InlineData("unknown", "Default value")]
+ public void Resolve_Expected_Value_Per_Option_Create_Single_Object(object option, string expectedResult)
+ {
+ using var _ = SetupTestGlobals(option);
+
+ var xaml = @"
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+";
+
+ var contentControl = (ContentControl)AvaloniaRuntimeXamlLoader.Load(xaml);
+ var obj = Assert.IsType(contentControl.Content);
+
+ Assert.Equal(expectedResult, obj.Name);
+ Assert.Equal(1, ObjectsCreated);
+ }
+
+ [Fact]
+ public void Convert_Bcl_Type()
+ {
+ using var _ = SetupTestGlobals("option 1");
+
+ var xaml = @"
+";
+
+ var border = (Border)AvaloniaRuntimeXamlLoader.Load(xaml);
+
+ Assert.Equal(50.1, border.Height);
+ }
+
+ [Fact]
+ public void Convert_Avalonia_Type()
+ {
+ using var _ = SetupTestGlobals("option 1");
+
+ var xaml = @"
+";
+
+ var border = (Border)AvaloniaRuntimeXamlLoader.Load(xaml);
+
+ Assert.Equal(new Thickness(10, 8, 10, 8), border.Padding);
+ }
+
+ [PlatformFact(TestPlatforms.Windows | TestPlatforms.Linux, "TypeArguments test is failing on macOS from SRE emit")]
+ public void Respect_Custom_TypeArgument()
+ {
+ using var _ = SetupTestGlobals("option 1");
+
+ var xaml = @"
+";
+
+ var textBlock = (TextBlock)AvaloniaRuntimeXamlLoader.Load(xaml);
+
+ Assert.Equal(new Thickness(10, 10, 10, 10), textBlock.Tag);
+ }
+
+ [Fact]
+ public void Allow_Nester_Markup_Extensions()
+ {
+ using var _ = SetupTestGlobals("option 1");
+
+ var xaml = @"
+
+
+ #ff506070
+
+
+";
+
+ var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
+ var border = (Border)userControl.Content!;
+
+ Assert.Equal(Color.Parse("#ff506070"), ((ISolidColorBrush)border.Background!).Color);
+ }
+
+ [Fact]
+ public void Allow_Nester_On_Platform_Markup_Extensions()
+ {
+ using var _ = SetupTestGlobals("option 1");
+
+ var xaml = @"
+";
+
+ var border = (Border)AvaloniaRuntimeXamlLoader.Load(xaml);
+
+ Assert.Equal(new Thickness(10), border.Margin);
+ }
+
+ [Fact]
+ public void Support_Xml_Syntax()
+ {
+ using var _ = SetupTestGlobals("option 1");
+
+ var xaml = @"
+
+
+
+
+
+
+
+
+";
+
+ var border = (Border)AvaloniaRuntimeXamlLoader.Load(xaml);
+
+ Assert.Equal(Color.Parse("#ff506070"), ((ISolidColorBrush)border.Background!).Color);
+ }
+
+ [PlatformFact(TestPlatforms.Windows | TestPlatforms.Linux, "TypeArguments test is failing on macOS from SRE emit")]
+ public void Support_Xml_Syntax_With_Custom_TypeArguments()
+ {
+ using var _ = SetupTestGlobals("option 1");
+
+ var xaml = @"
+
+
+
+
+";
+
+ var border = (Border)AvaloniaRuntimeXamlLoader.Load(xaml);
+
+ Assert.Equal(new Thickness(10, 10, 10, 10), border.Tag);
+ }
+
+ [Theory]
+ [InlineData("option 1", "#ff506070")]
+ [InlineData("3", "#000")]
+ public void Support_Special_On_Syntax(object option, string color)
+ {
+ using var _ = SetupTestGlobals(option);
+
+ var xaml = @"
+
+
+
+
+
+
+
+
+
+
+
+";
+
+ var border = (Border)AvaloniaRuntimeXamlLoader.Load(xaml);
+
+ Assert.Equal(Color.Parse(color), ((ISolidColorBrush)border.Background!).Color);
+ }
+
+ [Fact]
+ public void Support_Control_Inside_Xml_Syntax()
+ {
+ using var _ = SetupTestGlobals("option 1");
+
+ var xaml = @"
+
+
+
+
+
+
+";
+
+ var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
+ var button = (Button)userControl.Content!;
+
+ Assert.Equal("Hello World", button.Content);
+ }
+
+ [Fact]
+ public void Support_Default_Control_Inside_Xml_Syntax()
+ {
+ using var _ = SetupTestGlobals("unknown");
+
+ var xaml = @"
+
+
+
+
+
+
+";
+
+ var userControl = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
+ var button = (Button)userControl.Content!;
+
+ Assert.Equal("Hello World", button.Content);
+ }
+
+ [Fact]
+ public void Support_Complex_Property_Setters_Dictionary()
+ {
+ using var _ = SetupTestGlobals("option 1");
+
+ var xaml = @"
+
+ Black
+
+
+
+
+
+ White
+";
+
+ var resourceDictionary = (ResourceDictionary)AvaloniaRuntimeXamlLoader.Load(xaml);
+ var button = Assert.IsType(resourceDictionary["MyKey"]);
+ Assert.Equal("Hello World", button.Content);
+ Assert.Equal(Colors.Black, resourceDictionary["Color1"]);
+ Assert.Equal(Colors.White, resourceDictionary["Color2"]);
+ }
+
+ [Fact]
+ public void Support_Complex_Property_Setters_List()
+ {
+ using var _ = SetupTestGlobals("option 1");
+
+ var xaml = @"
+
+
+
+
+
+
+
+
+";
+
+ var panel = (Panel)AvaloniaRuntimeXamlLoader.Load(xaml);
+ Assert.Equal(3, panel.Children.Count);
+ Assert.IsType(panel.Children[1]);
+ }
+
+ [Theory]
+ [InlineData("option 1", "foo")]
+ [InlineData("option 2", "bar")]
+ public void BindingExtension_Works_Inside_Of_OptionsMarkupExtension(string option, string expected)
+ {
+ using var _ = SetupTestGlobals(option);
+
+ var xaml = @"
+
+
+ foo
+
+
+
+";
+
+ var window = (UserControl)AvaloniaRuntimeXamlLoader.Load(xaml);
+ var textBlock = window.FindControl("textBlock");
+
+ Assert.Equal(expected, textBlock.Text);
+ }
+
+ [Fact]
+ public void Resolve_Expected_Value_With_Method_Without_ServiceProvider()
+ {
+ using var _ = SetupTestGlobals(2);
+
+ var xaml = @"
+";
+
+ var textBlock = (TextBlock)AvaloniaRuntimeXamlLoader.Load(xaml);
+
+ Assert.Equal("Im Option 2", textBlock.Text);
+ }
+
+ [Fact]
+ public void Resolve_Expected_Value_Minimal_Extension()
+ {
+ var xaml = @"
+";
+
+ var textBlock = (TextBlock)AvaloniaRuntimeXamlLoader.Load(xaml);
+
+ Assert.True(textBlock.IsSet(Visual.IsVisibleProperty));
+ Assert.True(textBlock.IsVisible);
+ }
+
+ [Fact]
+ public void Resolve_Expected_Value_Extension_With_Property()
+ {
+ var xaml = @"
+";
+
+ var textBlock = (TextBlock)AvaloniaRuntimeXamlLoader.Load(xaml);
+
+ Assert.True(textBlock.IsSet(Visual.IsVisibleProperty));
+ Assert.True(textBlock.IsVisible);
+ }
+
+ private static IDisposable SetupTestGlobals(object acceptedOption)
+ {
+ RaisedOption = o => o.Equals(acceptedOption);
+ ObjectsCreated = 0;
+ return Disposable.Create(() =>
+ {
+ RaisedOption = null;
+ ObjectsCreated = null;
+ });
+ }
+}
+
+public class OptionsMarkupExtension : OptionsMarkupExtensionBase
+{
+ public OptionsMarkupExtension()
+ {
+
+ }
+
+ public OptionsMarkupExtension(object defaultValue)
+ {
+ Default = defaultValue;
+ }
+}
+
+public class OptionsMarkupExtension : OptionsMarkupExtensionBase>
+{
+ public OptionsMarkupExtension()
+ {
+
+ }
+
+ public OptionsMarkupExtension(TReturn defaultValue)
+ {
+ Default = defaultValue;
+ }
+}
+
+public class OptionsMarkupExtensionBase : IAddChild
+ where TOn : On
+{
+ [MarkupExtensionOption("option 1")]
+ public TReturn OptionA { get; set; }
+
+ [MarkupExtensionOption("option 2")]
+ public TReturn OptionB { get; set; }
+
+ [MarkupExtensionOption(3)]
+ public TReturn OptionNumber { get; set; }
+
+ [Content]
+ [MarkupExtensionDefaultOption]
+ public TReturn Default { get; set; }
+
+ public bool ShouldProvideOption(IServiceProvider serviceProvider, string option)
+ {
+ return OptionsMarkupExtensionTests.RaisedOption(option);
+ }
+
+ public TReturn ProvideValue(IServiceProvider serviceProvider) { throw null; }
+
+ public void AddChild(TOn child) {}
+}
+
+public class OptionsMarkupExtensionNoServiceProvider
+{
+ [MarkupExtensionOption(1)]
+ public object OptionA { get; set; }
+
+ [MarkupExtensionOption(2)]
+ public object OptionB { get; set; }
+
+ [Content]
+ [MarkupExtensionDefaultOption]
+ public object Default { get; set; }
+
+ public static bool ShouldProvideOption(int option)
+ {
+ return OptionsMarkupExtensionTests.RaisedOption(option);
+ }
+
+ public object ProvideValue(IServiceProvider serviceProvider) { throw null; }
+}
+
+public class OptionsMarkupExtensionMinimal
+{
+ [MarkupExtensionOption(11.0)]
+ public bool OptionA { get; set; }
+
+ public static bool ShouldProvideOption(double option) => option > 0;
+
+ public object ProvideValue() { throw null; }
+}
+
+public class OptionsMarkupExtensionWithProperty
+{
+ [MarkupExtensionOption(5)]
+ public bool OptionA { get; set; }
+
+ public int Property { get; set; }
+
+ public bool ShouldProvideOption(int option) => option == Property;
+
+ public object ProvideValue() { throw null; }
+}
+
+public class OptionsMarkupExtensionWithGeneric
+{
+ [MarkupExtensionOption("option 1")]
+ public TResult OptionA { get; set; }
+
+ [Content]
+ [MarkupExtensionDefaultOption]
+ public TResult Default { get; set; }
+
+ public bool ShouldProvideOption(string option)
+ {
+ return OptionsMarkupExtensionTests.RaisedOption(option);
+ }
+
+ public TResult ProvideValue() { throw null; }
+}
+
+public class ChildObject
+{
+ public string Name { get; set; }
+
+ public ChildObject()
+ {
+ OptionsMarkupExtensionTests.ObjectsCreated++;
+ }
+}
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs b/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs
new file mode 100644
index 0000000000..6fc0f2d91c
--- /dev/null
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/SetterTests.cs
@@ -0,0 +1,51 @@
+using Avalonia.Controls;
+using Avalonia.Styling;
+using Avalonia.UnitTests;
+using Xunit;
+
+namespace Avalonia.Markup.Xaml.UnitTests;
+
+public class SetterTests : XamlTestBase
+{
+ [Fact]
+ public void SetterTargetType_Should_Understand_xType_Extensions()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+
+
+
+
+
+";
+ var animation = (Animation.Animation)AvaloniaRuntimeXamlLoader.Load(xaml);
+ var setter = (Setter)animation.Children[0].Setters[0];
+
+ Assert.Equal(typeof(ContentControl), setter.Property.OwnerType);
+ }
+ }
+
+ [Fact]
+ public void SetterTargetType_Should_Understand_Type_From_Xmlns()
+ {
+ using (UnitTestApplication.Start(TestServices.StyledWindow))
+ {
+ var xaml = @"
+
+
+
+
+
+
+
+";
+ var animation = (Animation.Animation)AvaloniaRuntimeXamlLoader.Load(xaml);
+ var setter = (Setter)animation.Children[0].Setters[0];
+
+ Assert.Equal(typeof(ContentControl), setter.Property.OwnerType);
+ }
+ }
+}
diff --git a/tests/Avalonia.Markup.Xaml.UnitTests/XamlTestBase.cs b/tests/Avalonia.Markup.Xaml.UnitTests/XamlTestBase.cs
index 2bc82d1353..2fc4867b35 100644
--- a/tests/Avalonia.Markup.Xaml.UnitTests/XamlTestBase.cs
+++ b/tests/Avalonia.Markup.Xaml.UnitTests/XamlTestBase.cs
@@ -20,8 +20,8 @@ namespace Avalonia.Markup.Xaml.UnitTests
class TestXamlLoaderShim : AvaloniaXamlLoader.IRuntimeXamlLoader
{
- public object Load(Stream stream, Assembly localAsm, object o, Uri baseUri, bool designMode)
- => AvaloniaRuntimeXamlLoader.Load(stream, localAsm, o, baseUri, designMode);
+ public object Load(Stream stream, RuntimeXamlLoaderConfiguration configuration)
+ => AvaloniaRuntimeXamlLoader.Load(stream, configuration);
}
}
}
diff --git a/tests/Avalonia.RenderTests/Media/GlyphRunTests.cs b/tests/Avalonia.RenderTests/Media/GlyphRunTests.cs
index 1b0193bfdb..31e485448e 100644
--- a/tests/Avalonia.RenderTests/Media/GlyphRunTests.cs
+++ b/tests/Avalonia.RenderTests/Media/GlyphRunTests.cs
@@ -50,6 +50,66 @@ namespace Avalonia.Direct2D1.RenderTests.Media
CompareImages();
}
+ [Win32Fact("For consistent results")]
+ public async Task Should_Render_GlyphRun_UnPositioned()
+ {
+ var control = new UnPositionedGlyphRunControl
+ {
+ [TextElement.ForegroundProperty] = new LinearGradientBrush
+ {
+ StartPoint = new RelativePoint(0, 0.5, RelativeUnit.Relative),
+ EndPoint = new RelativePoint(1, 0.5, RelativeUnit.Relative),
+ GradientStops =
+ {
+ new GradientStop { Color = Colors.Red, Offset = 0 },
+ new GradientStop { Color = Colors.Blue, Offset = 1 }
+ }
+ }
+ };
+
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(8),
+ Width = 190,
+ Height = 120,
+ Child = control
+ };
+
+ await RenderToFile(target);
+
+ CompareImages();
+ }
+
+ [Win32Fact("For consistent results")]
+ public async Task Should_Render_GlyphRun_Positioned()
+ {
+ var control = new PositionedGlyphRunControl
+ {
+ [TextElement.ForegroundProperty] = new LinearGradientBrush
+ {
+ StartPoint = new RelativePoint(0, 0.5, RelativeUnit.Relative),
+ EndPoint = new RelativePoint(1, 0.5, RelativeUnit.Relative),
+ GradientStops =
+ {
+ new GradientStop { Color = Colors.Red, Offset = 0 },
+ new GradientStop { Color = Colors.Blue, Offset = 1 }
+ }
+ }
+ };
+
+ Decorator target = new Decorator
+ {
+ Padding = new Thickness(8),
+ Width = 190,
+ Height = 120,
+ Child = control
+ };
+
+ await RenderToFile(target);
+
+ CompareImages();
+ }
+
public class GlyphRunGeometryControl : Control
{
public GlyphRunGeometryControl()
@@ -74,5 +134,58 @@ namespace Avalonia.Direct2D1.RenderTests.Media
context.DrawGeometry(foreground, null, Geometry);
}
}
+
+ public class UnPositionedGlyphRunControl : Control
+ {
+ public UnPositionedGlyphRunControl()
+ {
+ var glyphTypeface = new Typeface(TestFontFamily).GlyphTypeface;
+
+ var glyphIndices = new[] { glyphTypeface.GetGlyph('A'), glyphTypeface.GetGlyph('B'), glyphTypeface.GetGlyph('C') };
+
+ var characters = new[] { 'A', 'B', 'C' };
+
+ GlyphRun = new GlyphRun(glyphTypeface, 100, characters, glyphIndices);
+ }
+
+ public GlyphRun GlyphRun { get; }
+
+ public override void Render(DrawingContext context)
+ {
+ var foreground = TextElement.GetForeground(this);
+
+ context.DrawGlyphRun(foreground, GlyphRun);
+ }
+ }
+
+ public class PositionedGlyphRunControl : Control
+ {
+ public PositionedGlyphRunControl()
+ {
+ var glyphTypeface = new Typeface(TestFontFamily).GlyphTypeface;
+
+ var glyphIndices = new[] { glyphTypeface.GetGlyph('A'), glyphTypeface.GetGlyph('B'), glyphTypeface.GetGlyph('C') };
+
+ var scale = 100.0 / glyphTypeface.Metrics.DesignEmHeight;
+
+ var advance = glyphTypeface.GetGlyphAdvance(glyphIndices[0]) * scale;
+
+ var advances = new[] { advance, advance, advance};
+
+ var characters = new[] { 'A', 'B', 'C' };
+
+ GlyphRun = new GlyphRun(glyphTypeface, 100, characters, glyphIndices, advances);
+ }
+
+ public GlyphRun GlyphRun { get; }
+
+ public override void Render(DrawingContext context)
+ {
+ var foreground = TextElement.GetForeground(this);
+
+ context.DrawGlyphRun(foreground, GlyphRun);
+ }
+ }
+
}
}
diff --git a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs
index 7998c95877..a748f6cf00 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/CustomFontManagerImpl.cs
@@ -103,7 +103,7 @@ namespace Avalonia.Skia.UnitTests.Media
}
}
- return new GlyphTypefaceImpl(skTypeface);
+ return new GlyphTypefaceImpl(skTypeface, FontSimulations.None);
}
}
}
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
index a315158e1b..316926b00c 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextFormatterTests.cs
@@ -425,7 +425,7 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var defaultProperties = new GenericTextRunProperties(Typeface.Default);
var paragraphProperties = new GenericTextParagraphProperties(flowDirection, textAlignment, true, true,
- defaultProperties, TextWrapping.NoWrap, 0, 0);
+ defaultProperties, TextWrapping.NoWrap, 0, 0, 0);
var textSource = new SingleBufferTextSource(text, defaultProperties);
var formatter = new TextFormatterImpl();
diff --git a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
index 33c18c5064..87de9ed11f 100644
--- a/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
+++ b/tests/Avalonia.Skia.UnitTests/Media/TextFormatting/TextLineTests.cs
@@ -878,7 +878,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textLine =
formatter.FormatLine(textSource, 0, 200,
- new GenericTextParagraphProperties(FlowDirection.LeftToRight, TextAlignment.Left, true, true, defaultProperties, TextWrapping.NoWrap, 0, 0));
+ new GenericTextParagraphProperties(FlowDirection.LeftToRight, TextAlignment.Left,
+ true, true, defaultProperties, TextWrapping.NoWrap, 0, 0, 0));
var textBounds = textLine.GetTextBounds(0, 3);
@@ -924,7 +925,8 @@ namespace Avalonia.Skia.UnitTests.Media.TextFormatting
var textLine =
formatter.FormatLine(textSource, 0, 200,
- new GenericTextParagraphProperties(FlowDirection.RightToLeft, TextAlignment.Left, true, true, defaultProperties, TextWrapping.NoWrap, 0, 0));
+ new GenericTextParagraphProperties(FlowDirection.RightToLeft, TextAlignment.Left,
+ true, true, defaultProperties, TextWrapping.NoWrap, 0, 0, 0));
var textBounds = textLine.GetTextBounds(0, 4);
diff --git a/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs b/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs
index 3bcbc2efbd..5b11345f16 100644
--- a/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs
+++ b/tests/Avalonia.UnitTests/HarfBuzzGlyphTypefaceImpl.cs
@@ -10,7 +10,7 @@ namespace Avalonia.UnitTests
private bool _isDisposed;
private Blob _blob;
- public HarfBuzzGlyphTypefaceImpl(Stream data, bool isFakeBold = false, bool isFakeItalic = false)
+ public HarfBuzzGlyphTypefaceImpl(Stream data)
{
_blob = Blob.FromStream(data);
@@ -45,10 +45,6 @@ namespace Avalonia.UnitTests
};
GlyphCount = Face.GlyphCount;
-
- IsFakeBold = isFakeBold;
-
- IsFakeItalic = isFakeItalic;
}
public FontMetrics Metrics { get; }
@@ -58,10 +54,8 @@ namespace Avalonia.UnitTests
public Font Font { get; }
public int GlyphCount { get; set; }
-
- public bool IsFakeBold { get; }
-
- public bool IsFakeItalic { get; }
+
+ public FontSimulations FontSimulations { get; }
///
public ushort GetGlyph(uint codepoint)
@@ -162,5 +156,25 @@ namespace Avalonia.UnitTests
Dispose(true);
GC.SuppressFinalize(this);
}
+
+ public bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics)
+ {
+ metrics = default;
+
+ if (!Font.TryGetGlyphExtents(glyph, out var extents))
+ {
+ return false;
+ }
+
+ metrics = new GlyphMetrics
+ {
+ XBearing = extents.XBearing,
+ YBearing = extents.YBearing,
+ Width = extents.Width,
+ Height = extents.Height
+ };
+
+ return true;
+ }
}
}
diff --git a/tests/Avalonia.UnitTests/MockGlyphRun.cs b/tests/Avalonia.UnitTests/MockGlyphRun.cs
new file mode 100644
index 0000000000..24948aff01
--- /dev/null
+++ b/tests/Avalonia.UnitTests/MockGlyphRun.cs
@@ -0,0 +1,12 @@
+using Avalonia.Platform;
+
+namespace Avalonia.UnitTests
+{
+ public class MockGlyphRun : IGlyphRunImpl
+ {
+ public void Dispose()
+ {
+
+ }
+ }
+}
diff --git a/tests/Avalonia.UnitTests/MockGlyphTypeface.cs b/tests/Avalonia.UnitTests/MockGlyphTypeface.cs
index a1c492a7f1..bd9d8e5adf 100644
--- a/tests/Avalonia.UnitTests/MockGlyphTypeface.cs
+++ b/tests/Avalonia.UnitTests/MockGlyphTypeface.cs
@@ -15,6 +15,8 @@ namespace Avalonia.UnitTests
public int GlyphCount => 1337;
+ public FontSimulations FontSimulations => throw new NotImplementedException();
+
public ushort GetGlyph(uint codepoint)
{
return (ushort)codepoint;
@@ -56,5 +58,16 @@ namespace Avalonia.UnitTests
table = null;
return false;
}
+
+ public bool TryGetGlyphMetrics(ushort glyph, out GlyphMetrics metrics)
+ {
+ metrics = new GlyphMetrics
+ {
+ Width = 10,
+ Height = 10
+ };
+
+ return true;
+ }
}
}
diff --git a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
index 586436ef7f..0f951ed867 100644
--- a/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
+++ b/tests/Avalonia.UnitTests/MockPlatformRenderInterface.cs
@@ -142,7 +142,7 @@ namespace Avalonia.UnitTests
throw new NotImplementedException();
}
- public IGlyphRunImpl CreateGlyphRun(GlyphRun glyphRun)
+ public IGlyphRunImpl CreateGlyphRun(IGlyphTypeface glyphTypeface, double fontRenderingEmSize, IReadOnlyList glyphIndices, IReadOnlyList glyphAdvances, IReadOnlyList glyphOffsets)
{
return Mock.Of();
}
diff --git a/tests/TestFiles/Direct2D1/Media/GlyphRun/Should_Render_GlyphRun_Positioned.expected.png b/tests/TestFiles/Direct2D1/Media/GlyphRun/Should_Render_GlyphRun_Positioned.expected.png
new file mode 100644
index 0000000000..913266b652
Binary files /dev/null and b/tests/TestFiles/Direct2D1/Media/GlyphRun/Should_Render_GlyphRun_Positioned.expected.png differ
diff --git a/tests/TestFiles/Direct2D1/Media/GlyphRun/Should_Render_GlyphRun_UnPositioned.expected.png b/tests/TestFiles/Direct2D1/Media/GlyphRun/Should_Render_GlyphRun_UnPositioned.expected.png
new file mode 100644
index 0000000000..913266b652
Binary files /dev/null and b/tests/TestFiles/Direct2D1/Media/GlyphRun/Should_Render_GlyphRun_UnPositioned.expected.png differ
diff --git a/tests/TestFiles/Skia/Media/GlyphRun/Should_Render_GlyphRun_Positioned.expected.png b/tests/TestFiles/Skia/Media/GlyphRun/Should_Render_GlyphRun_Positioned.expected.png
new file mode 100644
index 0000000000..4b8371541e
Binary files /dev/null and b/tests/TestFiles/Skia/Media/GlyphRun/Should_Render_GlyphRun_Positioned.expected.png differ
diff --git a/tests/TestFiles/Skia/Media/GlyphRun/Should_Render_GlyphRun_UnPositioned.expected.png b/tests/TestFiles/Skia/Media/GlyphRun/Should_Render_GlyphRun_UnPositioned.expected.png
new file mode 100644
index 0000000000..4b8371541e
Binary files /dev/null and b/tests/TestFiles/Skia/Media/GlyphRun/Should_Render_GlyphRun_UnPositioned.expected.png differ