Browse Source

Merge branch 'master' into fix-more-32bit-linux-session-manager-pinvokes

pull/7774/head
Jumar Macato 5 years ago
committed by GitHub
parent
commit
c3414c4c43
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 27
      Avalonia.sln
  2. 15
      samples/ControlCatalog.iOS.Legacy/AppDelegate.cs
  3. 117
      samples/ControlCatalog.iOS.Legacy/Assets.xcassets/AppIcon.appiconset/Contents.json
  4. 99
      samples/ControlCatalog.iOS.Legacy/ControlCatalog.iOS.Legacy.csproj
  5. 6
      samples/ControlCatalog.iOS.Legacy/Entitlements.plist
  6. 47
      samples/ControlCatalog.iOS.Legacy/Info.plist
  7. 15
      samples/ControlCatalog.iOS.Legacy/Main.cs
  8. 43
      samples/ControlCatalog.iOS.Legacy/Resources/LaunchScreen.xib
  9. 10
      samples/ControlCatalog.iOS/Info.plist
  10. 3
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  11. 6
      samples/ControlCatalog/Pages/TextBoxPage.xaml.cs
  12. 3
      samples/RenderDemo/MainWindow.xaml
  13. 71
      samples/RenderDemo/Pages/BrushesPage.axaml
  14. 18
      samples/RenderDemo/Pages/BrushesPage.axaml.cs
  15. 23
      src/Android/Avalonia.Android/AndroidInputMethod.cs
  16. 3
      src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj
  17. 1
      src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt
  18. 3
      src/Avalonia.Controls/ApiCompatBaseline.txt
  19. 5
      src/Avalonia.Controls/TextBox.cs
  20. 72
      src/Avalonia.Controls/Viewbox.cs
  21. 1
      src/Avalonia.DesignerSupport/ApiCompatBaseline.txt
  22. 6
      src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs
  23. 4
      src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs
  24. 2
      src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs
  25. 17
      src/Avalonia.Input/ApiCompatBaseline.txt
  26. 17
      src/Avalonia.Input/InputElement.cs
  27. 1
      src/Avalonia.Input/Properties/AssemblyInfo.cs
  28. 4
      src/Avalonia.Input/TextInput/ITextInputMethodImpl.cs
  29. 30
      src/Avalonia.Input/TextInput/InputMethodManager.cs
  30. 62
      src/Avalonia.Input/TextInput/TextInputContentType.cs
  31. 220
      src/Avalonia.Input/TextInput/TextInputOptions.cs
  32. 32
      src/Avalonia.Input/TextInput/TextInputOptionsQueryEventArgs.cs
  33. 1
      src/Avalonia.Interactivity/ApiCompatBaseline.txt
  34. 1
      src/Avalonia.Layout/ApiCompatBaseline.txt
  35. 2
      src/Avalonia.ReactiveUI/RoutedViewHost.cs
  36. 1
      src/Avalonia.Remote.Protocol/ApiCompatBaseline.txt
  37. 1
      src/Avalonia.Themes.Default/ApiCompatBaseline.txt
  38. 1
      src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt
  39. 6
      src/Avalonia.Visuals/Animation/Animators/GradientBrushAnimator.cs
  40. 10
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  41. 17
      src/Avalonia.Visuals/Media/Brush.cs
  42. 11
      src/Avalonia.Visuals/Media/Color.cs
  43. 478
      src/Avalonia.Visuals/Media/HsvColor.cs
  44. 5
      src/Avalonia.Visuals/Media/IBrush.cs
  45. 4
      src/Avalonia.Visuals/Media/Immutable/ImmutableConicGradientBrush.cs
  46. 10
      src/Avalonia.Visuals/Media/Immutable/ImmutableGradientBrush.cs
  47. 3
      src/Avalonia.Visuals/Media/Immutable/ImmutableImageBrush.cs
  48. 4
      src/Avalonia.Visuals/Media/Immutable/ImmutableLinearGradientBrush.cs
  49. 4
      src/Avalonia.Visuals/Media/Immutable/ImmutableRadialGradientBrush.cs
  50. 15
      src/Avalonia.Visuals/Media/Immutable/ImmutableSolidColorBrush.cs
  51. 9
      src/Avalonia.Visuals/Media/Immutable/ImmutableTileBrush.cs
  52. 21
      src/Avalonia.Visuals/Media/Immutable/ImmutableTransform.cs
  53. 3
      src/Avalonia.Visuals/Media/Immutable/ImmutableVisualBrush.cs
  54. 10
      src/Avalonia.Visuals/Media/Transform.cs
  55. 26
      src/Avalonia.Visuals/Media/TransformExtensions.cs
  56. 7
      src/Avalonia.X11/X11Window.Xim.cs
  57. 1
      src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt
  58. 1
      src/Markup/Avalonia.Markup/ApiCompatBaseline.txt
  59. 67
      src/Skia/Avalonia.Skia/DrawingContextImpl.cs
  60. 6
      src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs
  61. 8
      src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs
  62. 11
      src/Windows/Avalonia.Win32/Interop/UnmanagedMethods.cs
  63. 60
      src/Windows/Avalonia.Win32/WinRT/Composition/WinUICompositorConnection.cs
  64. 6
      src/Windows/Avalonia.Win32/WinRT/winrt.idl
  65. 83
      src/Windows/Avalonia.Win32/WindowImpl.cs
  66. 140
      src/iOS/Avalonia.iOS/AvaloniaView.Text.cs
  67. 7
      src/iOS/Avalonia.iOS/AvaloniaView.cs
  68. 7
      src/iOS/Avalonia.iOS/Platform.cs
  69. 24
      src/iOS/Avalonia.iOS/SoftKeyboardHelper.cs
  70. 16
      tests/Avalonia.Controls.UnitTests/ViewboxTests.cs

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}

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

3
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.
@ -68,4 +69,4 @@ 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: 69
Total Issues: 70

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

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

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)
{
}

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);

Loading…
Cancel
Save