Browse Source

Merge remote-tracking branch 'origin/master' into platform-screen-api

# Conflicts:
#	src/Avalonia.Controls/ApiCompatBaseline.txt
pull/7165/head
Dan Walmsley 4 years ago
parent
commit
ab643f9ab4
  1. 2
      .gitignore
  2. 27
      Avalonia.sln
  3. 2
      build/SharedVersion.props
  4. 2
      native/Avalonia.Native/src/OSX/window.mm
  5. 2
      readme.md
  6. 15
      samples/ControlCatalog.iOS.Legacy/AppDelegate.cs
  7. 117
      samples/ControlCatalog.iOS.Legacy/Assets.xcassets/AppIcon.appiconset/Contents.json
  8. 99
      samples/ControlCatalog.iOS.Legacy/ControlCatalog.iOS.Legacy.csproj
  9. 6
      samples/ControlCatalog.iOS.Legacy/Entitlements.plist
  10. 47
      samples/ControlCatalog.iOS.Legacy/Info.plist
  11. 15
      samples/ControlCatalog.iOS.Legacy/Main.cs
  12. 43
      samples/ControlCatalog.iOS.Legacy/Resources/LaunchScreen.xib
  13. 10
      samples/ControlCatalog.iOS/Info.plist
  14. 3
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  15. 6
      samples/ControlCatalog/Pages/TextBoxPage.xaml.cs
  16. 3
      samples/RenderDemo/MainWindow.xaml
  17. 71
      samples/RenderDemo/Pages/BrushesPage.axaml
  18. 18
      samples/RenderDemo/Pages/BrushesPage.axaml.cs
  19. 23
      src/Android/Avalonia.Android/AndroidInputMethod.cs
  20. 3
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  21. 1
      src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt
  22. 2
      src/Avalonia.Controls/ApiCompatBaseline.txt
  23. 4
      src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs
  24. 5
      src/Avalonia.Controls/TextBox.cs
  25. 72
      src/Avalonia.Controls/Viewbox.cs
  26. 1
      src/Avalonia.DesignerSupport/ApiCompatBaseline.txt
  27. 2
      src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/index.html
  28. 6
      src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs
  29. 4
      src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs
  30. 2
      src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs
  31. 17
      src/Avalonia.Input/ApiCompatBaseline.txt
  32. 17
      src/Avalonia.Input/InputElement.cs
  33. 1
      src/Avalonia.Input/Properties/AssemblyInfo.cs
  34. 4
      src/Avalonia.Input/TextInput/ITextInputMethodImpl.cs
  35. 30
      src/Avalonia.Input/TextInput/InputMethodManager.cs
  36. 62
      src/Avalonia.Input/TextInput/TextInputContentType.cs
  37. 220
      src/Avalonia.Input/TextInput/TextInputOptions.cs
  38. 32
      src/Avalonia.Input/TextInput/TextInputOptionsQueryEventArgs.cs
  39. 1
      src/Avalonia.Interactivity/ApiCompatBaseline.txt
  40. 1
      src/Avalonia.Layout/ApiCompatBaseline.txt
  41. 2
      src/Avalonia.ReactiveUI/RoutedViewHost.cs
  42. 1
      src/Avalonia.Remote.Protocol/ApiCompatBaseline.txt
  43. 1
      src/Avalonia.Themes.Default/ApiCompatBaseline.txt
  44. 1
      src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt
  45. 6
      src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs
  46. 10
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  47. 17
      src/Avalonia.Visuals/Media/Brush.cs
  48. 11
      src/Avalonia.Visuals/Media/Color.cs
  49. 478
      src/Avalonia.Visuals/Media/HsvColor.cs
  50. 5
      src/Avalonia.Visuals/Media/IBrush.cs
  51. 4
      src/Avalonia.Visuals/Media/Immutable/ImmutableConicGradientBrush.cs
  52. 10
      src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs
  53. 3
      src/Avalonia.Visuals/Media/Immutable/ImmutableImageBrush.cs
  54. 4
      src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs
  55. 4
      src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs
  56. 15
      src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs
  57. 9
      src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs
  58. 21
      src/Avalonia.Visuals/Media/Immutable/ImmutableTransform.cs
  59. 3
      src/Avalonia.Visuals/Media/Immutable/ImmutableVisualBrush.cs
  60. 10
      src/Avalonia.Visuals/Media/Transform.cs
  61. 26
      src/Avalonia.Visuals/Media/TransformExtensions.cs
  62. 31
      src/Avalonia.Visuals/Rendering/SceneGraph/GeometryBoundsHelper.cs
  63. 2
      src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs
  64. 2
      src/Avalonia.X11/ICELib.cs
  65. 2
      src/Avalonia.X11/SMLib.cs
  66. 4
      src/Avalonia.X11/X11PlatformLifetimeEvents.cs
  67. 7
      src/Avalonia.X11/X11Window.Xim.cs
  68. 1
      src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt
  69. 1
      src/Markup/Avalonia.Markup/ApiCompatBaseline.txt
  70. 67
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  71. 6
      src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs
  72. 2
      src/Web/Avalonia.Web.Blazor/Interop/Typescript/types/dotnet/index.d.ts
  73. 8
      src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs
  74. 11
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  75. 60
      src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs
  76. 6
      src/Windows/Avalonia.Win32/WinRT/winrt.idl
  77. 83
      src/Windows/Avalonia.Win32/WindowImpl.cs
  78. 140
      src/iOS/Avalonia.iOS/AvaloniaView.Text.cs
  79. 7
      src/iOS/Avalonia.iOS/AvaloniaView.cs
  80. 7
      src/iOS/Avalonia.iOS/Platform.cs
  81. 24
      src/iOS/Avalonia.iOS/SoftKeyboardHelper.cs
  82. 16
      tests/Avalonia.Controls.UnitTests/ViewboxTests.cs
  83. 4
      tests/Avalonia.IntegrationTests.Appium/readme.md

2
.gitignore

@ -192,7 +192,7 @@ dirs.sln
##################
# XCode
# Xcode
##################
Index/
Logs/

27
Avalonia.sln

@ -234,8 +234,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.PlatformSupport",
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.iOS", "samples\ControlCatalog.iOS\ControlCatalog.iOS.csproj", "{70B9F5CC-E2F9-4314-9514-EDE762ACCC4B}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "ControlCatalog.iOS.Legacy", "samples\ControlCatalog.iOS.Legacy\ControlCatalog.iOS.Legacy.csproj", "{3AF75F00-B497-4517-9491-922173DE216E}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Ad-Hoc|Any CPU = Ad-Hoc|Any CPU
@ -2214,30 +2212,6 @@ Global
{70B9F5CC-E2F9-4314-9514-EDE762ACCC4B}.Release|iPhone.Build.0 = Release|Any CPU
{70B9F5CC-E2F9-4314-9514-EDE762ACCC4B}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{70B9F5CC-E2F9-4314-9514-EDE762ACCC4B}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{3AF75F00-B497-4517-9491-922173DE216E}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{3AF75F00-B497-4517-9491-922173DE216E}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{3AF75F00-B497-4517-9491-922173DE216E}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{3AF75F00-B497-4517-9491-922173DE216E}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{3AF75F00-B497-4517-9491-922173DE216E}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{3AF75F00-B497-4517-9491-922173DE216E}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{3AF75F00-B497-4517-9491-922173DE216E}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{3AF75F00-B497-4517-9491-922173DE216E}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{3AF75F00-B497-4517-9491-922173DE216E}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{3AF75F00-B497-4517-9491-922173DE216E}.AppStore|iPhone.Build.0 = Debug|Any CPU
{3AF75F00-B497-4517-9491-922173DE216E}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{3AF75F00-B497-4517-9491-922173DE216E}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{3AF75F00-B497-4517-9491-922173DE216E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{3AF75F00-B497-4517-9491-922173DE216E}.Debug|Any CPU.Build.0 = Debug|Any CPU
{3AF75F00-B497-4517-9491-922173DE216E}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{3AF75F00-B497-4517-9491-922173DE216E}.Debug|iPhone.Build.0 = Debug|Any CPU
{3AF75F00-B497-4517-9491-922173DE216E}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{3AF75F00-B497-4517-9491-922173DE216E}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{3AF75F00-B497-4517-9491-922173DE216E}.Release|Any CPU.ActiveCfg = Release|Any CPU
{3AF75F00-B497-4517-9491-922173DE216E}.Release|Any CPU.Build.0 = Release|Any CPU
{3AF75F00-B497-4517-9491-922173DE216E}.Release|iPhone.ActiveCfg = Release|Any CPU
{3AF75F00-B497-4517-9491-922173DE216E}.Release|iPhone.Build.0 = Release|Any CPU
{3AF75F00-B497-4517-9491-922173DE216E}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{3AF75F00-B497-4517-9491-922173DE216E}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@ -2302,7 +2276,6 @@ Global
{26A98DA1-D89D-4A95-8152-349F404DA2E2} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
{A0D0A6A4-5C72-4ADA-9B27-621C7D94F270} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{70B9F5CC-E2F9-4314-9514-EDE762ACCC4B} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{3AF75F00-B497-4517-9491-922173DE216E} = {9B9E3891-2366-4253-A952-D08BCEB71098}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {87366D66-1391-4D90-8999-95A620AD786A}

2
build/SharedVersion.props

@ -11,7 +11,7 @@
<LangVersion>latest</LangVersion>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageIcon>Icon.png</PackageIcon>
<PackageDescription>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.</PackageDescription>
<PackageDescription>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.</PackageDescription>
<PackageTags>avalonia;avaloniaui;mvvm;rx;reactive extensions;android;ios;mac;forms;wpf;net;netstandard;net461;uwp;xamarin</PackageTags>
<PackageReleaseNotes>https://github.com/AvaloniaUI/Avalonia/releases</PackageReleaseNotes>
<RepositoryType>git</RepositoryType>

2
native/Avalonia.Native/src/OSX/window.mm

@ -735,7 +735,7 @@ private:
return E_INVALIDARG;
// If one tries to show a child window with a minimized parent window, then the parent window will be
// restored but MacOS isn't kind enough to *tell* us that, so the window will be left in a non-interactive
// restored but macOS isn't kind enough to *tell* us that, so the window will be left in a non-interactive
// state. Detect this and explicitly restore the parent window ourselves to avoid this situation.
if (cparent->WindowState() == Minimized)
cparent->SetWindowState(Normal);

2
readme.md

@ -5,7 +5,7 @@
## 📖 About
Avalonia is a cross-platform UI framework for dotnet, providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, MacOs. Avalonia is mature and production ready. We also have in beta release support for iOS, Android and in early stages support for browser via WASM.
Avalonia is a cross-platform UI framework for dotnet, providing a flexible styling system and supporting a wide range of Operating Systems such as Windows, Linux, macOS. Avalonia is mature and production ready. We also have in beta release support for iOS, Android and in early stages support for browser via WASM.
![image](https://user-images.githubusercontent.com/4672627/152126443-932966cf-57e7-4e77-9be6-62463a66b9f8.png)

15
samples/ControlCatalog.iOS.Legacy/AppDelegate.cs

@ -1,15 +0,0 @@
using Avalonia.iOS;
using Foundation;
using UIKit;
namespace ControlCatalog.iOS.Legacy
{
// The UIApplicationDelegate for the application. This class is responsible for launching the
// User Interface of the application, as well as listening (and optionally responding) to
// application events from iOS.
[Register("AppDelegate")]
public partial class AppDelegate : AvaloniaAppDelegate<App>
{
}
}

117
samples/ControlCatalog.iOS.Legacy/Assets.xcassets/AppIcon.appiconset/Contents.json

@ -1,117 +0,0 @@
{
"images": [
{
"scale": "2x",
"size": "20x20",
"idiom": "iphone",
"filename": "Icon40.png"
},
{
"scale": "3x",
"size": "20x20",
"idiom": "iphone",
"filename": "Icon60.png"
},
{
"scale": "2x",
"size": "29x29",
"idiom": "iphone",
"filename": "Icon58.png"
},
{
"scale": "3x",
"size": "29x29",
"idiom": "iphone",
"filename": "Icon87.png"
},
{
"scale": "2x",
"size": "40x40",
"idiom": "iphone",
"filename": "Icon80.png"
},
{
"scale": "3x",
"size": "40x40",
"idiom": "iphone",
"filename": "Icon120.png"
},
{
"scale": "2x",
"size": "60x60",
"idiom": "iphone",
"filename": "Icon120.png"
},
{
"scale": "3x",
"size": "60x60",
"idiom": "iphone",
"filename": "Icon180.png"
},
{
"scale": "1x",
"size": "20x20",
"idiom": "ipad",
"filename": "Icon20.png"
},
{
"scale": "2x",
"size": "20x20",
"idiom": "ipad",
"filename": "Icon40.png"
},
{
"scale": "1x",
"size": "29x29",
"idiom": "ipad",
"filename": "Icon29.png"
},
{
"scale": "2x",
"size": "29x29",
"idiom": "ipad",
"filename": "Icon58.png"
},
{
"scale": "1x",
"size": "40x40",
"idiom": "ipad",
"filename": "Icon40.png"
},
{
"scale": "2x",
"size": "40x40",
"idiom": "ipad",
"filename": "Icon80.png"
},
{
"scale": "1x",
"size": "76x76",
"idiom": "ipad",
"filename": "Icon76.png"
},
{
"scale": "2x",
"size": "76x76",
"idiom": "ipad",
"filename": "Icon152.png"
},
{
"scale": "2x",
"size": "83.5x83.5",
"idiom": "ipad",
"filename": "Icon167.png"
},
{
"scale": "1x",
"size": "1024x1024",
"idiom": "ios-marketing",
"filename": "Icon1024.png"
}
],
"properties": {},
"info": {
"version": 1,
"author": "xcode"
}
}

99
samples/ControlCatalog.iOS.Legacy/ControlCatalog.iOS.Legacy.csproj

@ -1,99 +0,0 @@
<Project Sdk="Xamarin.Legacy.Sdk">
<PropertyGroup>
<TargetFramework>xamarin.ios10</TargetFramework>
<SupportedOSPlatformVersion>15.0</SupportedOSPlatformVersion>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">iPhoneSimulator</Platform>
<ProjectGuid>{3AF75F00-B497-4517-9491-922173DE216E}</ProjectGuid>
<ProjectTypeGuids>{FEACFBD2-3405-455C-9665-78FE426C6842};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}</ProjectTypeGuids>
<OutputType>Exe</OutputType>
<RootNamespace>ControlCatalog.iOS.Legacy</RootNamespace>
<IPhoneResourcePrefix>Resources</IPhoneResourcePrefix>
<AssemblyName>ControlCatalog.iOS.Legacy</AssemblyName>
<MtouchHttpClientHandler>NSUrlSessionHandler</MtouchHttpClientHandler>
<ProvisioningType>manual</ProvisioningType>
<MtouchInterpreter>-all</MtouchInterpreter>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhoneSimulator' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\iPhoneSimulator\Debug</OutputPath>
<DefineConstants>DEBUG</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
<MtouchArch>x86_64</MtouchArch>
<MtouchLink>None</MtouchLink>
<MtouchDebug>true</MtouchDebug>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhoneSimulator' ">
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\iPhoneSimulator\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<MtouchLink>None</MtouchLink>
<MtouchArch>x86_64</MtouchArch>
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|iPhone' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\iPhone\Debug</OutputPath>
<DefineConstants>DEBUG</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
<MtouchArch>ARM64</MtouchArch>
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
<MtouchDebug>true</MtouchDebug>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|iPhone' ">
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\iPhone\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<CodesignEntitlements>Entitlements.plist</CodesignEntitlements>
<MtouchArch>ARM64</MtouchArch>
<ConsolePause>false</ConsolePause>
</PropertyGroup>
<ItemGroup>
<None Include="Info.plist" />
<Content Include="Entitlements.plist" />
</ItemGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Xml" />
<Reference Include="System.Core" />
<Reference Include="Xamarin.iOS" />
</ItemGroup>
<ItemGroup>
<ImageAsset Include="Assets.xcassets\AppIcon.appiconset\Contents.json">
<Visible>false</Visible>
</ImageAsset>
</ItemGroup>
<ItemGroup>
<Folder Include="Resources\" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\..\src\Avalonia.Controls\Avalonia.Controls.csproj">
<Project>{d2221c82-4a25-4583-9b43-d791e3f6820c}</Project>
<Name>Avalonia.Controls</Name>
</ProjectReference>
<ProjectReference Include="..\..\src\iOS\Avalonia.iOS\Avalonia.iOS.csproj">
<Project>{4488ad85-1495-4809-9aa4-ddfe0a48527e}</Project>
<Name>Avalonia.iOS</Name>
</ProjectReference>
<ProjectReference Include="..\ControlCatalog\ControlCatalog.csproj">
<Project>{d0a739b9-3c68-4ba6-a328-41606954b6bd}</Project>
<Name>ControlCatalog</Name>
</ProjectReference>
</ItemGroup>
<!-- <Import Project="..\..\build\LegacyProject.targets" />-->
<!-- <Import Project="..\..\build\SkiaSharp.props" />-->
<!-- <Import Project="..\..\build\HarfBuzzSharp.props" />-->
</Project>

6
samples/ControlCatalog.iOS.Legacy/Entitlements.plist

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
</dict>
</plist>

47
samples/ControlCatalog.iOS.Legacy/Info.plist

@ -1,47 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDisplayName</key>
<string>ControlCatalog</string>
<key>CFBundleIdentifier</key>
<string>com.companyname.ControlCatalog.iOS</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1.0</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>MinimumOSVersion</key>
<string>15.0</string>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer><integer>2</integer>
</array>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>UISupportedInterfaceOrientations</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UISupportedInterfaceOrientations~ipad</key>
<array>
<string>UIInterfaceOrientationPortrait</string>
<string>UIInterfaceOrientationPortraitUpsideDown</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/AppIcon.appiconset</string>
<key>UIStatusBarHidden</key>
<true/>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>

15
samples/ControlCatalog.iOS.Legacy/Main.cs

@ -1,15 +0,0 @@
using UIKit;
namespace ControlCatalog.iOS.Legacy
{
public class Application
{
// This is the main entry point of the application.
static void Main(string[] args)
{
// if you want to use a different Application Delegate class from "AppDelegate"
// you can specify it here.
UIApplication.Main(args, null, "AppDelegate");
}
}
}

43
samples/ControlCatalog.iOS.Legacy/Resources/LaunchScreen.xib

@ -1,43 +0,0 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="6214" systemVersion="14A314h" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="6207" />
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1" />
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner" />
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder" />
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="480" height="480" />
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES" />
<subviews>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text=" Copyright (c) 2022 " textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines"
minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
<rect key="frame" x="20" y="439" width="441" height="21" />
<fontDescription key="fontDescription" type="system" pointSize="17" />
<color key="textColor" cocoaTouchSystemColor="darkTextColor" />
<nil key="highlightedColor" />
</label>
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="ControlCatalog.iOS.Legacy" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines"
minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
<rect key="frame" x="20" y="140" width="441" height="43" />
<fontDescription key="fontDescription" type="boldSystem" pointSize="36" />
<color key="textColor" cocoaTouchSystemColor="darkTextColor" />
<nil key="highlightedColor" />
</label>
</subviews>
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite" />
<constraints>
<constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC" />
<constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk" />
<constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Kzo-t9-V3l" />
<constraint firstItem="8ie-xW-0ye" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="MfP-vx-nX0" />
<constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9" />
<constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g" />
</constraints>
<nil key="simulatedStatusBarMetrics" />
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics" />
<point key="canvasLocation" x="548" y="455" />
</view>
</objects>
</document>

10
samples/ControlCatalog.iOS/Info.plist

@ -5,7 +5,7 @@
<key>CFBundleDisplayName</key>
<string>ControlCatalog.iOS</string>
<key>CFBundleIdentifier</key>
<string>com.companyname.ControlCatalog.iOS</string>
<string>Avalonia.ControlCatalog</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
@ -39,9 +39,9 @@
<string>UIInterfaceOrientationLandscapeLeft</string>
<string>UIInterfaceOrientationLandscapeRight</string>
</array>
<key>UIStatusBarHidden</key>
<true/>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
<key>UIStatusBarHidden</key>
<true/>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/>
</dict>
</plist>

3
samples/ControlCatalog/Pages/TextBoxPage.xaml

@ -18,7 +18,7 @@
</TextBox.ContextFlyout>
</TextBox>
<TextBox Width="200" Watermark="ReadOnly" IsReadOnly="True" Text="This is read only"/>
<TextBox Width="200" Watermark="Numeric with watermark" x:Name="numericWatermark"/>
<TextBox Width="200" Watermark="Numeric with watermark" TextInputOptions.ContentType="Number" />
<TextBox Width="200"
Watermark="Floating Watermark"
UseFloatingWatermark="True"
@ -34,6 +34,7 @@
<TextBox Width="200"
Watermark="Password Box"
Classes="revealPasswordButton"
TextInputOptions.ContentType="Password"
UseFloatingWatermark="True"
PasswordChar="*"
Text="Password" />

6
samples/ControlCatalog/Pages/TextBoxPage.xaml.cs

@ -13,12 +13,6 @@ namespace ControlCatalog.Pages
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
this.Get<TextBox>("numericWatermark")
.TextInputOptionsQuery += (s, a) =>
{
a.ContentType = Avalonia.Input.TextInput.TextInputContentType.Number;
};
}
}
}

3
samples/RenderDemo/MainWindow.xaml

@ -66,5 +66,8 @@
<TabItem Header="Path Measurement">
<pages:PathMeasurementPage />
</TabItem>
<TabItem Header="Brushes">
<pages:BrushesPage />
</TabItem>
</controls:HamburgerMenu>
</Window>

71
samples/RenderDemo/Pages/BrushesPage.axaml

@ -0,0 +1,71 @@
<UserControl 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"
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
x:Class="RenderDemo.Pages.BrushesPage">
<Canvas Background="White" Width="480" Height="360">
<Rectangle Canvas.Left="20" Canvas.Top="20" Width="440" Height="50">
<Rectangle.Fill>
<LinearGradientBrush StartPoint="0,0" EndPoint="410,0">
<LinearGradientBrush.Transform>
<TransformGroup>
<ScaleTransform ScaleX="0.5" />
<SkewTransform />
<RotateTransform />
<TranslateTransform X="5" Y="15" />
</TransformGroup>
</LinearGradientBrush.Transform>
<LinearGradientBrush.GradientStops>
<GradientStop Color="Blue" Offset="0" />
<GradientStop Color="Green" Offset="0.5" />
<GradientStop Color="Lime" Offset="1" />
</LinearGradientBrush.GradientStops>
</LinearGradientBrush>
</Rectangle.Fill>
</Rectangle>
<TextBlock Canvas.Left="20" Canvas.Top="70" FontSize="30" Text="scale(0.5) on gradient" />
<Rectangle Canvas.Left="20" Canvas.Top="110" Width="440" Height="50">
<Rectangle.Fill>
<RadialGradientBrush Center="0.0,0.0" GradientOrigin="0.0,0.0" Radius="0.13636364">
<RadialGradientBrush.Transform>
<TransformGroup>
<ScaleTransform />
<SkewTransform AngleX="45" />
<RotateTransform />
<TranslateTransform X="240" Y="45" />
</TransformGroup>
</RadialGradientBrush.Transform>
<RadialGradientBrush.GradientStops>
<GradientStop Color="Black" Offset="0" />
<GradientStop Color="#FFFFA500" Offset="1" />
</RadialGradientBrush.GradientStops>
</RadialGradientBrush>
</Rectangle.Fill>
</Rectangle>
<TextBlock Canvas.Left="20" Canvas.Top="160" FontSize="30" Text="skewX(45) on gradient" />
<Rectangle Canvas.Left="20" Canvas.Top="210" Width="440" Height="50">
<Rectangle.Fill>
<VisualBrush TileMode="Tile" SourceRect="0,0,20,20" DestinationRect="0,0,20,20" Stretch="None">
<VisualBrush.Transform>
<TransformGroup>
<ScaleTransform ScaleX="2" ScaleY="2" />
<SkewTransform AngleX="45" />
<RotateTransform />
<TranslateTransform X="5" Y="5" />
</TransformGroup>
</VisualBrush.Transform>
<VisualBrush.Visual>
<Canvas Width="20" Height="20">
<Rectangle Canvas.Left="0" Canvas.Top="0" Width="10" Height="10" Fill="Maroon" />
<Rectangle Canvas.Left="10" Canvas.Top="0" Width="10" Height="10" Fill="Green" />
<Rectangle Canvas.Left="0" Canvas.Top="10" Width="10" Height="10" Fill="Blue" />
<Rectangle Canvas.Left="10" Canvas.Top="10" Width="10" Height="10" Fill="Yellow" />
</Canvas>
</VisualBrush.Visual>
</VisualBrush>
</Rectangle.Fill>
</Rectangle>
<TextBlock Canvas.Left="20" Canvas.Top="260" FontSize="30" Text="scale(2), skewX(45) on pattern" />
</Canvas>
</UserControl>

18
samples/RenderDemo/Pages/BrushesPage.axaml.cs

@ -0,0 +1,18 @@
using Avalonia.Controls;
using Avalonia.Markup.Xaml;
namespace RenderDemo.Pages;
public class BrushesPage : UserControl
{
public BrushesPage()
{
InitializeComponent();
}
private void InitializeComponent()
{
AvaloniaXamlLoader.Load(this);
}
}

23
src/Android/Avalonia.Android/AndroidInputMethod.cs

@ -13,7 +13,6 @@ namespace Avalonia.Android
{
private readonly TView _host;
private readonly InputMethodManager _imm;
private IInputElement _inputElement;
public AndroidInputMethod(TView host)
{
@ -33,8 +32,10 @@ namespace Avalonia.Android
_imm.RestartInput(_host);
}
public void SetActive(bool active)
public void SetClient(ITextInputMethodClient client)
{
var active = client is { };
if (active)
{
_host.RequestFocus();
@ -49,20 +50,8 @@ namespace Avalonia.Android
{
}
public void SetOptions(TextInputOptionsQueryEventArgs options)
public void SetOptions(TextInputOptions options)
{
if (_inputElement != null)
{
_inputElement.PointerReleased -= RestoreSoftKeyboard;
}
_inputElement = options.Source as InputElement;
if (_inputElement == null)
{
_imm.HideSoftInputFromWindow(_host.WindowToken, HideSoftInputFlags.None);
}
_host.InitEditorInfo((outAttrs) =>
{
outAttrs.InputType = options.ContentType switch
@ -70,7 +59,7 @@ namespace Avalonia.Android
TextInputContentType.Email => global::Android.Text.InputTypes.TextVariationEmailAddress,
TextInputContentType.Number => global::Android.Text.InputTypes.ClassNumber,
TextInputContentType.Password => global::Android.Text.InputTypes.TextVariationPassword,
TextInputContentType.Phone => global::Android.Text.InputTypes.ClassPhone,
TextInputContentType.Digits => global::Android.Text.InputTypes.ClassPhone,
TextInputContentType.Url => global::Android.Text.InputTypes.TextVariationUri,
_ => global::Android.Text.InputTypes.ClassText
};
@ -86,8 +75,6 @@ namespace Avalonia.Android
outAttrs.ImeOptions |= ImeFlags.NoFullscreen | ImeFlags.NoExtractUi;
});
//_inputElement.PointerReleased += RestoreSoftKeyboard;
}
private void RestoreSoftKeyboard(object sender, PointerReleasedEventArgs e)

3
src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj

@ -83,6 +83,9 @@
<Compile Include="../Avalonia.Visuals/Media/Color.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="../Avalonia.Visuals/Media/HsvColor.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>
<Compile Include="../Avalonia.Visuals/Media/KnownColors.cs">
<Link>Markup/%(RecursiveDir)%(FileName)%(Extension)</Link>
</Compile>

1
src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt

@ -0,0 +1 @@
Total Issues: 0

2
src/Avalonia.Controls/ApiCompatBaseline.txt

@ -30,6 +30,7 @@ MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownV
MembersMustExist : Member 'public System.Double Avalonia.Controls.NumericUpDownValueChangedEventArgs.OldValue.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.StyledProperty<System.Boolean> Avalonia.StyledProperty<System.Boolean> Avalonia.Controls.ScrollViewer.AllowAutoHideProperty' does not exist in the implementation but it does exist in the contract.
CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.TopLevel' does not implement interface 'Avalonia.Utilities.IWeakSubscriber<Avalonia.Controls.ResourcesChangedEventArgs>' in the implementation but it does in the contract.
CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.Viewbox' does not inherit from base type 'Avalonia.Controls.Decorator' in the implementation but it does in the contract.
MembersMustExist : Member 'public Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.AvaloniaProperty<Avalonia.Media.Stretch> Avalonia.Controls.Viewbox.StretchProperty' does not exist in the implementation but it does exist in the contract.
CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.Window' does not implement interface 'Avalonia.Utilities.IWeakSubscriber<Avalonia.Controls.ResourcesChangedEventArgs>' in the implementation but it does in the contract.
CannotRemoveBaseTypeOrInterface : Type 'Avalonia.Controls.WindowBase' does not implement interface 'Avalonia.Utilities.IWeakSubscriber<Avalonia.Controls.ResourcesChangedEventArgs>' in the implementation but it does in the contract.
@ -71,5 +72,6 @@ InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platfor
MembersMustExist : Member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Platform.IWindowImpl.Resize(Avalonia.Size, Avalonia.Platform.PlatformResizeReason)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.ITrayIconImpl Avalonia.Platform.IWindowingPlatform.CreateTrayIcon()' is present in the implementation but not in the contract.
Total Issues: 70
Total Issues: 69
Total Issues: 66

4
src/Avalonia.Controls/Primitives/PopupPositioning/ManagedPopupPositioner.cs

@ -106,9 +106,9 @@ namespace Avalonia.Controls.Primitives.PopupPositioning
{
var screens = _popup.Screens;
var targetScreen = screens.FirstOrDefault(s => s.Bounds.Contains(anchorRect.TopLeft))
var targetScreen = screens.FirstOrDefault(s => s.Bounds.ContainsExclusive(anchorRect.TopLeft))
?? screens.FirstOrDefault(s => s.Bounds.Intersects(anchorRect))
?? screens.FirstOrDefault(s => s.Bounds.Contains(parentGeometry.TopLeft))
?? screens.FirstOrDefault(s => s.Bounds.ContainsExclusive(parentGeometry.TopLeft))
?? screens.FirstOrDefault(s => s.Bounds.Intersects(parentGeometry))
?? screens.FirstOrDefault();

5
src/Avalonia.Controls/TextBox.cs

@ -202,7 +202,10 @@ namespace Avalonia.Controls
FocusableProperty.OverrideDefaultValue(typeof(TextBox), true);
TextInputMethodClientRequestedEvent.AddClassHandler<TextBox>((tb, e) =>
{
e.Client = tb._imClient;
if (!tb.IsReadOnly)
{
e.Client = tb._imClient;
}
});
}

72
src/Avalonia.Controls/Viewbox.cs

@ -1,13 +1,15 @@
using Avalonia.Media;
using Avalonia.Metadata;
namespace Avalonia.Controls
{
/// <summary>
/// Viewbox is used to scale single child to fit in the available space.
/// </summary>
/// <seealso cref="Avalonia.Controls.Decorator" />
public class Viewbox : Decorator
public class Viewbox : Control
{
private Decorator _containerVisual;
/// <summary>
/// Defines the <see cref="Stretch"/> property.
/// </summary>
@ -20,12 +22,27 @@ namespace Avalonia.Controls
public static readonly StyledProperty<StretchDirection> StretchDirectionProperty =
AvaloniaProperty.Register<Viewbox, StretchDirection>(nameof(StretchDirection), StretchDirection.Both);
/// <summary>
/// Defines the <see cref="Child"/> property
/// </summary>
public static readonly StyledProperty<IControl?> ChildProperty =
Decorator.ChildProperty.AddOwner<Viewbox>();
static Viewbox()
{
ClipToBoundsProperty.OverrideDefaultValue<Viewbox>(true);
UseLayoutRoundingProperty.OverrideDefaultValue<Viewbox>(true);
AffectsMeasure<Viewbox>(StretchProperty, StretchDirectionProperty);
}
public Viewbox()
{
_containerVisual = new Decorator();
_containerVisual.RenderTransformOrigin = RelativePoint.TopLeft;
LogicalChildren.Add(_containerVisual);
VisualChildren.Add(_containerVisual);
}
/// <summary>
/// Gets or sets the stretch mode,
/// which determines how child fits into the available space.
@ -45,9 +62,40 @@ namespace Avalonia.Controls
set => SetValue(StretchDirectionProperty, value);
}
/// <summary>
/// Gets or sets the child of the Viewbox
/// </summary>
[Content]
public IControl? Child
{
get => GetValue(ChildProperty);
set => SetValue(ChildProperty, value);
}
/// <summary>
/// Gets or sets the transform applied to the container visual that
/// hosts the child of the Viewbox
/// </summary>
protected internal ITransform? InternalTransform
{
get => _containerVisual.RenderTransform;
set => _containerVisual.RenderTransform = value;
}
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change)
{
base.OnPropertyChanged(change);
if (change.Property == ChildProperty)
{
_containerVisual.Child = change.NewValue.GetValueOrDefault<IControl>();
InvalidateMeasure();
}
}
protected override Size MeasureOverride(Size availableSize)
{
var child = Child;
var child = _containerVisual;
if (child != null)
{
@ -57,7 +105,7 @@ namespace Avalonia.Controls
var size = Stretch.CalculateSize(availableSize, childSize, StretchDirection);
return size.Constrain(availableSize);
return size;
}
return new Size();
@ -65,31 +113,21 @@ namespace Avalonia.Controls
protected override Size ArrangeOverride(Size finalSize)
{
var child = Child;
var child = _containerVisual;
if (child != null)
{
var childSize = child.DesiredSize;
var scale = Stretch.CalculateScaling(finalSize, childSize, StretchDirection);
// TODO: Viewbox should have another decorator as a child so we won't affect other render transforms.
var scaleTransform = child.RenderTransform as ScaleTransform;
if (scaleTransform == null)
{
child.RenderTransform = scaleTransform = new ScaleTransform(scale.X, scale.Y);
child.RenderTransformOrigin = RelativePoint.TopLeft;
}
scaleTransform.ScaleX = scale.X;
scaleTransform.ScaleY = scale.Y;
InternalTransform = new ScaleTransform(scale.X, scale.Y);
child.Arrange(new Rect(childSize));
return childSize * scale;
}
return new Size();
return finalSize;
}
}
}

1
src/Avalonia.DesignerSupport/ApiCompatBaseline.txt

@ -0,0 +1 @@
Total Issues: 0

2
src/Avalonia.DesignerSupport/Remote/HtmlTransport/webapp/src/index.html

@ -9,6 +9,6 @@
<div id="app">
<center>Loading...</center>
</div>
<noscript>Javascript is required</noscript>
<noscript>JavaScript is required</noscript>
</body>
</html>

6
src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs

@ -198,9 +198,9 @@ namespace Avalonia.FreeDesktop.DBusIme
UpdateActive();
}
void ITextInputMethodImpl.SetActive(bool active)
void ITextInputMethodImpl.SetClient(ITextInputMethodClient client)
{
_controlActive = active;
_controlActive = client is { };
UpdateActive();
}
@ -272,7 +272,7 @@ namespace Avalonia.FreeDesktop.DBusIme
UpdateCursorRect();
}
public abstract void SetOptions(TextInputOptionsQueryEventArgs options);
public abstract void SetOptions(TextInputOptions options);
void ITextInputMethodImpl.Reset()
{

4
src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs

@ -93,7 +93,7 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx
(uint)args.Timestamp).ConfigureAwait(false);
}
public override void SetOptions(TextInputOptionsQueryEventArgs options) =>
public override void SetOptions(TextInputOptions options) =>
Enqueue(async () =>
{
if(_context == null)
@ -111,7 +111,7 @@ namespace Avalonia.FreeDesktop.DBusIme.Fcitx
flags |= FcitxCapabilityFlags.CAPACITY_NUMBER;
else if (options.ContentType == TextInputContentType.Password)
flags |= FcitxCapabilityFlags.CAPACITY_PASSWORD;
else if (options.ContentType == TextInputContentType.Phone)
else if (options.ContentType == TextInputContentType.Digits)
flags |= FcitxCapabilityFlags.CAPACITY_DIALABLE;
else if (options.ContentType == TextInputContentType.Url)
flags |= FcitxCapabilityFlags.CAPACITY_URL;

2
src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs

@ -97,7 +97,7 @@ namespace Avalonia.FreeDesktop.DBusIme.IBus
return _context.ProcessKeyEventAsync((uint)keyVal, (uint)keyCode, (uint)state);
}
public override void SetOptions(TextInputOptionsQueryEventArgs options)
public override void SetOptions(TextInputOptions options)
{
// No-op, because ibus
}

17
src/Avalonia.Input/ApiCompatBaseline.txt

@ -4,11 +4,26 @@ MembersMustExist : Member 'public Avalonia.Interactivity.RoutedEvent<Avalonia.In
MembersMustExist : Member 'public Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Input.Gestures.RightTappedEvent' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Input.Gestures.TappedEvent' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Input.IFocusManager.RemoveFocusScope(Avalonia.Input.IFocusScope)' is present in the implementation but not in the contract.
MembersMustExist : Member 'public Avalonia.Interactivity.RoutedEvent<Avalonia.Input.TextInput.TextInputOptionsQueryEventArgs> Avalonia.Interactivity.RoutedEvent<Avalonia.Input.TextInput.TextInputOptionsQueryEventArgs> Avalonia.Input.InputElement.TextInputOptionsQueryEvent' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Input.InputElement.DoubleTappedEvent' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Interactivity.RoutedEvent<Avalonia.Interactivity.RoutedEventArgs> Avalonia.Input.InputElement.TappedEvent' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Input.InputElement.add_DoubleTapped(System.EventHandler<Avalonia.Interactivity.RoutedEventArgs>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Input.InputElement.add_Tapped(System.EventHandler<Avalonia.Interactivity.RoutedEventArgs>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Input.InputElement.add_TextInputOptionsQuery(System.EventHandler<Avalonia.Input.TextInput.TextInputOptionsQueryEventArgs>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Input.InputElement.remove_DoubleTapped(System.EventHandler<Avalonia.Interactivity.RoutedEventArgs>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Input.InputElement.remove_Tapped(System.EventHandler<Avalonia.Interactivity.RoutedEventArgs>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Input.InputElement.remove_TextInputOptionsQuery(System.EventHandler<Avalonia.Input.TextInput.TextInputOptionsQueryEventArgs>)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Input.TextInput.ITextInputMethodImpl.SetActive(System.Boolean)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Input.TextInput.ITextInputMethodImpl.SetActive(System.Boolean)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Input.TextInput.ITextInputMethodImpl.SetClient(Avalonia.Input.TextInput.ITextInputMethodClient)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Input.TextInput.ITextInputMethodImpl.SetOptions(Avalonia.Input.TextInput.TextInputOptions)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Input.TextInput.ITextInputMethodImpl.SetOptions(Avalonia.Input.TextInput.TextInputOptionsQueryEventArgs)' is present in the contract but not in the implementation.
MembersMustExist : Member 'public void Avalonia.Input.TextInput.ITextInputMethodImpl.SetOptions(Avalonia.Input.TextInput.TextInputOptionsQueryEventArgs)' does not exist in the implementation but it does exist in the contract.
EnumValuesMustMatch : Enum value 'Avalonia.Input.TextInput.TextInputContentType Avalonia.Input.TextInput.TextInputContentType.Email' is (System.Int32)5 in the implementation but (System.Int32)1 in the contract.
EnumValuesMustMatch : Enum value 'Avalonia.Input.TextInput.TextInputContentType Avalonia.Input.TextInput.TextInputContentType.Number' is (System.Int32)4 in the implementation but (System.Int32)3 in the contract.
EnumValuesMustMatch : Enum value 'Avalonia.Input.TextInput.TextInputContentType Avalonia.Input.TextInput.TextInputContentType.Password' is (System.Int32)8 in the implementation but (System.Int32)5 in the contract.
MembersMustExist : Member 'public Avalonia.Input.TextInput.TextInputContentType Avalonia.Input.TextInput.TextInputContentType Avalonia.Input.TextInput.TextInputContentType.Phone' does not exist in the implementation but it does exist in the contract.
EnumValuesMustMatch : Enum value 'Avalonia.Input.TextInput.TextInputContentType Avalonia.Input.TextInput.TextInputContentType.Url' is (System.Int32)6 in the implementation but (System.Int32)4 in the contract.
TypesMustExist : Type 'Avalonia.Input.TextInput.TextInputOptionsQueryEventArgs' does not exist in the implementation but it does exist in the contract.
TypesMustExist : Type 'Avalonia.Platform.IStandardCursorFactory' does not exist in the implementation but it does exist in the contract.
Total Issues: 12
Total Issues: 27

17
src/Avalonia.Input/InputElement.cs

@ -126,14 +126,6 @@ namespace Avalonia.Input
RoutedEvent.Register<InputElement, TextInputMethodClientRequestedEventArgs>(
"TextInputMethodClientRequested",
RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="TextInputOptionsQuery"/> event.
/// </summary>
public static readonly RoutedEvent<TextInputOptionsQueryEventArgs> TextInputOptionsQueryEvent =
RoutedEvent.Register<InputElement, TextInputOptionsQueryEventArgs>(
"TextInputOptionsQuery",
RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="PointerEnter"/> event.
@ -283,15 +275,6 @@ namespace Avalonia.Input
add { AddHandler(TextInputMethodClientRequestedEvent, value); }
remove { RemoveHandler(TextInputMethodClientRequestedEvent, value); }
}
/// <summary>
/// Occurs when an input element gains input focus and input method is asking for required content options
/// </summary>
public event EventHandler<TextInputOptionsQueryEventArgs>? TextInputOptionsQuery
{
add { AddHandler(TextInputOptionsQueryEvent, value); }
remove { RemoveHandler(TextInputOptionsQueryEvent, value); }
}
/// <summary>
/// Occurs when the pointer enters the control.

1
src/Avalonia.Input/Properties/AssemblyInfo.cs

@ -2,4 +2,5 @@ using System.Reflection;
using Avalonia.Metadata;
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Input")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Input.TextInput")]
[assembly: XmlnsDefinition("https://github.com/avaloniaui", "Avalonia.Input.GestureRecognizers")]

4
src/Avalonia.Input/TextInput/ITextInputMethodImpl.cs

@ -2,9 +2,9 @@ namespace Avalonia.Input.TextInput
{
public interface ITextInputMethodImpl
{
void SetActive(bool active);
void SetClient(ITextInputMethodClient? client);
void SetCursorRect(Rect rect);
void SetOptions(TextInputOptionsQueryEventArgs options);
void SetOptions(TextInputOptions options);
void Reset();
}

30
src/Avalonia.Input/TextInput/InputMethodManager.cs

@ -35,21 +35,26 @@ namespace Avalonia.Input.TextInput
{
_client.CursorRectangleChanged += OnCursorRectangleChanged;
_client.TextViewVisualChanged += OnTextViewVisualChanged;
var optionsQuery = new TextInputOptionsQueryEventArgs
{
RoutedEvent = InputElement.TextInputOptionsQueryEvent
};
_focusedElement?.RaiseEvent(optionsQuery);
_im?.Reset();
_im?.SetOptions(optionsQuery);
_transformTracker?.SetVisual(_client?.TextViewVisual);
if (_focusedElement is StyledElement target)
{
_im?.SetOptions(TextInputOptions.FromStyledElement(target));
}
else
{
_im?.SetOptions(TextInputOptions.Default);
}
_transformTracker.SetVisual(_client?.TextViewVisual);
UpdateCursorRect();
_im?.SetActive(true);
_im?.SetClient(_client);
}
else
{
_im?.SetActive(false);
_im?.SetClient(null);
_transformTracker.SetVisual(null);
}
}
@ -91,9 +96,12 @@ namespace Avalonia.Input.TextInput
_focusedElement = element;
var inputMethod = (element?.VisualRoot as ITextInputMethodRoot)?.InputMethod;
if (_im != inputMethod)
_im?.SetActive(false);
if (_im != inputMethod)
{
_im?.SetClient(null);
}
_im = inputMethod;
TryFindAndApplyClient();

62
src/Avalonia.Input/TextInput/TextInputContentType.cs

@ -1,12 +1,62 @@
namespace Avalonia.Input.TextInput
{
{
public enum TextInputContentType
{
/// <summary>
/// Default keyboard for the users configured input method.
/// </summary>
Normal = 0,
Email = 1,
Phone = 2,
Number = 3,
Url = 4,
Password = 5
/// <summary>
/// Display a keyboard that only has alphabetic characters.
/// </summary>
Alpha,
/// <summary>
/// Display a numeric keypad only capable of numbers. i.e. Phone number
/// </summary>
Digits,
/// <summary>
/// Display a numeric keypad for inputting a PIN.
/// </summary>
Pin,
/// <summary>
/// Display a numeric keypad capable of inputting numbers including decimal seperator and sign.
/// </summary>
Number,
/// <summary>
/// Display a keyboard for entering an email address.
/// </summary>
Email,
/// <summary>
/// Display a keyboard for entering a URL.
/// </summary>
Url,
/// <summary>
/// Display a keyboard for entering a persons name.
/// </summary>
Name,
/// <summary>
/// Display a keyboard for entering sensitive data.
/// </summary>
Password,
/// <summary>
/// Display a keyboard suitable for #tag and @mentions.
/// Not available on all platforms, will fallback to a suitable keyboard
/// when not available.
/// </summary>
Social,
/// <summary>
/// Display a keyboard for entering a search keyword.
/// </summary>
Search
}
}

220
src/Avalonia.Input/TextInput/TextInputOptions.cs

@ -0,0 +1,220 @@
namespace Avalonia.Input.TextInput;
public class TextInputOptions
{
public static TextInputOptions FromStyledElement(StyledElement avaloniaObject)
{
var result = new TextInputOptions
{
ContentType = GetContentType(avaloniaObject),
Multiline = GetMultiline(avaloniaObject),
AutoCapitalization = GetAutoCapitalization(avaloniaObject),
IsSensitive = GetIsSensitive(avaloniaObject),
Lowercase = GetLowercase(avaloniaObject),
Uppercase = GetUppercase(avaloniaObject)
};
return result;
}
public static readonly TextInputOptions Default = new();
/// <summary>
/// Defines the <see cref="ContentType"/> property.
/// </summary>
public static readonly AttachedProperty<TextInputContentType> ContentTypeProperty =
AvaloniaProperty.RegisterAttached<TextInputOptions, StyledElement, TextInputContentType>(
"ContentType",
defaultValue: TextInputContentType.Normal,
inherits: true);
/// <summary>
/// Sets the value of the attached <see cref="ContentTypeProperty"/> on a control.
/// </summary>
/// <param name="avaloniaObject">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetContentType(StyledElement avaloniaObject, TextInputContentType value)
{
avaloniaObject.SetValue(ContentTypeProperty, value);
}
/// <summary>
/// Gets the value of the attached <see cref="ContentTypeProperty"/>.
/// </summary>
/// <param name="avaloniaObject">The target.</param>
/// <returns>TextInputContentType</returns>
public static TextInputContentType GetContentType(StyledElement avaloniaObject)
{
return avaloniaObject.GetValue(ContentTypeProperty);
}
/// <summary>
/// The content type (mostly for determining the shape of the virtual keyboard)
/// </summary>
public TextInputContentType ContentType { get; set; }
/// <summary>
/// Defines the <see cref="Multiline"/> property.
/// </summary>
public static readonly AttachedProperty<bool> MultilineProperty =
AvaloniaProperty.RegisterAttached<TextInputOptions, StyledElement, bool>(
"Multiline",
inherits: true);
/// <summary>
/// Sets the value of the attached <see cref="MultilineProperty"/> on a control.
/// </summary>
/// <param name="avaloniaObject">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetMultiline(StyledElement avaloniaObject, bool value)
{
avaloniaObject.SetValue(MultilineProperty, value);
}
/// <summary>
/// Gets the value of the attached <see cref="MultilineProperty"/>.
/// </summary>
/// <param name="avaloniaObject">The target.</param>
/// <returns>true if multiline</returns>
public static bool GetMultiline(StyledElement avaloniaObject)
{
return avaloniaObject.GetValue(MultilineProperty);
}
/// <summary>
/// Text is multiline
/// </summary>
public bool Multiline { get; set; }
/// <summary>
/// Defines the <see cref="Lowercase"/> property.
/// </summary>
public static readonly AttachedProperty<bool> LowercaseProperty =
AvaloniaProperty.RegisterAttached<TextInputOptions, StyledElement, bool>(
"Lowercase",
inherits: true);
/// <summary>
/// Sets the value of the attached <see cref="LowercaseProperty"/> on a control.
/// </summary>
/// <param name="avaloniaObject">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetLowercase(StyledElement avaloniaObject, bool value)
{
avaloniaObject.SetValue(LowercaseProperty, value);
}
/// <summary>
/// Gets the value of the attached <see cref="LowercaseProperty"/>.
/// </summary>
/// <param name="avaloniaObject">The target.</param>
/// <returns>true if Lowercase</returns>
public static bool GetLowercase(StyledElement avaloniaObject)
{
return avaloniaObject.GetValue(LowercaseProperty);
}
/// <summary>
/// Text is in lower case
/// </summary>
public bool Lowercase { get; set; }
/// <summary>
/// Defines the <see cref="Uppercase"/> property.
/// </summary>
public static readonly AttachedProperty<bool> UppercaseProperty =
AvaloniaProperty.RegisterAttached<TextInputOptions, StyledElement, bool>(
"Uppercase",
inherits: true);
/// <summary>
/// Sets the value of the attached <see cref="UppercaseProperty"/> on a control.
/// </summary>
/// <param name="avaloniaObject">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetUppercase(StyledElement avaloniaObject, bool value)
{
avaloniaObject.SetValue(UppercaseProperty, value);
}
/// <summary>
/// Gets the value of the attached <see cref="UppercaseProperty"/>.
/// </summary>
/// <param name="avaloniaObject">The target.</param>
/// <returns>true if Uppercase</returns>
public static bool GetUppercase(StyledElement avaloniaObject)
{
return avaloniaObject.GetValue(UppercaseProperty);
}
/// <summary>
/// Text is in upper case
/// </summary>
public bool Uppercase { get; set; }
/// <summary>
/// Defines the <see cref="AutoCapitalization"/> property.
/// </summary>
public static readonly AttachedProperty<bool> AutoCapitalizationProperty =
AvaloniaProperty.RegisterAttached<TextInputOptions, StyledElement, bool>(
"AutoCapitalization",
inherits: true);
/// <summary>
/// Sets the value of the attached <see cref="AutoCapitalizationProperty"/> on a control.
/// </summary>
/// <param name="avaloniaObject">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetAutoCapitalization(StyledElement avaloniaObject, bool value)
{
avaloniaObject.SetValue(AutoCapitalizationProperty, value);
}
/// <summary>
/// Gets the value of the attached <see cref="AutoCapitalizationProperty"/>.
/// </summary>
/// <param name="avaloniaObject">The target.</param>
/// <returns>true if AutoCapitalization</returns>
public static bool GetAutoCapitalization(StyledElement avaloniaObject)
{
return avaloniaObject.GetValue(AutoCapitalizationProperty);
}
/// <summary>
/// Automatically capitalize letters at the start of the sentence
/// </summary>
public bool AutoCapitalization { get; set; }
/// <summary>
/// Defines the <see cref="IsSensitive"/> property.
/// </summary>
public static readonly AttachedProperty<bool> IsSensitiveProperty =
AvaloniaProperty.RegisterAttached<TextInputOptions, StyledElement, bool>(
"IsSensitive",
inherits: true);
/// <summary>
/// Sets the value of the attached <see cref="IsSensitiveProperty"/> on a control.
/// </summary>
/// <param name="avaloniaObject">The control.</param>
/// <param name="value">The property value to set.</param>
public static void SetIsSensitive(StyledElement avaloniaObject, bool value)
{
avaloniaObject.SetValue(IsSensitiveProperty, value);
}
/// <summary>
/// Gets the value of the attached <see cref="IsSensitiveProperty"/>.
/// </summary>
/// <param name="avaloniaObject">The target.</param>
/// <returns>true if IsSensitive</returns>
public static bool GetIsSensitive(StyledElement avaloniaObject)
{
return avaloniaObject.GetValue(IsSensitiveProperty);
}
/// <summary>
/// Text contains sensitive data like card numbers and should not be stored
/// </summary>
public bool IsSensitive { get; set; }
}

32
src/Avalonia.Input/TextInput/TextInputOptionsQueryEventArgs.cs

@ -1,32 +0,0 @@
using Avalonia.Interactivity;
namespace Avalonia.Input.TextInput
{
public class TextInputOptionsQueryEventArgs : RoutedEventArgs
{
/// <summary>
/// The content type (mostly for determining the shape of the virtual keyboard)
/// </summary>
public TextInputContentType ContentType { get; set; }
/// <summary>
/// Text is multiline
/// </summary>
public bool Multiline { get; set; }
/// <summary>
/// Text is in lower case
/// </summary>
public bool Lowercase { get; set; }
/// <summary>
/// Text is in upper case
/// </summary>
public bool Uppercase { get; set; }
/// <summary>
/// Automatically capitalize letters at the start of the sentence
/// </summary>
public bool AutoCapitalization { get; set; }
/// <summary>
/// Text contains sensitive data like card numbers and should not be stored
/// </summary>
public bool IsSensitive { get; set; }
}
}

1
src/Avalonia.Interactivity/ApiCompatBaseline.txt

@ -0,0 +1 @@
Total Issues: 0

1
src/Avalonia.Layout/ApiCompatBaseline.txt

@ -0,0 +1 @@
Total Issues: 0

2
src/Avalonia.ReactiveUI/RoutedViewHost.cs

@ -62,7 +62,7 @@ namespace Avalonia.ReactiveUI
/// <see cref="AvaloniaProperty"/> for the <see cref="ViewContract"/> property.
/// </summary>
public static readonly StyledProperty<string?> ViewContractProperty =
AvaloniaProperty.Register<ViewModelViewHost, string?>(nameof(ViewContract));
AvaloniaProperty.Register<RoutedViewHost, string?>(nameof(ViewContract));
/// <summary>
/// Initializes a new instance of the <see cref="RoutedViewHost"/> class.

1
src/Avalonia.Remote.Protocol/ApiCompatBaseline.txt

@ -0,0 +1 @@
Total Issues: 0

1
src/Avalonia.Themes.Default/ApiCompatBaseline.txt

@ -0,0 +1 @@
Total Issues: 0

1
src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt

@ -0,0 +1 @@
Total Issues: 0

6
src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs

@ -30,6 +30,7 @@ namespace Avalonia.Animation.Animators
return new ImmutableRadialGradientBrush(
InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops),
s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity),
oldValue.Transform is { } ? new ImmutableTransform(oldValue.Transform.Value) : null,
oldValue.SpreadMethod,
s_relativePointAnimator.Interpolate(progress, oldRadial.Center, newRadial.Center),
s_relativePointAnimator.Interpolate(progress, oldRadial.GradientOrigin, newRadial.GradientOrigin),
@ -39,6 +40,7 @@ namespace Avalonia.Animation.Animators
return new ImmutableConicGradientBrush(
InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops),
s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity),
oldValue.Transform is { } ? new ImmutableTransform(oldValue.Transform.Value) : null,
oldValue.SpreadMethod,
s_relativePointAnimator.Interpolate(progress, oldConic.Center, newConic.Center),
s_doubleAnimator.Interpolate(progress, oldConic.Angle, newConic.Angle));
@ -47,6 +49,7 @@ namespace Avalonia.Animation.Animators
return new ImmutableLinearGradientBrush(
InterpolateStops(progress, oldValue.GradientStops, newValue.GradientStops),
s_doubleAnimator.Interpolate(progress, oldValue.Opacity, newValue.Opacity),
oldValue.Transform is { } ? new ImmutableTransform(oldValue.Transform.Value) : null,
oldValue.SpreadMethod,
s_relativePointAnimator.Interpolate(progress, oldLinear.StartPoint, newLinear.StartPoint),
s_relativePointAnimator.Interpolate(progress, oldLinear.EndPoint, newLinear.EndPoint));
@ -98,16 +101,19 @@ namespace Avalonia.Animation.Animators
case IRadialGradientBrush oldRadial:
return new ImmutableRadialGradientBrush(
CreateStopsFromSolidColorBrush(solidColorBrush, oldRadial.GradientStops), solidColorBrush.Opacity,
oldRadial.Transform is { } ? new ImmutableTransform(oldRadial.Transform.Value) : null,
oldRadial.SpreadMethod, oldRadial.Center, oldRadial.GradientOrigin, oldRadial.Radius);
case IConicGradientBrush oldConic:
return new ImmutableConicGradientBrush(
CreateStopsFromSolidColorBrush(solidColorBrush, oldConic.GradientStops), solidColorBrush.Opacity,
oldConic.Transform is { } ? new ImmutableTransform(oldConic.Transform.Value) : null,
oldConic.SpreadMethod, oldConic.Center, oldConic.Angle);
case ILinearGradientBrush oldLinear:
return new ImmutableLinearGradientBrush(
CreateStopsFromSolidColorBrush(solidColorBrush, oldLinear.GradientStops), solidColorBrush.Opacity,
oldLinear.Transform is { } ? new ImmutableTransform(oldLinear.Transform.Value) : null,
oldLinear.SpreadMethod, oldLinear.StartPoint, oldLinear.EndPoint);
default:

10
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -41,6 +41,8 @@ MembersMustExist : Member 'public void Avalonia.Media.GlyphRun.GlyphIndices.set(
MembersMustExist : Member 'public Avalonia.Utilities.ReadOnlySlice<Avalonia.Vector> Avalonia.Media.GlyphRun.GlyphOffsets.get()' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.GlyphRun.GlyphOffsets.set(Avalonia.Utilities.ReadOnlySlice<Avalonia.Vector>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.GlyphRun.GlyphTypeface.set(Avalonia.Media.GlyphTypeface)' does not exist in the implementation but it does exist in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.ITransform Avalonia.Media.IBrush.Transform' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.ITransform Avalonia.Media.IBrush.Transform.get()' is present in the implementation but not in the contract.
CannotSealType : Type 'Avalonia.Media.Pen' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract.
MembersMustExist : Member 'protected void Avalonia.Media.Pen.AffectsRender<T>(Avalonia.AvaloniaProperty[])' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Media.Pen.RaiseInvalidated(System.EventArgs)' does not exist in the implementation but it does exist in the contract.
@ -52,7 +54,13 @@ MembersMustExist : Member 'public void Avalonia.Media.TextHitTestResult.IsTraili
MembersMustExist : Member 'public void Avalonia.Media.TextHitTestResult.TextPosition.set(System.Int32)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.Typeface..ctor(Avalonia.Media.FontFamily, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.Typeface..ctor(System.String, Avalonia.Media.FontStyle, Avalonia.Media.FontWeight)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.Immutable.ImmutableConicGradientBrush..ctor(System.Collections.Generic.IReadOnlyList<Avalonia.Media.Immutable.ImmutableGradientStop>, System.Double, Avalonia.Media.GradientSpreadMethod, System.Nullable<Avalonia.RelativePoint>, System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Media.Immutable.ImmutableGradientBrush..ctor(System.Collections.Generic.IReadOnlyList<Avalonia.Media.Immutable.ImmutableGradientStop>, System.Double, Avalonia.Media.GradientSpreadMethod)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.Immutable.ImmutableLinearGradientBrush..ctor(System.Collections.Generic.IReadOnlyList<Avalonia.Media.Immutable.ImmutableGradientStop>, System.Double, Avalonia.Media.GradientSpreadMethod, System.Nullable<Avalonia.RelativePoint>, System.Nullable<Avalonia.RelativePoint>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.Immutable.ImmutableRadialGradientBrush..ctor(System.Collections.Generic.IReadOnlyList<Avalonia.Media.Immutable.ImmutableGradientStop>, System.Double, Avalonia.Media.GradientSpreadMethod, System.Nullable<Avalonia.RelativePoint>, System.Nullable<Avalonia.RelativePoint>, System.Double)' does not exist in the implementation but it does exist in the contract.
TypeCannotChangeClassification : Type 'Avalonia.Media.Immutable.ImmutableSolidColorBrush' is a 'class' in the implementation but is a 'struct' in the contract.
MembersMustExist : Member 'public void Avalonia.Media.Immutable.ImmutableSolidColorBrush..ctor(Avalonia.Media.Color, System.Double)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Media.Immutable.ImmutableTileBrush..ctor(Avalonia.Media.AlignmentX, Avalonia.Media.AlignmentY, Avalonia.RelativeRect, System.Double, Avalonia.RelativeRect, Avalonia.Media.Stretch, Avalonia.Media.TileMode, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext)' does not exist in the implementation but it does exist in the contract.
CannotAddAbstractMembers : Member 'public void Avalonia.Media.TextFormatting.DrawableTextRun.Draw(Avalonia.Media.DrawingContext, Avalonia.Point)' is abstract in the implementation but is missing in the contract.
CannotSealType : Type 'Avalonia.Media.TextFormatting.GenericTextParagraphProperties' is actually (has the sealed modifier) sealed in the implementation but not sealed in the contract.
@ -153,4 +161,4 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Media.GlyphR
MembersMustExist : Member 'public Avalonia.Media.GlyphRun Avalonia.Platform.ITextShaperImpl.ShapeText(Avalonia.Utilities.ReadOnlySlice<System.Char>, Avalonia.Media.Typeface, System.Double, System.Globalization.CultureInfo)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'protected void Avalonia.Rendering.RendererBase.RenderFps(Avalonia.Platform.IDrawingContextImpl, Avalonia.Rect, System.Nullable<System.Int32>)' does not exist in the implementation but it does exist in the contract.
MembersMustExist : Member 'public void Avalonia.Utilities.ReadOnlySlice<T>..ctor(System.ReadOnlyMemory<T>, System.Int32, System.Int32)' does not exist in the implementation but it does exist in the contract.
Total Issues: 153
Total Issues: 162

17
src/Avalonia.Visuals/Media/Brush.cs

@ -18,13 +18,19 @@ namespace Avalonia.Media
public static readonly StyledProperty<double> OpacityProperty =
AvaloniaProperty.Register<Brush, double>(nameof(Opacity), 1.0);
/// <summary>
/// Defines the <see cref="Transform"/> property.
/// </summary>
public static readonly StyledProperty<ITransform?> TransformProperty =
AvaloniaProperty.Register<Brush, ITransform?>(nameof(Transform));
/// <inheritdoc/>
public event EventHandler? Invalidated;
static Brush()
{
Animation.Animation.RegisterAnimator<BaseBrushAnimator>(prop => typeof(IBrush).IsAssignableFrom(prop.PropertyType));
AffectsRender<Brush>(OpacityProperty);
AffectsRender<Brush>(OpacityProperty, TransformProperty);
}
/// <summary>
@ -36,6 +42,15 @@ namespace Avalonia.Media
set { SetValue(OpacityProperty, value); }
}
/// <summary>
/// Gets or sets the transform of the brush.
/// </summary>
public ITransform? Transform
{
get { return GetValue(TransformProperty); }
set { SetValue(TransformProperty, value); }
}
/// <summary>
/// Parses a brush string.
/// </summary>

11
src/Avalonia.Visuals/Media/Color.cs

@ -273,8 +273,17 @@ namespace Avalonia.Media
}
/// <summary>
/// Check if two colors are equal.
/// Returns the HSV color model equivalent of this RGB color.
/// </summary>
/// <returns>The HSV equivalent color.</returns>
public HsvColor ToHsv()
{
// Use the by-channel conversion method directly for performance
// Don't use the HsvColor(Color) constructor to avoid an extra HsvColor
return HsvColor.FromRgb(R, G, B, A);
}
/// <inheritdoc/>
public bool Equals(Color other)
{
return A == other.A && R == other.R && G == other.G && B == other.B;

478
src/Avalonia.Visuals/Media/HsvColor.cs

@ -0,0 +1,478 @@
// Color conversion portions of this source file are adapted from the WinUI project.
// (https://github.com/microsoft/microsoft-ui-xaml)
//
// Licensed to The Avalonia Project under MIT License, courtesy of The .NET Foundation.
using System;
using Avalonia.Utilities;
namespace Avalonia.Media
{
/// <summary>
/// Defines a color using the hue/saturation/value (HSV) model.
/// </summary>
#if !BUILDTASK
public
#endif
readonly struct HsvColor : IEquatable<HsvColor>
{
/// <summary>
/// Initializes a new instance of the <see cref="HsvColor"/> struct.
/// </summary>
/// <param name="alpha">The Alpha (transparency) channel value in the range from 0..1.</param>
/// <param name="hue">The Hue channel value in the range from 0..360.</param>
/// <param name="saturation">The Saturation channel value in the range from 0..1.</param>
/// <param name="value">The Value channel value in the range from 0..1.</param>
public HsvColor(
double alpha,
double hue,
double saturation,
double value)
{
A = MathUtilities.Clamp(alpha, 0.0, 1.0);
H = MathUtilities.Clamp(hue, 0.0, 360.0);
S = MathUtilities.Clamp(saturation, 0.0, 1.0);
V = MathUtilities.Clamp(value, 0.0, 1.0);
}
/// <summary>
/// Initializes a new instance of the <see cref="HsvColor"/> struct.
/// </summary>
/// <remarks>
/// This constructor exists only for internal use where performance is critical.
/// Whether or not the channel values are in the correct ranges must be known.
/// </remarks>
/// <param name="alpha">The Alpha (transparency) channel value in the range from 0..1.</param>
/// <param name="hue">The Hue channel value in the range from 0..360.</param>
/// <param name="saturation">The Saturation channel value in the range from 0..1.</param>
/// <param name="value">The Value channel value in the range from 0..1.</param>
/// <param name="clampValues">Whether to clamp channel values to their required ranges.</param>
internal HsvColor(
double alpha,
double hue,
double saturation,
double value,
bool clampValues)
{
if (clampValues)
{
A = MathUtilities.Clamp(alpha, 0.0, 1.0);
H = MathUtilities.Clamp(hue, 0.0, 360.0);
S = MathUtilities.Clamp(saturation, 0.0, 1.0);
V = MathUtilities.Clamp(value, 0.0, 1.0);
}
else
{
A = alpha;
H = hue;
S = saturation;
V = value;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="HsvColor"/> struct.
/// </summary>
/// <param name="color">The RGB color to convert to HSV.</param>
public HsvColor(Color color)
{
var hsv = HsvColor.FromRgb(color);
A = hsv.A;
H = hsv.H;
S = hsv.S;
V = hsv.V;
}
/// <summary>
/// Gets the Alpha (transparency) channel value in the range from 0..1.
/// </summary>
public double A { get; }
/// <summary>
/// Gets the Hue channel value in the range from 0..360.
/// </summary>
public double H { get; }
/// <summary>
/// Gets the Saturation channel value in the range from 0..1.
/// </summary>
public double S { get; }
/// <summary>
/// Gets the Value channel value in the range from 0..1.
/// </summary>
public double V { get; }
/// <inheritdoc/>
public bool Equals(HsvColor other)
{
return other.A == A &&
other.H == H &&
other.S == S &&
other.V == V;
}
/// <inheritdoc/>
public override bool Equals(object? obj)
{
if (obj is HsvColor hsvColor)
{
return Equals(hsvColor);
}
else
{
return false;
}
}
/// <summary>
/// Gets a hashcode for this object.
/// Hashcode is not guaranteed to be unique.
/// </summary>
/// <returns>The hashcode for this object.</returns>
public override int GetHashCode()
{
// Same algorithm as Color
// This is used instead of HashCode.Combine() due to .NET Standard 2.0 requirements
unchecked
{
int hashCode = A.GetHashCode();
hashCode = (hashCode * 397) ^ H.GetHashCode();
hashCode = (hashCode * 397) ^ S.GetHashCode();
hashCode = (hashCode * 397) ^ V.GetHashCode();
return hashCode;
}
}
/// <summary>
/// Returns the RGB color model equivalent of this HSV color.
/// </summary>
/// <returns>The RGB equivalent color.</returns>
public Color ToRgb()
{
// Use the by-channel conversion method directly for performance
return HsvColor.ToRgb(H, S, V, A);
}
/// <summary>
/// Creates a new <see cref="HsvColor"/> from individual color channel values.
/// </summary>
/// <remarks>
/// This exists for symmetry with the <see cref="Color"/> struct; however, the
/// appropriate constructor should commonly be used instead.
/// </remarks>
/// <param name="a">The Alpha (transparency) channel value in the range from 0..1.</param>
/// <param name="h">The Hue channel value in the range from 0..360.</param>
/// <param name="s">The Saturation channel value in the range from 0..1.</param>
/// <param name="v">The Value channel value in the range from 0..1.</param>
/// <returns>A new <see cref="HsvColor"/> built from the individual color channel values.</returns>
public static HsvColor FromAhsv(double a, double h, double s, double v)
{
return new HsvColor(a, h, s, v);
}
/// <summary>
/// Converts the given HSV color to it's RGB color equivalent.
/// </summary>
/// <param name="hsvColor">The color in the HSV color model.</param>
/// <returns>A new RGB <see cref="Color"/> equivalent to the given HSVA values.</returns>
public static Color ToRgb(HsvColor hsvColor)
{
return HsvColor.ToRgb(hsvColor.H, hsvColor.S, hsvColor.V, hsvColor.A);
}
/// <summary>
/// Converts the given HSVA color channel values to it's RGB color equivalent.
/// </summary>
/// <param name="hue">The hue channel value in the HSV color model in the range from 0..360.</param>
/// <param name="saturation">The saturation channel value in the HSV color model in the range from 0..1.</param>
/// <param name="value">The value channel value in the HSV color model in the range from 0..1.</param>
/// <param name="alpha">The alpha channel value in the range from 0..1.</param>
/// <returns>A new RGB <see cref="Color"/> equivalent to the given HSVA values.</returns>
public static Color ToRgb(
double hue,
double saturation,
double value,
double alpha = 1.0)
{
// Note: Conversion code is originally based on the C++ in WinUI (licensed MIT)
// https://github.com/microsoft/microsoft-ui-xaml/blob/main/dev/Common/ColorConversion.cpp
// This was used because it is the best documented and likely most optimized for performance
// Alpha channel support was added
// We want the hue to be between 0 and 359,
// so we first ensure that that's the case.
while (hue >= 360.0)
{
hue -= 360.0;
}
while (hue < 0.0)
{
hue += 360.0;
}
// We similarly clamp saturation, value and alpha between 0 and 1.
saturation = saturation < 0.0 ? 0.0 : saturation;
saturation = saturation > 1.0 ? 1.0 : saturation;
value = value < 0.0 ? 0.0 : value;
value = value > 1.0 ? 1.0 : value;
alpha = alpha < 0.0 ? 0.0 : alpha;
alpha = alpha > 1.0 ? 1.0 : alpha;
// The first thing that we need to do is to determine the chroma (see above for its definition).
// Remember from above that:
//
// 1. The chroma is the difference between the maximum and the minimum of the RGB channels,
// 2. The value is the maximum of the RGB channels, and
// 3. The saturation comes from dividing the chroma by the maximum of the RGB channels (i.e., the value).
//
// From these facts, you can see that we can retrieve the chroma by simply multiplying the saturation and the value,
// and we can retrieve the minimum of the RGB channels by subtracting the chroma from the value.
var chroma = saturation * value;
var min = value - chroma;
// If the chroma is zero, then we have a greyscale color. In that case, the maximum and the minimum RGB channels
// have the same value (and, indeed, all of the RGB channels are the same), so we can just immediately return
// the minimum value as the value of all the channels.
if (chroma == 0)
{
return Color.FromArgb(
(byte)Math.Round(alpha * 255),
(byte)Math.Round(min * 255),
(byte)Math.Round(min * 255),
(byte)Math.Round(min * 255));
}
// If the chroma is not zero, then we need to continue. The first step is to figure out
// what section of the color wheel we're located in. In order to do that, we'll divide the hue by 60.
// The resulting value means we're in one of the following locations:
//
// 0 - Between red and yellow.
// 1 - Between yellow and green.
// 2 - Between green and cyan.
// 3 - Between cyan and blue.
// 4 - Between blue and purple.
// 5 - Between purple and red.
//
// In each of these sextants, one of the RGB channels is completely present, one is partially present, and one is not present.
// For example, as we transition between red and yellow, red is completely present, green is becoming increasingly present, and blue is not present.
// Then, as we transition from yellow and green, green is now completely present, red is becoming decreasingly present, and blue is still not present.
// As we transition from green to cyan, green is still completely present, blue is becoming increasingly present, and red is no longer present. And so on.
//
// To convert from hue to RGB value, we first need to figure out which of the three channels is in which configuration
// in the sextant that we're located in. Next, we figure out what value the completely-present color should have.
// We know that chroma = (max - min), and we know that this color is the max color, so to find its value we simply add
// min to chroma to retrieve max. Finally, we consider how far we've transitioned from the pure form of that color
// to the next color (e.g., how far we are from pure red towards yellow), and give a value to the partially present channel
// equal to the minimum plus the chroma (i.e., the max minus the min), multiplied by the percentage towards the new color.
// This gets us a value between the maximum and the minimum representing the partially present channel.
// Finally, the not-present color must be equal to the minimum value, since it is the one least participating in the overall color.
int sextant = (int)(hue / 60);
double intermediateColorPercentage = (hue / 60) - sextant;
double max = chroma + min;
double r = 0;
double g = 0;
double b = 0;
switch (sextant)
{
case 0:
r = max;
g = min + (chroma * intermediateColorPercentage);
b = min;
break;
case 1:
r = min + (chroma * (1 - intermediateColorPercentage));
g = max;
b = min;
break;
case 2:
r = min;
g = max;
b = min + (chroma * intermediateColorPercentage);
break;
case 3:
r = min;
g = min + (chroma * (1 - intermediateColorPercentage));
b = max;
break;
case 4:
r = min + (chroma * intermediateColorPercentage);
g = min;
b = max;
break;
case 5:
r = max;
g = min;
b = min + (chroma * (1 - intermediateColorPercentage));
break;
}
return Color.FromArgb(
(byte)Math.Round(alpha * 255),
(byte)Math.Round(r * 255),
(byte)Math.Round(g * 255),
(byte)Math.Round(b * 255));
}
/// <summary>
/// Converts the given RGB color to it's HSV color equivalent.
/// </summary>
/// <param name="color">The color in the RGB color model.</param>
/// <returns>A new <see cref="HsvColor"/> equivalent to the given RGBA values.</returns>
public static HsvColor FromRgb(Color color)
{
return HsvColor.FromRgb(color.R, color.G, color.B, color.A);
}
/// <summary>
/// Converts the given RGBA color channel values to it's HSV color equivalent.
/// </summary>
/// <param name="red">The red channel value in the RGB color model.</param>
/// <param name="green">The green channel value in the RGB color model.</param>
/// <param name="blue">The blue channel value in the RGB color model.</param>
/// <param name="alpha">The alpha channel value.</param>
/// <returns>A new <see cref="HsvColor"/> equivalent to the given RGBA values.</returns>
public static HsvColor FromRgb(
byte red,
byte green,
byte blue,
byte alpha = 0xFF)
{
// Note: Conversion code is originally based on the C++ in WinUI (licensed MIT)
// https://github.com/microsoft/microsoft-ui-xaml/blob/main/dev/Common/ColorConversion.cpp
// This was used because it is the best documented and likely most optimized for performance
// Alpha channel support was added
// Normalize RGBA channel values into the 0..1 range used by this algorithm
double r = red / 255.0;
double g = green / 255.0;
double b = blue / 255.0;
double a = alpha / 255.0;
double hue;
double saturation;
double value;
double max = r >= g ? (r >= b ? r : b) : (g >= b ? g : b);
double min = r <= g ? (r <= b ? r : b) : (g <= b ? g : b);
// The value, a number between 0 and 1, is the largest of R, G, and B (divided by 255).
// Conceptually speaking, it represents how much color is present.
// If at least one of R, G, B is 255, then there exists as much color as there can be.
// If RGB = (0, 0, 0), then there exists no color at all - a value of zero corresponds
// to black (i.e., the absence of any color).
value = max;
// The "chroma" of the color is a value directly proportional to the extent to which
// the color diverges from greyscale. If, for example, we have RGB = (255, 255, 0),
// then the chroma is maximized - this is a pure yellow, no gray of any kind.
// On the other hand, if we have RGB = (128, 128, 128), then the chroma being zero
// implies that this color is pure greyscale, with no actual hue to be found.
var chroma = max - min;
// If the chrome is zero, then hue is technically undefined - a greyscale color
// has no hue. For the sake of convenience, we'll just set hue to zero, since
// it will be unused in this circumstance. Since the color is purely gray,
// saturation is also equal to zero - you can think of saturation as basically
// a measure of hue intensity, such that no hue at all corresponds to a
// nonexistent intensity.
if (chroma == 0)
{
hue = 0.0;
saturation = 0.0;
}
else
{
// In this block, hue is properly defined, so we'll extract both hue
// and saturation information from the RGB color.
// Hue can be thought of as a cyclical thing, between 0 degrees and 360 degrees.
// A hue of 0 degrees is red; 120 degrees is green; 240 degrees is blue; and 360 is back to red.
// Every other hue is somewhere between either red and green, green and blue, and blue and red,
// so every other hue can be thought of as an angle on this color wheel.
// These if/else statements determines where on this color wheel our color lies.
if (r == max)
{
// If the red channel is the most pronounced channel, then we exist
// somewhere between (-60, 60) on the color wheel - i.e., the section around 0 degrees
// where red dominates. We figure out where in that section we are exactly
// by considering whether the green or the blue channel is greater - by subtracting green from blue,
// then if green is greater, we'll nudge ourselves closer to 60, whereas if blue is greater, then
// we'll nudge ourselves closer to -60. We then divide by chroma (which will actually make the result larger,
// since chroma is a value between 0 and 1) to normalize the value to ensure that we get the right hue
// even if we're very close to greyscale.
hue = 60 * (g - b) / chroma;
}
else if (g == max)
{
// We do the exact same for the case where the green channel is the most pronounced channel,
// only this time we want to see if we should tilt towards the blue direction or the red direction.
// We add 120 to center our value in the green third of the color wheel.
hue = 120 + (60 * (b - r) / chroma);
}
else // blue == max
{
// And we also do the exact same for the case where the blue channel is the most pronounced channel,
// only this time we want to see if we should tilt towards the red direction or the green direction.
// We add 240 to center our value in the blue third of the color wheel.
hue = 240 + (60 * (r - g) / chroma);
}
// Since we want to work within the range [0, 360), we'll add 360 to any value less than zero -
// this will bump red values from within -60 to -1 to 300 to 359. The hue is the same at both values.
if (hue < 0.0)
{
hue += 360.0;
}
// The saturation, our final HSV axis, can be thought of as a value between 0 and 1 indicating how intense our color is.
// To find it, we divide the chroma - the distance between the minimum and the maximum RGB channels - by the maximum channel (i.e., the value).
// This effectively normalizes the chroma - if the maximum is 0.5 and the minimum is 0, the saturation will be (0.5 - 0) / 0.5 = 1,
// meaning that although this color is not as bright as it can be, the dark color is as intense as it possibly could be.
// If, on the other hand, the maximum is 0.5 and the minimum is 0.25, then the saturation will be (0.5 - 0.25) / 0.5 = 0.5,
// meaning that this color is partially washed out.
// A saturation value of 0 corresponds to a greyscale color, one in which the color is *completely* washed out and there is no actual hue.
saturation = chroma / value;
}
return new HsvColor(a, hue, saturation, value, false);
}
/// <summary>
/// Indicates whether the values of two specified <see cref="HsvColor"/> objects are equal.
/// </summary>
/// <param name="left">The first object to compare.</param>
/// <param name="right">The second object to compare.</param>
/// <returns>True if left and right are equal; otherwise, false.</returns>
public static bool operator ==(HsvColor left, HsvColor right)
{
return left.Equals(right);
}
/// <summary>
/// Indicates whether the values of two specified <see cref="HsvColor"/> objects are not equal.
/// </summary>
/// <param name="left">The first object to compare.</param>
/// <param name="right">The second object to compare.</param>
/// <returns>True if left and right are not equal; otherwise, false.</returns>
public static bool operator !=(HsvColor left, HsvColor right)
{
return !(left == right);
}
/// <summary>
/// Explicit conversion from an <see cref="HsvColor"/> to a <see cref="Color"/>.
/// </summary>
/// <param name="hsvColor">The <see cref="HsvColor"/> to convert.</param>
public static explicit operator Color(HsvColor hsvColor)
{
return hsvColor.ToRgb();
}
}
}

5
src/Avalonia.Visuals/Media/IBrush.cs

@ -12,5 +12,10 @@ namespace Avalonia.Media
/// Gets the opacity of the brush.
/// </summary>
double Opacity { get; }
/// <summary>
/// Gets the transform of the brush.
/// </summary>
ITransform? Transform { get; }
}
}

4
src/Avalonia.Visuals/Media/Immutable/ImmutableConicGradientBrush.cs

@ -12,16 +12,18 @@ namespace Avalonia.Media.Immutable
/// </summary>
/// <param name="gradientStops">The gradient stops.</param>
/// <param name="opacity">The opacity of the brush.</param>
/// <param name="transform">The transform of the brush.</param>
/// <param name="spreadMethod">The spread method.</param>
/// <param name="center">The center point for the gradient.</param>
/// <param name="angle">The starting angle for the gradient.</param>
public ImmutableConicGradientBrush(
IReadOnlyList<ImmutableGradientStop> gradientStops,
double opacity = 1,
ImmutableTransform? transform = null,
GradientSpreadMethod spreadMethod = GradientSpreadMethod.Pad,
RelativePoint? center = null,
double angle = 0)
: base(gradientStops, opacity, spreadMethod)
: base(gradientStops, opacity, transform, spreadMethod)
{
Center = center ?? RelativePoint.Center;
Angle = angle;

10
src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs

@ -12,14 +12,17 @@ namespace Avalonia.Media.Immutable
/// </summary>
/// <param name="gradientStops">The gradient stops.</param>
/// <param name="opacity">The opacity of the brush.</param>
/// <param name="transform">The transform of the brush.</param>
/// <param name="spreadMethod">The spread method.</param>
protected ImmutableGradientBrush(
IReadOnlyList<ImmutableGradientStop> gradientStops,
double opacity,
ImmutableTransform? transform,
GradientSpreadMethod spreadMethod)
{
GradientStops = gradientStops;
Opacity = opacity;
Transform = transform;
SpreadMethod = spreadMethod;
}
@ -28,7 +31,7 @@ namespace Avalonia.Media.Immutable
/// </summary>
/// <param name="source">The brush from which this brush's properties should be copied.</param>
protected ImmutableGradientBrush(GradientBrush source)
: this(source.GradientStops.ToImmutable(), source.Opacity, source.SpreadMethod)
: this(source.GradientStops.ToImmutable(), source.Opacity, source.Transform?.ToImmutable(), source.SpreadMethod)
{
}
@ -39,6 +42,11 @@ namespace Avalonia.Media.Immutable
/// <inheritdoc/>
public double Opacity { get; }
/// <summary>
/// Gets the transform of the brush.
/// </summary>
public ITransform? Transform { get; }
/// <inheritdoc/>
public GradientSpreadMethod SpreadMethod { get; }
}

3
src/Avalonia.Visuals/Media/Immutable/ImmutableImageBrush.cs

@ -16,6 +16,7 @@ namespace Avalonia.Media.Immutable
/// <param name="alignmentY">The vertical alignment of a tile in the destination.</param>
/// <param name="destinationRect">The rectangle on the destination in which to paint a tile.</param>
/// <param name="opacity">The opacity of the brush.</param>
/// <param name="transform">The transform of the brush.</param>
/// <param name="sourceRect">The rectangle of the source image that will be displayed.</param>
/// <param name="stretch">
/// How the source rectangle will be stretched to fill the destination rect.
@ -28,6 +29,7 @@ namespace Avalonia.Media.Immutable
AlignmentY alignmentY = AlignmentY.Center,
RelativeRect? destinationRect = null,
double opacity = 1,
ImmutableTransform? transform = null,
RelativeRect? sourceRect = null,
Stretch stretch = Stretch.Uniform,
TileMode tileMode = TileMode.None,
@ -37,6 +39,7 @@ namespace Avalonia.Media.Immutable
alignmentY,
destinationRect ?? RelativeRect.Fill,
opacity,
transform,
sourceRect ?? RelativeRect.Fill,
stretch,
tileMode,

4
src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs

@ -12,16 +12,18 @@ namespace Avalonia.Media.Immutable
/// </summary>
/// <param name="gradientStops">The gradient stops.</param>
/// <param name="opacity">The opacity of the brush.</param>
/// <param name="transform">The transform of the brush.</param>
/// <param name="spreadMethod">The spread method.</param>
/// <param name="startPoint">The start point for the gradient.</param>
/// <param name="endPoint">The end point for the gradient.</param>
public ImmutableLinearGradientBrush(
IReadOnlyList<ImmutableGradientStop> gradientStops,
double opacity = 1,
ImmutableTransform? transform = null,
GradientSpreadMethod spreadMethod = GradientSpreadMethod.Pad,
RelativePoint? startPoint = null,
RelativePoint? endPoint = null)
: base(gradientStops, opacity, spreadMethod)
: base(gradientStops, opacity, transform, spreadMethod)
{
StartPoint = startPoint ?? RelativePoint.TopLeft;
EndPoint = endPoint ?? RelativePoint.BottomRight;

4
src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs

@ -12,6 +12,7 @@ namespace Avalonia.Media.Immutable
/// </summary>
/// <param name="gradientStops">The gradient stops.</param>
/// <param name="opacity">The opacity of the brush.</param>
/// <param name="transform">The transform of the brush.</param>
/// <param name="spreadMethod">The spread method.</param>
/// <param name="center">The start point for the gradient.</param>
/// <param name="gradientOrigin">
@ -23,11 +24,12 @@ namespace Avalonia.Media.Immutable
public ImmutableRadialGradientBrush(
IReadOnlyList<ImmutableGradientStop> gradientStops,
double opacity = 1,
ImmutableTransform? transform = null,
GradientSpreadMethod spreadMethod = GradientSpreadMethod.Pad,
RelativePoint? center = null,
RelativePoint? gradientOrigin = null,
double radius = 0.5)
: base(gradientStops, opacity, spreadMethod)
: base(gradientStops, opacity, transform, spreadMethod)
{
Center = center ?? RelativePoint.Center;
GradientOrigin = gradientOrigin ?? RelativePoint.Center;

15
src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs

@ -12,10 +12,12 @@ namespace Avalonia.Media.Immutable
/// </summary>
/// <param name="color">The color to use.</param>
/// <param name="opacity">The opacity of the brush.</param>
public ImmutableSolidColorBrush(Color color, double opacity = 1)
/// <param name="transform">The transform of the brush.</param>
public ImmutableSolidColorBrush(Color color, double opacity = 1, ImmutableTransform? transform = null)
{
Color = color;
Opacity = opacity;
Transform = null;
}
/// <summary>
@ -32,7 +34,7 @@ namespace Avalonia.Media.Immutable
/// </summary>
/// <param name="source">The brush from which this brush's properties should be copied.</param>
public ImmutableSolidColorBrush(ISolidColorBrush source)
: this(source.Color, source.Opacity)
: this(source.Color, source.Opacity, source.Transform?.ToImmutable())
{
}
@ -46,11 +48,16 @@ namespace Avalonia.Media.Immutable
/// </summary>
public double Opacity { get; }
/// <summary>
/// Gets the transform of the brush.
/// </summary>
public ITransform? Transform { get; }
public bool Equals(ImmutableSolidColorBrush? other)
{
if (ReferenceEquals(null, other)) return false;
if (ReferenceEquals(this, other)) return true;
return Color.Equals(other.Color) && Opacity.Equals(other.Opacity);
return Color.Equals(other.Color) && Opacity.Equals(other.Opacity) && (Transform == null && other.Transform == null ? true : (Transform != null && Transform.Equals(other.Transform)));
}
public override bool Equals(object? obj)
@ -62,7 +69,7 @@ namespace Avalonia.Media.Immutable
{
unchecked
{
return (Color.GetHashCode() * 397) ^ Opacity.GetHashCode();
return (Color.GetHashCode() * 397) ^ Opacity.GetHashCode() ^ (Transform is null ? 0 : Transform.GetHashCode());
}
}

9
src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs

@ -14,6 +14,7 @@ namespace Avalonia.Media.Immutable
/// <param name="alignmentY">The vertical alignment of a tile in the destination.</param>
/// <param name="destinationRect">The rectangle on the destination in which to paint a tile.</param>
/// <param name="opacity">The opacity of the brush.</param>
/// <param name="transform">The transform of the brush.</param>
/// <param name="sourceRect">The rectangle of the source image that will be displayed.</param>
/// <param name="stretch">
/// How the source rectangle will be stretched to fill the destination rect.
@ -25,6 +26,7 @@ namespace Avalonia.Media.Immutable
AlignmentY alignmentY,
RelativeRect destinationRect,
double opacity,
ImmutableTransform? transform,
RelativeRect sourceRect,
Stretch stretch,
TileMode tileMode,
@ -34,6 +36,7 @@ namespace Avalonia.Media.Immutable
AlignmentY = alignmentY;
DestinationRect = destinationRect;
Opacity = opacity;
Transform = transform;
SourceRect = sourceRect;
Stretch = stretch;
TileMode = tileMode;
@ -50,6 +53,7 @@ namespace Avalonia.Media.Immutable
source.AlignmentY,
source.DestinationRect,
source.Opacity,
source.Transform?.ToImmutable(),
source.SourceRect,
source.Stretch,
source.TileMode,
@ -69,6 +73,11 @@ namespace Avalonia.Media.Immutable
/// <inheritdoc/>
public double Opacity { get; }
/// <summary>
/// Gets the transform of the brush.
/// </summary>
public ITransform? Transform { get; }
/// <inheritdoc/>
public RelativeRect SourceRect { get; }

21
src/Avalonia.Visuals/Media/Immutable/ImmutableTransform.cs

@ -0,0 +1,21 @@
using Avalonia.VisualTree;
namespace Avalonia.Media.Immutable
{
/// <summary>
/// Represents a transform on an <see cref="IVisual"/>.
/// </summary>
public class ImmutableTransform : ITransform
{
public Matrix Value { get; }
/// <summary>
/// Initializes a new instance of the <see cref="ImmutableTransform"/> class.
/// </summary>
/// <param name="matrix">The transform matrix.</param>
public ImmutableTransform(Matrix matrix)
{
Value = matrix;
}
}
}

3
src/Avalonia.Visuals/Media/Immutable/ImmutableVisualBrush.cs

@ -16,6 +16,7 @@ namespace Avalonia.Media.Immutable
/// <param name="alignmentY">The vertical alignment of a tile in the destination.</param>
/// <param name="destinationRect">The rectangle on the destination in which to paint a tile.</param>
/// <param name="opacity">The opacity of the brush.</param>
/// <param name="transform">The transform of the brush.</param>
/// <param name="sourceRect">The rectangle of the source image that will be displayed.</param>
/// <param name="stretch">
/// How the source rectangle will be stretched to fill the destination rect.
@ -28,6 +29,7 @@ namespace Avalonia.Media.Immutable
AlignmentY alignmentY = AlignmentY.Center,
RelativeRect? destinationRect = null,
double opacity = 1,
ImmutableTransform? transform = null,
RelativeRect? sourceRect = null,
Stretch stretch = Stretch.Uniform,
TileMode tileMode = TileMode.None,
@ -37,6 +39,7 @@ namespace Avalonia.Media.Immutable
alignmentY,
destinationRect ?? RelativeRect.Fill,
opacity,
transform,
sourceRect ?? RelativeRect.Fill,
stretch,
tileMode,

10
src/Avalonia.Visuals/Media/Transform.cs

@ -1,6 +1,7 @@
using System;
using Avalonia.Animation;
using Avalonia.Animation.Animators;
using Avalonia.Media.Immutable;
using Avalonia.VisualTree;
namespace Avalonia.Media
@ -44,6 +45,15 @@ namespace Avalonia.Media
Changed?.Invoke(this, EventArgs.Empty);
}
/// <summary>
/// Converts a transform to an immutable transform.
/// </summary>
/// <returns>The immutable transform</returns>
public ImmutableTransform ToImmutable()
{
return new ImmutableTransform(this.Value);
}
/// <summary>
/// Returns a String representing this transform matrix instance.
/// </summary>

26
src/Avalonia.Visuals/Media/TransformExtensions.cs

@ -0,0 +1,26 @@
using System;
using Avalonia.Media.Immutable;
namespace Avalonia.Media
{
/// <summary>
/// Extension methods for transform classes.
/// </summary>
public static class TransformExtensions
{
/// <summary>
/// Converts a transform to an immutable transform.
/// </summary>
/// <param name="transform">The transform.</param>
/// <returns>
/// The result of calling <see cref="Transform.ToImmutable"/> if the transform is mutable,
/// otherwise <paramref name="transform"/>.
/// </returns>
public static ImmutableTransform ToImmutable(this ITransform transform)
{
_ = transform ?? throw new ArgumentNullException(nameof(transform));
return (transform as Transform)?.ToImmutable() ?? new ImmutableTransform(transform.Value);
}
}
}

31
src/Avalonia.Visuals/Rendering/SceneGraph/GeometryBoundsHelper.cs

@ -0,0 +1,31 @@
using System;
using Avalonia.Media;
using Avalonia.Utilities;
namespace Avalonia.Rendering.SceneGraph;
internal static class GeometryBoundsHelper
{
/// <summary>
/// Calculates the bounds of a given geometry with respect to the pens <see cref="IPen.LineCap"/>
/// </summary>
/// <param name="originalBounds">The calculated bounds without <see cref="IPen.LineCap"/>s</param>
/// <param name="pen">The pen with information about the <see cref="IPen.LineCap"/>s</param>
/// <returns></returns>
public static Rect CalculateBoundsWithLineCaps(this Rect originalBounds, IPen? pen)
{
if (pen is null || MathUtilities.IsZero(pen.Thickness)) return originalBounds;
switch (pen.LineCap)
{
case PenLineCap.Flat:
return originalBounds;
case PenLineCap.Round:
return originalBounds.Inflate(pen.Thickness / 2);
case PenLineCap.Square:
return originalBounds.Inflate(pen.Thickness);
default:
throw new ArgumentOutOfRangeException();
}
}
}

2
src/Avalonia.Visuals/Rendering/SceneGraph/GeometryNode.cs

@ -24,7 +24,7 @@ namespace Avalonia.Rendering.SceneGraph
IPen? pen,
IGeometryImpl geometry,
IDictionary<IVisual, Scene>? childScenes = null)
: base(geometry.GetRenderBounds(pen), transform)
: base(geometry.GetRenderBounds(pen).CalculateBoundsWithLineCaps(pen), transform)
{
Transform = transform;
Brush = brush?.ToImmutable();

2
src/Avalonia.X11/ICELib.cs

@ -46,7 +46,7 @@ namespace Avalonia.X11
IntPtr iceConn,
bool swap,
int offendingMinorOpcode,
ulong offendingSequence,
nuint offendingSequence,
int errorClass,
int severity,
IntPtr values

2
src/Avalonia.X11/SMLib.cs

@ -124,7 +124,7 @@ namespace Avalonia.X11
IntPtr smcConn,
bool swap,
int offendingMinorOpcode,
ulong offendingSequence,
nuint offendingSequence,
int errorClass,
int severity,
IntPtr values

4
src/Avalonia.X11/X11PlatformLifetimeEvents.cs

@ -153,14 +153,14 @@ namespace Avalonia.X11
}
private static void StaticErrorHandler(IntPtr smcConn, bool swap, int offendingMinorOpcode,
ulong offendingSequence, int errorClass, int severity, IntPtr values)
nuint offendingSequence, int errorClass, int severity, IntPtr values)
{
GetInstance(smcConn)
?.ErrorHandler(swap, offendingMinorOpcode, offendingSequence, errorClass, severity, values);
}
// ReSharper disable UnusedParameter.Local
private void ErrorHandler(bool swap, int offendingMinorOpcode, ulong offendingSequence, int errorClass,
private void ErrorHandler(bool swap, int offendingMinorOpcode, nuint offendingSequence, int errorClass,
int severity, IntPtr values)
{
Logger.TryGet(LogEventLevel.Warning, LogArea.X11Platform)?.Log(this,

7
src/Avalonia.X11/X11Window.Xim.cs

@ -10,7 +10,6 @@ namespace Avalonia.X11
{
partial class X11Window
{
class XimInputMethod : ITextInputMethodImpl, IX11InputMethodControl
{
private readonly X11Window _parent;
@ -58,9 +57,9 @@ namespace Avalonia.X11
UpdateActive();
}
public void SetActive(bool active)
public void SetClient(ITextInputMethodClient client)
{
_controlActive = active;
_controlActive = client is { };
UpdateActive();
}
@ -87,7 +86,7 @@ namespace Avalonia.X11
// No-op
}
public void SetOptions(TextInputOptionsQueryEventArgs options)
public void SetOptions(TextInputOptions options)
{
// No-op
}

1
src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt

@ -0,0 +1 @@
Total Issues: 0

1
src/Markup/Avalonia.Markup/ApiCompatBaseline.txt

@ -0,0 +1 @@
Total Issues: 0

67
src/Skia/Avalonia.Skia/DrawingContextImpl.cs

@ -614,10 +614,21 @@ namespace Avalonia.Skia
var end = linearGradient.EndPoint.ToPixels(targetSize).ToSKPoint();
// would be nice to cache these shaders possibly?
using (var shader =
SKShader.CreateLinearGradient(start, end, stopColors, stopOffsets, tileMode))
if (linearGradient.Transform is null)
{
paintWrapper.Paint.Shader = shader;
using (var shader =
SKShader.CreateLinearGradient(start, end, stopColors, stopOffsets, tileMode))
{
paintWrapper.Paint.Shader = shader;
}
}
else
{
using (var shader =
SKShader.CreateLinearGradient(start, end, stopColors, stopOffsets, tileMode, linearGradient.Transform.Value.ToSKMatrix()))
{
paintWrapper.Paint.Shader = shader;
}
}
break;
@ -632,10 +643,21 @@ namespace Avalonia.Skia
if (origin.Equals(center))
{
// when the origin is the same as the center the Skia RadialGradient acts the same as D2D
using (var shader =
SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode))
if (radialGradient.Transform is null)
{
paintWrapper.Paint.Shader = shader;
using (var shader =
SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode))
{
paintWrapper.Paint.Shader = shader;
}
}
else
{
using (var shader =
SKShader.CreateRadialGradient(center, radius, stopColors, stopOffsets, tileMode, radialGradient.Transform.Value.ToSKMatrix()))
{
paintWrapper.Paint.Shader = shader;
}
}
}
else
@ -659,12 +681,25 @@ namespace Avalonia.Skia
}
// compose with a background colour of the final stop to match D2D's behaviour of filling with the final color
using (var shader = SKShader.CreateCompose(
SKShader.CreateColor(reversedColors[0]),
SKShader.CreateTwoPointConicalGradient(center, radius, origin, 0, reversedColors, reversedStops, tileMode)
))
if (radialGradient.Transform is null)
{
paintWrapper.Paint.Shader = shader;
using (var shader = SKShader.CreateCompose(
SKShader.CreateColor(reversedColors[0]),
SKShader.CreateTwoPointConicalGradient(center, radius, origin, 0, reversedColors, reversedStops, tileMode)
))
{
paintWrapper.Paint.Shader = shader;
}
}
else
{
using (var shader = SKShader.CreateCompose(
SKShader.CreateColor(reversedColors[0]),
SKShader.CreateTwoPointConicalGradient(center, radius, origin, 0, reversedColors, reversedStops, tileMode, radialGradient.Transform.Value.ToSKMatrix())
))
{
paintWrapper.Paint.Shader = shader;
}
}
}
@ -679,6 +714,11 @@ namespace Avalonia.Skia
var angle = (float)(conicGradient.Angle - 90);
var rotation = SKMatrix.CreateRotationDegrees(angle, center.X, center.Y);
if (conicGradient.Transform is { })
{
rotation = rotation.PreConcat(conicGradient.Transform.Value.ToSKMatrix());
}
using (var shader =
SKShader.CreateSweepGradient(center, stopColors, stopOffsets, rotation))
{
@ -751,6 +791,11 @@ namespace Avalonia.Skia
tileTransform,
SKMatrix.CreateScale((float)(96.0 / _dpi.X), (float)(96.0 / _dpi.Y)));
if (tileBrush.Transform is { })
{
paintTransform = paintTransform.PreConcat(tileBrush.Transform.Value.ToSKMatrix());
}
using (var shader = image.ToShader(tileX, tileY, paintTransform))
{
paintWrapper.Paint.Shader = shader;

6
src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs

@ -367,10 +367,12 @@ namespace Avalonia.Web.Blazor
}
}
public void SetActive(bool active)
public void SetClient(ITextInputMethodClient? client)
{
_inputHelper.Clear();
var active = client is { };
if (active)
{
_inputHelper.Show();
@ -386,7 +388,7 @@ namespace Avalonia.Web.Blazor
{
}
public void SetOptions(TextInputOptionsQueryEventArgs options)
public void SetOptions(TextInputOptions options)
{
}

2
src/Web/Avalonia.Web.Blazor/Interop/Typescript/types/dotnet/index.d.ts

@ -6,7 +6,7 @@
// Here be dragons!
// This is community-maintained definition file intended to ease the process of developing
// high quality JavaScript interop code to be used in Blazor application from your C# .Net code.
// high quality JavaScript interop code to be used in Blazor application from your C# .NET code.
// Could be removed without a notice in case official definition types ships with Blazor itself.
// tslint:disable:no-unnecessary-generics

8
src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs

@ -74,12 +74,12 @@ namespace Avalonia.Win32.Input
}
}
public void SetActive(bool active)
public void SetClient(ITextInputMethodClient client)
{
_active = active;
_active = client is { };
Dispatcher.UIThread.Post(() =>
{
if (active)
if (_active)
{
if (DefaultImc != IntPtr.Zero)
{
@ -216,7 +216,7 @@ namespace Avalonia.Win32.Input
ImmSetCompositionFont(himc, ref logFont);
}
public void SetOptions(TextInputOptionsQueryEventArgs options)
public void SetOptions(TextInputOptions options)
{
// we're skipping this. not usable on windows
}

11
src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs

@ -765,6 +765,14 @@ namespace Avalonia.Win32.Interop
DWMWA_CLOAK,
DWMWA_CLOAKED,
DWMWA_FREEZE_REPRESENTATION,
DWMWA_PASSIVE_UPDATE_MODE,
DWMWA_USE_HOSTBACKDROPBRUSH,
DWMWA_USE_IMMERSIVE_DARK_MODE = 20,
DWMWA_WINDOW_CORNER_PREFERENCE = 33,
DWMWA_BORDER_COLOR,
DWMWA_CAPTION_COLOR,
DWMWA_TEXT_COLOR,
DWMWA_VISIBLE_FRAME_BORDER_THICKNESS,
DWMWA_LAST
};
@ -1456,6 +1464,9 @@ namespace Avalonia.Win32.Interop
[DllImport("dwmapi.dll")]
public static extern int DwmGetWindowAttribute(IntPtr hwnd, int dwAttribute, out RECT pvAttribute, int cbAttribute);
[DllImport("dwmapi.dll")]
public static extern int DwmSetWindowAttribute(IntPtr hwnd, int dwAttribute, void* pvAttribute, int cbAttribute);
[DllImport("dwmapi.dll")]
public static extern int DwmIsCompositionEnabled(out bool enabled);

60
src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs

@ -17,11 +17,11 @@ namespace Avalonia.Win32.WinRT.Composition
{
class WinUICompositorConnection : IRenderTimer
{
public static readonly Version MinHostBackdropVersion = new Version(10, 0, 22000);
private readonly float? _backdropCornerRadius;
private readonly EglContext _syncContext;
private readonly ICompositionBrush _micaBrush;
private ICompositor _compositor;
private ICompositor2 _compositor2;
private ICompositor5 _compositor5;
private ICompositorInterop _compositorInterop;
private AngleWin32EglDisplay _angle;
@ -39,12 +39,11 @@ namespace Avalonia.Win32.WinRT.Composition
_syncContext = _gl.PrimaryEglContext;
_angle = (AngleWin32EglDisplay)_gl.Display;
_compositor = NativeWinRTMethods.CreateInstance<ICompositor>("Windows.UI.Composition.Compositor");
_compositor2 = _compositor.QueryInterface<ICompositor2>();
_compositor5 = _compositor.QueryInterface<ICompositor5>();
_compositorInterop = _compositor.QueryInterface<ICompositorInterop>();
_compositorDesktopInterop = _compositor.QueryInterface<ICompositorDesktopInterop>();
using var device = MicroComRuntime.CreateProxyFor<IUnknown>(_angle.GetDirect3DDevice(), true);
_device = _compositorInterop.CreateGraphicsDevice(device);
_blurBrush = CreateAcrylicBlurBackdropBrush();
_micaBrush = CreateMicaBackdropBrush();
@ -71,7 +70,7 @@ namespace Avalonia.Win32.WinRT.Composition
AvaloniaLocator.CurrentMutable.BindToSelf(connect);
AvaloniaLocator.CurrentMutable.Bind<IRenderTimer>().ToConstant(connect);
tcs.SetResult(true);
}
catch (Exception e)
{
@ -99,7 +98,7 @@ namespace Avalonia.Win32.WinRT.Composition
}
public void Dispose()
{
}
public void Invoke(IAsyncAction asyncInfo, AsyncStatus asyncStatus)
@ -118,12 +117,12 @@ namespace Avalonia.Win32.WinRT.Composition
{
}
}
private void RunLoop()
{
{
var st = Stopwatch.StartNew();
using (var act = _compositor5.RequestCommitAsync())
using (var act = _compositor5.RequestCommitAsync())
act.SetCompleted(new RunLoopHandler(this));
while (true)
{
@ -172,12 +171,12 @@ namespace Avalonia.Win32.WinRT.Composition
using var sc = _syncContext.EnsureLocked();
using var desktopTarget = _compositorDesktopInterop.CreateDesktopWindowTarget(hWnd, 0);
using var target = desktopTarget.QueryInterface<ICompositionTarget>();
using var drawingSurface = _device.CreateDrawingSurface(new UnmanagedMethods.SIZE(), DirectXPixelFormat.B8G8R8A8UIntNormalized,
DirectXAlphaMode.Premultiplied);
using var surface = drawingSurface.QueryInterface<ICompositionSurface>();
using var surfaceInterop = drawingSurface.QueryInterface<ICompositionDrawingSurfaceInterop>();
using var surfaceBrush = _compositor.CreateSurfaceBrushWithSurface(surface);
using var brush = surfaceBrush.QueryInterface<ICompositionBrush>();
@ -190,7 +189,7 @@ namespace Avalonia.Win32.WinRT.Composition
using var containerVisual2 = container.QueryInterface<IVisual2>();
containerVisual2.SetRelativeSizeAdjustment(new Vector2(1, 1));
using var containerChildren = container.Children;
target.SetRoot(containerVisual);
using var blur = CreateBlurVisual(_blurBrush);
@ -202,10 +201,10 @@ namespace Avalonia.Win32.WinRT.Composition
}
var compositionRoundedRectangleGeometry = ClipVisual(blur, mica);
containerChildren.InsertAtTop(blur);
containerChildren.InsertAtTop(visual);
return new WinUICompositedWindow(_syncContext, _compositor, _pumpLock, target, surfaceInterop, visual,
blur, mica, compositionRoundedRectangleGeometry);
}
@ -234,10 +233,8 @@ namespace Avalonia.Win32.WinRT.Composition
var blurEffect = new WinUIGaussianBlurEffect(backDropParameterAsSource);
using var blurEffectFactory = _compositor.CreateEffectFactory(blurEffect);
using var compositionEffectBrush = blurEffectFactory.CreateBrush();
using var backdrop = _compositor2.CreateBackdropBrush();
using var backdropBrush = backdrop.QueryInterface<ICompositionBrush>();
using var backdropBrush = CreateBackdropBrush();
var saturateEffect = new SaturationEffect(blurEffect);
using var satEffectFactory = _compositor.CreateEffectFactory(saturateEffect);
using var sat = satEffectFactory.CreateBrush();
@ -261,8 +258,8 @@ namespace Avalonia.Win32.WinRT.Composition
foreach (var visual in containerVisuals)
{
visual?.SetClip(geometricClipWithGeometry.QueryInterface<ICompositionClip>());
}
}
return roundedRectangleGeometry.CloneReference();
}
@ -271,8 +268,8 @@ namespace Avalonia.Win32.WinRT.Composition
using var spriteVisual = _compositor.CreateSpriteVisual();
using var visual = spriteVisual.QueryInterface<IVisual>();
using var visual2 = spriteVisual.QueryInterface<IVisual2>();
spriteVisual.SetBrush(compositionBrush);
visual.SetIsVisible(0);
visual2.SetRelativeSizeAdjustment(new Vector2(1.0f, 1.0f));
@ -280,6 +277,29 @@ namespace Avalonia.Win32.WinRT.Composition
return visual.CloneReference();
}
private ICompositionBrush CreateBackdropBrush()
{
ICompositionBackdropBrush brush = null;
try
{
if (Win32Platform.WindowsVersion >= MinHostBackdropVersion)
{
using var compositor3 = _compositor.QueryInterface<ICompositor3>();
brush = compositor3.CreateHostBackdropBrush();
}
else
{
using var compositor2 = _compositor.QueryInterface<ICompositor2>();
brush = compositor2.CreateBackdropBrush();
}
return brush.QueryInterface<ICompositionBrush>();
}
finally
{
brush?.Dispose();
}
}
public event Action<TimeSpan> Tick;
}

6
src/Windows/Avalonia.Win32/WinRT/winrt.idl

@ -358,6 +358,12 @@ interface ICompositor2 : IInspectable
[overload("CreateStepEasingFunction")] HRESULT CreateStepEasingFunctionWithStepCount([in] INT32 stepCount, [out] [retval] void** result);
}
[uuid(C9DD8EF0-6EB1-4E3C-A658-675D9C64D4AB)]
interface ICompositor3 : IInspectable
{
HRESULT CreateHostBackdropBrush([out][retval] ICompositionBackdropBrush** result);
}
[uuid(0D8FB190-F122-5B8D-9FDD-543B0D8EB7F3)]
interface ICompositorWithBlurredWallpaperBackdropBrush : IInspectable
{

83
src/Windows/Avalonia.Win32/WindowImpl.cs

@ -86,7 +86,7 @@ namespace Avalonia.Win32
private Size _minSize;
private Size _maxSize;
private POINT _maxTrackSize;
private WindowImpl _parent;
private WindowImpl _parent;
private ExtendClientAreaChromeHints _extendChromeHints = ExtendClientAreaChromeHints.Default;
private bool _isCloseRequested;
private bool _shown;
@ -174,7 +174,7 @@ namespace Avalonia.Win32
public Action<PixelPoint> PositionChanged { get; set; }
public Action<WindowState> WindowStateChanged { get; set; }
public Action LostFocus { get; set; }
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
@ -247,7 +247,7 @@ namespace Avalonia.Win32
{
get
{
if(_isFullScreenActive)
if (_isFullScreenActive)
{
return WindowState.FullScreen;
}
@ -270,7 +270,7 @@ namespace Avalonia.Win32
ShowWindow(value, value != WindowState.Minimized); // If the window is minimized, it shouldn't be activated
}
_showWindowState = value;
_showWindowState = value;
}
}
@ -278,7 +278,7 @@ namespace Avalonia.Win32
protected IntPtr Hwnd => _hwnd;
public void SetTransparencyLevelHint (WindowTransparencyLevel transparencyLevel)
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
{
TransparencyLevel = EnableBlur(transparencyLevel);
}
@ -318,12 +318,12 @@ namespace Avalonia.Win32
}
var blurInfo = new DWM_BLURBEHIND(false);
if (transparencyLevel == WindowTransparencyLevel.Blur)
{
blurInfo = new DWM_BLURBEHIND(true);
}
DwmEnableBlurBehindWindow(_hwnd, ref blurInfo);
if (transparencyLevel == WindowTransparencyLevel.Transparent)
@ -379,13 +379,24 @@ namespace Avalonia.Win32
{
if (_isUsingComposition)
{
_blurHost?.SetBlur(transparencyLevel switch
var effect = transparencyLevel switch
{
WindowTransparencyLevel.Mica => BlurEffect.Mica,
WindowTransparencyLevel.AcrylicBlur => BlurEffect.Acrylic,
WindowTransparencyLevel.Blur => BlurEffect.Acrylic,
_ => BlurEffect.None
});
};
if (Win32Platform.WindowsVersion >= WinUICompositorConnection.MinHostBackdropVersion)
{
unsafe
{
int pvUseBackdropBrush = effect == BlurEffect.Acrylic ? 1 : 0;
DwmSetWindowAttribute(_hwnd, (int)DwmWindowAttribute.DWMWA_USE_HOSTBACKDROPBRUSH, &pvUseBackdropBrush, sizeof(int));
}
}
_blurHost?.SetBlur(effect);
return transparencyLevel;
}
@ -483,12 +494,12 @@ namespace Avalonia.Win32
if (customRendererFactory != null)
return customRendererFactory.Create(root, loop);
return Win32Platform.UseDeferredRendering
? _isUsingComposition
return Win32Platform.UseDeferredRendering
? _isUsingComposition
? new DeferredRenderer(root, loop)
{
RenderOnlyOnRenderThread = true
}
}
: (IRenderer)new DeferredRenderer(root, loop, rendererLock: _rendererLock)
: new ImmediateRenderer(root);
}
@ -543,7 +554,7 @@ namespace Avalonia.Win32
{
BeforeCloseCleanup(true);
}
DestroyWindow(_hwnd);
_hwnd = IntPtr.Zero;
}
@ -609,7 +620,7 @@ namespace Avalonia.Win32
public void SetParent(IWindowImpl parent)
{
_parent = (WindowImpl)parent;
var parentHwnd = _parent?._hwnd ?? IntPtr.Zero;
if (parentHwnd == IntPtr.Zero && !_windowProperties.ShowInTaskbar)
@ -723,7 +734,7 @@ namespace Avalonia.Win32
_isUsingComposition ? (int)WindowStyles.WS_EX_NOREDIRECTIONBITMAP : 0,
atom,
null,
(int)WindowStyles.WS_OVERLAPPEDWINDOW | (int) WindowStyles.WS_CLIPCHILDREN,
(int)WindowStyles.WS_OVERLAPPEDWINDOW | (int)WindowStyles.WS_CLIPCHILDREN,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
@ -779,7 +790,7 @@ namespace Avalonia.Win32
}
if (ShCoreAvailable && Win32Platform.WindowsVersion > PlatformConstants.Windows8)
{
{
var monitor = MonitorFromWindow(
_hwnd,
MONITOR.MONITOR_DEFAULTTONEAREST);
@ -862,14 +873,14 @@ namespace Avalonia.Win32
}
TaskBarList.MarkFullscreen(_hwnd, fullscreen);
ExtendClientArea();
}
private MARGINS UpdateExtendMargins()
{
RECT borderThickness = new RECT();
RECT borderCaptionThickness = new RECT();
RECT borderCaptionThickness = new RECT();
AdjustWindowRectEx(ref borderCaptionThickness, (uint)(GetStyle()), false, 0);
AdjustWindowRectEx(ref borderThickness, (uint)(GetStyle() & ~WindowStyles.WS_CAPTION), false, 0);
@ -892,7 +903,7 @@ namespace Avalonia.Win32
if (_extendTitleBarHint != -1)
{
borderCaptionThickness.top = (int)(_extendTitleBarHint * RenderScaling);
borderCaptionThickness.top = (int)(_extendTitleBarHint * RenderScaling);
}
margins.cyTopHeight = _extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.SystemChrome) && !_extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.PreferSystemChrome) ? borderCaptionThickness.top : 1;
@ -917,7 +928,7 @@ namespace Avalonia.Win32
{
return;
}
if (DwmIsCompositionEnabled(out bool compositionEnabled) < 0 || !compositionEnabled)
{
_isClientAreaExtended = false;
@ -945,11 +956,11 @@ namespace Avalonia.Win32
_offScreenMargin = new Thickness();
_extendedMargins = new Thickness();
Resize(new Size(rcWindow.Width/ RenderScaling, rcWindow.Height / RenderScaling), PlatformResizeReason.Layout);
Resize(new Size(rcWindow.Width / RenderScaling, rcWindow.Height / RenderScaling), PlatformResizeReason.Layout);
}
if(!_isClientAreaExtended || (_extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.SystemChrome) &&
if (!_isClientAreaExtended || (_extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.SystemChrome) &&
!_extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.PreferSystemChrome)))
{
EnableCloseButton(_hwnd);
@ -965,12 +976,12 @@ namespace Avalonia.Win32
private void ShowWindow(WindowState state, bool activate)
{
_shown = true;
if (_isClientAreaExtended)
{
ExtendClientArea();
}
ShowWindowCommand? command;
var newWindowProperties = _windowProperties;
@ -988,7 +999,7 @@ namespace Avalonia.Win32
case WindowState.Normal:
newWindowProperties.IsFullScreen = false;
command = IsWindowVisible(_hwnd) ? ShowWindowCommand.Restore :
command = IsWindowVisible(_hwnd) ? ShowWindowCommand.Restore :
activate ? ShowWindowCommand.Normal : ShowWindowCommand.ShowNoActivate;
break;
@ -1019,7 +1030,7 @@ namespace Avalonia.Win32
SetForegroundWindow(_hwnd);
}
}
private void BeforeCloseCleanup(bool isDisposing)
{
// Based on https://github.com/dotnet/wpf/blob/master/src/Microsoft.DotNet.Wpf/src/PresentationFramework/System/Windows/Window.cs#L4270-L4337
@ -1037,7 +1048,7 @@ namespace Avalonia.Win32
// Our window closed callback will set enabled state to a correct value after child window gets destroyed.
_parent.SetEnabled(true);
}
// We also need to activate our parent window since again OS might try to activate a window behind if it is not set.
if (wasActive)
{
@ -1064,7 +1075,7 @@ namespace Avalonia.Win32
SetWindowPos(_hwnd, WindowPosZOrder.HWND_NOTOPMOST, x, y, cx, cy, SetWindowPosFlags.SWP_SHOWWINDOW);
}
}
}
}
private WindowStyles GetWindowStateStyles()
{
@ -1241,7 +1252,7 @@ namespace Avalonia.Win32
SetWindowPosFlags.SWP_NOZORDER | SetWindowPosFlags.SWP_NOACTIVATE |
SetWindowPosFlags.SWP_FRAMECHANGED);
}
}
}
}
private const int MF_BYCOMMAND = 0x0;
@ -1291,9 +1302,9 @@ namespace Avalonia.Win32
public void SetExtendClientAreaToDecorationsHint(bool hint)
{
_isClientAreaExtended = hint;
ExtendClientArea();
}
ExtendClientArea();
}
public void SetExtendClientAreaChromeHints(ExtendClientAreaChromeHints hints)
{
@ -1301,7 +1312,7 @@ namespace Avalonia.Win32
ExtendClientArea();
}
/// <inheritdoc/>
public void SetExtendClientAreaTitleBarHeightHint(double titleBarHeight)
{
@ -1315,7 +1326,7 @@ namespace Avalonia.Win32
/// <inheritdoc/>
public Action<bool> ExtendClientAreaToDecorationsChanged { get; set; }
/// <inheritdoc/>
public bool NeedsManagedDecorations => _isClientAreaExtended && _extendChromeHints.HasAllFlags(ExtendClientAreaChromeHints.PreferSystemChrome);
@ -1354,7 +1365,7 @@ namespace Avalonia.Win32
{
private readonly WindowImpl _owner;
private readonly PlatformResizeReason _restore;
public ResizeReasonScope(WindowImpl owner, PlatformResizeReason restore)
{
_owner = owner;

140
src/iOS/Avalonia.iOS/AvaloniaView.Text.cs

@ -1,32 +1,146 @@
using Avalonia.Input;
using Avalonia.Input.Raw;
using Foundation;
using ObjCRuntime;
using Avalonia.Input.TextInput;
using Avalonia.Input;
using Avalonia.Input.Raw;
using UIKit;
namespace Avalonia.iOS
namespace Avalonia.iOS;
#nullable enable
[Adopts("UITextInputTraits")]
[Adopts("UIKeyInput")]
public partial class AvaloniaView : ITextInputMethodImpl
{
[Adopts("UIKeyInput")]
public partial class AvaloniaView
private ITextInputMethodClient? _currentClient;
public override bool CanResignFirstResponder => true;
public override bool CanBecomeFirstResponder => true;
[Export("hasText")]
public bool HasText
{
public override bool CanBecomeFirstResponder => true;
get
{
if (_currentClient is { } && _currentClient.SupportsSurroundingText &&
_currentClient.SurroundingText.Text.Length > 0)
{
return true;
}
return false;
}
}
[Export("keyboardType")] public UIKeyboardType KeyboardType { get; private set; } = UIKeyboardType.Default;
[Export("hasText")] public bool HasText => false;
[Export("isSecureTextEntry")] public bool IsSecureEntry { get; private set; }
[Export("insertText:")]
public void InsertText(string text) =>
[Export("insertText:")]
public void InsertText(string text)
{
if (KeyboardDevice.Instance is { })
{
_topLevelImpl.Input?.Invoke(new RawTextInputEventArgs(KeyboardDevice.Instance,
0, InputRoot, text));
}
}
[Export("deleteBackward")]
public void DeleteBackward()
[Export("deleteBackward")]
public void DeleteBackward()
{
if (KeyboardDevice.Instance is { })
{
// TODO: pass this through IME infrastructure instead of emulating a backspace press
_topLevelImpl.Input?.Invoke(new RawKeyEventArgs(KeyboardDevice.Instance,
0, InputRoot, RawKeyEventType.KeyDown, Key.Back, RawInputModifiers.None));
_topLevelImpl.Input?.Invoke(new RawKeyEventArgs(KeyboardDevice.Instance,
0, InputRoot, RawKeyEventType.KeyUp, Key.Back, RawInputModifiers.None));
}
}
}
void ITextInputMethodImpl.SetClient(ITextInputMethodClient? client)
{
_currentClient = client;
if (client is { })
{
BecomeFirstResponder();
}
else
{
ResignFirstResponder();
}
}
void ITextInputMethodImpl.SetCursorRect(Rect rect)
{
}
void ITextInputMethodImpl.SetOptions(TextInputOptions options)
{
IsSecureEntry = false;
switch (options.ContentType)
{
case TextInputContentType.Normal:
KeyboardType = UIKeyboardType.Default;
break;
case TextInputContentType.Alpha:
KeyboardType = UIKeyboardType.AsciiCapable;
break;
case TextInputContentType.Digits:
KeyboardType = UIKeyboardType.PhonePad;
break;
case TextInputContentType.Pin:
KeyboardType = UIKeyboardType.NumberPad;
IsSecureEntry = true;
break;
case TextInputContentType.Number:
KeyboardType = UIKeyboardType.PhonePad;
break;
case TextInputContentType.Email:
KeyboardType = UIKeyboardType.EmailAddress;
break;
case TextInputContentType.Url:
KeyboardType = UIKeyboardType.Url;
break;
case TextInputContentType.Name:
KeyboardType = UIKeyboardType.NamePhonePad;
break;
case TextInputContentType.Password:
KeyboardType = UIKeyboardType.Default;
IsSecureEntry = true;
break;
case TextInputContentType.Social:
KeyboardType = UIKeyboardType.Twitter;
break;
case TextInputContentType.Search:
KeyboardType = UIKeyboardType.WebSearch;
break;
}
if (options.IsSensitive)
{
IsSecureEntry = true;
}
}
void ITextInputMethodImpl.Reset()
{
ResignFirstResponder();
}
}

7
src/iOS/Avalonia.iOS/AvaloniaView.cs

@ -2,12 +2,13 @@ using System;
using System.Collections.Generic;
using Avalonia.Controls;
using Avalonia.Controls.Embedding;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.Platform;
using Avalonia.Rendering;
using CoreAnimation;
using CoreGraphics;
using Foundation;
using ObjCRuntime;
using OpenGLES;
@ -42,7 +43,7 @@ namespace Avalonia.iOS
MultipleTouchEnabled = true;
}
internal class TopLevelImpl : ITopLevelImpl
internal class TopLevelImpl : ITopLevelImplWithTextInputMethod
{
private readonly AvaloniaView _view;
public AvaloniaView View => _view;
@ -109,6 +110,8 @@ namespace Avalonia.iOS
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels { get; } =
new AcrylicPlatformCompensationLevels();
public ITextInputMethodImpl? TextInputMethod => _view;
}
[Export("layerClass")]

7
src/iOS/Avalonia.iOS/Platform.cs

@ -44,7 +44,6 @@ namespace Avalonia.iOS
GlFeature ??= new EaglFeature();
Timer ??= new DisplayLinkTimer();
var keyboard = new KeyboardDevice();
var softKeyboard = new SoftKeyboardHelper();
AvaloniaLocator.CurrentMutable
.Bind<IPlatformOpenGlInterface>().ToConstant(GlFeature)
@ -58,12 +57,6 @@ namespace Avalonia.iOS
.Bind<IRenderTimer>().ToConstant(Timer)
.Bind<IPlatformThreadingInterface>().ToConstant(new PlatformThreadingInterface())
.Bind<IKeyboardDevice>().ToConstant(keyboard);
keyboard.PropertyChanged += (_, changed) =>
{
if (changed.PropertyName == nameof(KeyboardDevice.FocusedElement))
softKeyboard.UpdateKeyboard(keyboard.FocusedElement);
};
}

24
src/iOS/Avalonia.iOS/SoftKeyboardHelper.cs

@ -1,24 +0,0 @@
using Avalonia.Controls;
using Avalonia.Input;
namespace Avalonia.iOS
{
public class SoftKeyboardHelper
{
private AvaloniaView _oldView;
public void UpdateKeyboard(IInputElement focusedElement)
{
if (_oldView?.IsFirstResponder == true)
_oldView?.ResignFirstResponder();
_oldView = null;
//TODO: Raise a routed event to determine if any control wants to become the text input handler
if (focusedElement is TextBox)
{
var view = ((focusedElement.VisualRoot as TopLevel)?.PlatformImpl as AvaloniaView.TopLevelImpl)?.View;
view?.BecomeFirstResponder();
}
}
}
}

16
tests/Avalonia.Controls.UnitTests/ViewboxTests.cs

@ -18,7 +18,7 @@ namespace Avalonia.Controls.UnitTests
target.Arrange(new Rect(new Point(0, 0), target.DesiredSize));
Assert.Equal(new Size(200, 100), target.DesiredSize);
var scaleTransform = target.Child.RenderTransform as ScaleTransform;
var scaleTransform = target.InternalTransform as ScaleTransform;
Assert.NotNull(scaleTransform);
Assert.Equal(2.0, scaleTransform.ScaleX);
@ -36,7 +36,7 @@ namespace Avalonia.Controls.UnitTests
target.Arrange(new Rect(new Point(0, 0), target.DesiredSize));
Assert.Equal(new Size(100, 50), target.DesiredSize);
var scaleTransform = target.Child.RenderTransform as ScaleTransform;
var scaleTransform = target.InternalTransform as ScaleTransform;
Assert.NotNull(scaleTransform);
Assert.Equal(1.0, scaleTransform.ScaleX);
@ -54,7 +54,7 @@ namespace Avalonia.Controls.UnitTests
target.Arrange(new Rect(new Point(0, 0), target.DesiredSize));
Assert.Equal(new Size(200, 200), target.DesiredSize);
var scaleTransform = target.Child.RenderTransform as ScaleTransform;
var scaleTransform = target.InternalTransform as ScaleTransform;
Assert.NotNull(scaleTransform);
Assert.Equal(2.0, scaleTransform.ScaleX);
@ -72,7 +72,7 @@ namespace Avalonia.Controls.UnitTests
target.Arrange(new Rect(new Point(0, 0), target.DesiredSize));
Assert.Equal(new Size(200, 200), target.DesiredSize);
var scaleTransform = target.Child.RenderTransform as ScaleTransform;
var scaleTransform = target.InternalTransform as ScaleTransform;
Assert.NotNull(scaleTransform);
Assert.Equal(4.0, scaleTransform.ScaleX);
@ -90,7 +90,7 @@ namespace Avalonia.Controls.UnitTests
target.Arrange(new Rect(new Point(0, 0), target.DesiredSize));
Assert.Equal(new Size(400, 200), target.DesiredSize);
var scaleTransform = target.Child.RenderTransform as ScaleTransform;
var scaleTransform = target.InternalTransform as ScaleTransform;
Assert.NotNull(scaleTransform);
Assert.Equal(4.0, scaleTransform.ScaleX);
@ -108,7 +108,7 @@ namespace Avalonia.Controls.UnitTests
target.Arrange(new Rect(new Point(0, 0), target.DesiredSize));
Assert.Equal(new Size(200, 100), target.DesiredSize);
var scaleTransform = target.Child.RenderTransform as ScaleTransform;
var scaleTransform = target.InternalTransform as ScaleTransform;
Assert.NotNull(scaleTransform);
Assert.Equal(2.0, scaleTransform.ScaleX);
@ -136,7 +136,7 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Size(expectedWidth, expectedHeight), target.DesiredSize);
var scaleTransform = target.Child.RenderTransform as ScaleTransform;
var scaleTransform = target.InternalTransform as ScaleTransform;
Assert.NotNull(scaleTransform);
Assert.Equal(expectedScale, scaleTransform.ScaleX);
@ -164,7 +164,7 @@ namespace Avalonia.Controls.UnitTests
Assert.Equal(new Size(expectedWidth, expectedHeight), target.DesiredSize);
var scaleTransform = target.Child.RenderTransform as ScaleTransform;
var scaleTransform = target.InternalTransform as ScaleTransform;
Assert.NotNull(scaleTransform);
Assert.Equal(expectedScale, scaleTransform.ScaleX);

4
tests/Avalonia.IntegrationTests.Appium/readme.md

@ -11,12 +11,12 @@
- Run WinAppDriver (it gets installed to the start menu)
- Run the tests in this project
## MacOS
## macOS
### Prerequisites
- Install Appium: https://appium.io/
- Give [XCode helper the required permissions](https://apple.stackexchange.com/questions/334008)
- 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`

Loading…
Cancel
Save