Browse Source

Merge branch 'master' into BrushTransformProperty

Wiesław Šoltés 3 years ago
parent
commit
f72189fbd6
40 changed files with 488 additions and 531 deletions
  1. 0 27
      Avalonia.sln
  2. 0 15
      samples/ControlCatalog.iOS.Legacy/AppDelegate.cs
  3. 0 117
      samples/ControlCatalog.iOS.Legacy/Assets.xcassets/AppIcon.appiconset/Contents.json
  4. 0 99
      samples/ControlCatalog.iOS.Legacy/ControlCatalog.iOS.Legacy.csproj
  5. 0 6
      samples/ControlCatalog.iOS.Legacy/Entitlements.plist
  6. 0 47
      samples/ControlCatalog.iOS.Legacy/Info.plist
  7. 0 15
      samples/ControlCatalog.iOS.Legacy/Main.cs
  8. 0 43
      samples/ControlCatalog.iOS.Legacy/Resources/LaunchScreen.xib
  9. 5 5
      samples/ControlCatalog.iOS/Info.plist
  10. 2 1
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  11. 0 6
      samples/ControlCatalog/Pages/TextBoxPage.xaml.cs
  12. 5 18
      src/Android/Avalonia.Android/AndroidInputMethod.cs
  13. 1 0
      src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt
  14. 4 1
      src/Avalonia.Controls/TextBox.cs
  15. 1 0
      src/Avalonia.DesignerSupport/ApiCompatBaseline.txt
  16. 3 3
      src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs
  17. 2 2
      src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs
  18. 1 1
      src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs
  19. 16 1
      src/Avalonia.Input/ApiCompatBaseline.txt
  20. 0 17
      src/Avalonia.Input/InputElement.cs
  21. 1 0
      src/Avalonia.Input/Properties/AssemblyInfo.cs
  22. 2 2
      src/Avalonia.Input/TextInput/ITextInputMethodImpl.cs
  23. 19 11
      src/Avalonia.Input/TextInput/InputMethodManager.cs
  24. 56 6
      src/Avalonia.Input/TextInput/TextInputContentType.cs
  25. 220 0
      src/Avalonia.Input/TextInput/TextInputOptions.cs
  26. 0 32
      src/Avalonia.Input/TextInput/TextInputOptionsQueryEventArgs.cs
  27. 1 0
      src/Avalonia.Interactivity/ApiCompatBaseline.txt
  28. 1 0
      src/Avalonia.Layout/ApiCompatBaseline.txt
  29. 1 0
      src/Avalonia.Remote.Protocol/ApiCompatBaseline.txt
  30. 1 0
      src/Avalonia.Themes.Default/ApiCompatBaseline.txt
  31. 1 0
      src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt
  32. 3 4
      src/Avalonia.X11/X11Window.Xim.cs
  33. 1 0
      src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt
  34. 1 0
      src/Markup/Avalonia.Markup/ApiCompatBaseline.txt
  35. 4 2
      src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs
  36. 4 4
      src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs
  37. 127 13
      src/iOS/Avalonia.iOS/AvaloniaView.Text.cs
  38. 5 2
      src/iOS/Avalonia.iOS/AvaloniaView.cs
  39. 0 7
      src/iOS/Avalonia.iOS/Platform.cs
  40. 0 24
      src/iOS/Avalonia.iOS/SoftKeyboardHelper.cs

+ 0 - 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}

+ 0 - 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>
-    {
-        
-    }
-}

+ 0 - 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"
-  }
-}

+ 0 - 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>

+ 0 - 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>

+ 0 - 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>

+ 0 - 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");
-        }
-    }
-}

+ 0 - 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>

+ 5 - 5
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>

+ 2 - 1
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" />

+ 0 - 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;
-                };
         }
     }
 }

+ 5 - 18
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)

+ 1 - 0
src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt

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

+ 4 - 1
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;
+                }
             });
         }
 

+ 1 - 0
src/Avalonia.DesignerSupport/ApiCompatBaseline.txt

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

+ 3 - 3
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()
         {

+ 2 - 2
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;

+ 1 - 1
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 
         }

+ 16 - 1
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

+ 0 - 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 - 0
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")]

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

+ 19 - 11
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();

+ 56 - 6
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 - 0
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; }
+}

+ 0 - 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 - 0
src/Avalonia.Interactivity/ApiCompatBaseline.txt

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

+ 1 - 0
src/Avalonia.Layout/ApiCompatBaseline.txt

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

+ 1 - 0
src/Avalonia.Remote.Protocol/ApiCompatBaseline.txt

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

+ 1 - 0
src/Avalonia.Themes.Default/ApiCompatBaseline.txt

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

+ 1 - 0
src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt

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

+ 3 - 4
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 - 0
src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt

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

+ 1 - 0
src/Markup/Avalonia.Markup/ApiCompatBaseline.txt

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

+ 4 - 2
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)
         {
         }
 

+ 4 - 4
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
         }

+ 127 - 13
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();
+    }
+}

+ 5 - 2
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")]

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

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