Browse Source

Merge pull request #7781 from AvaloniaUI/ios-basic-ime

iOS Basic Text Input
pull/7787/head
Dan Walmsley 4 years ago
committed by GitHub
parent
commit
f2beebeb18
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. 23
      src/Android/Avalonia.Android/AndroidInputMethod.cs
  13. 1
      src/Avalonia.Controls.DataGrid/ApiCompatBaseline.txt
  14. 5
      src/Avalonia.Controls/TextBox.cs
  15. 1
      src/Avalonia.DesignerSupport/ApiCompatBaseline.txt
  16. 6
      src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs
  17. 4
      src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs
  18. 2
      src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs
  19. 17
      src/Avalonia.Input/ApiCompatBaseline.txt
  20. 17
      src/Avalonia.Input/InputElement.cs
  21. 1
      src/Avalonia.Input/Properties/AssemblyInfo.cs
  22. 4
      src/Avalonia.Input/TextInput/ITextInputMethodImpl.cs
  23. 30
      src/Avalonia.Input/TextInput/InputMethodManager.cs
  24. 62
      src/Avalonia.Input/TextInput/TextInputContentType.cs
  25. 220
      src/Avalonia.Input/TextInput/TextInputOptions.cs
  26. 32
      src/Avalonia.Input/TextInput/TextInputOptionsQueryEventArgs.cs
  27. 1
      src/Avalonia.Interactivity/ApiCompatBaseline.txt
  28. 1
      src/Avalonia.Layout/ApiCompatBaseline.txt
  29. 1
      src/Avalonia.Remote.Protocol/ApiCompatBaseline.txt
  30. 1
      src/Avalonia.Themes.Default/ApiCompatBaseline.txt
  31. 1
      src/Avalonia.Themes.Fluent/ApiCompatBaseline.txt
  32. 2
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  33. 7
      src/Avalonia.X11/X11Window.Xim.cs
  34. 1
      src/Markup/Avalonia.Markup.Xaml/ApiCompatBaseline.txt
  35. 1
      src/Markup/Avalonia.Markup/ApiCompatBaseline.txt
  36. 6
      src/Web/Avalonia.Web.Blazor/AvaloniaView.razor.cs
  37. 8
      src/Windows/Avalonia.Win32/Input/Imm32InputMethod.cs
  38. 140
      src/iOS/Avalonia.iOS/AvaloniaView.Text.cs
  39. 7
      src/iOS/Avalonia.iOS/AvaloniaView.cs
  40. 7
      src/iOS/Avalonia.iOS/Platform.cs
  41. 24
      src/iOS/Avalonia.iOS/SoftKeyboardHelper.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;
};
}
}
}

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)

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

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

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

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

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

2
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -153,4 +153,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: 154

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

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
}

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