Browse Source

Merge branch 'master' into fixes/5027-avaloniaobject-batching

pull/5070/head
Steven Kirk 5 years ago
committed by GitHub
parent
commit
b319a08cf0
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 31
      .github/ISSUE_TEMPLATE/bug_report.md
  2. 20
      .github/ISSUE_TEMPLATE/feature_request.md
  3. 3
      Avalonia.sln.DotSettings
  4. 6
      azure-pipelines.yml
  5. 2
      build/AndroidWorkarounds.props
  6. 2
      build/ApiDiff.props
  7. 4
      build/HarfBuzzSharp.props
  8. 4
      build/SharedVersion.props
  9. 23
      build/SourceLink.props
  10. 12
      native/Avalonia.Native/src/OSX/rendertarget.mm
  11. 12
      packages/Avalonia/Avalonia.csproj
  12. 11
      readme.md
  13. 14
      samples/ControlCatalog.Android/ControlCatalog.Android.csproj
  14. 21
      samples/ControlCatalog.Android/MainActivity.cs
  15. 2
      samples/ControlCatalog.Android/Properties/AndroidManifest.xml
  16. 59
      samples/ControlCatalog.Android/Resources/Resource.Designer.cs
  17. BIN
      samples/ControlCatalog.Android/Resources/drawable/Icon.png
  18. 13
      samples/ControlCatalog.Android/Resources/drawable/splash_screen.xml
  19. 13
      samples/ControlCatalog.Android/Resources/layout/Main.axml
  20. 5
      samples/ControlCatalog.Android/Resources/values/Strings.xml
  21. 4
      samples/ControlCatalog.Android/Resources/values/colors.xml
  22. 17
      samples/ControlCatalog.Android/Resources/values/styles.xml
  23. 32
      samples/ControlCatalog.Android/SplashActivity.cs
  24. 3
      samples/ControlCatalog.NetCore/Program.cs
  25. BIN
      samples/ControlCatalog/Assets/Fonts/WenQuanYiMicroHei-01.ttf
  26. 4
      samples/ControlCatalog/Pages/DataGridPage.xaml
  27. 14
      samples/ControlCatalog/Pages/SliderPage.xaml
  28. 3
      samples/ControlCatalog/Pages/TextBoxPage.xaml
  29. 27
      samples/ControlCatalog/Pages/ToolTipPage.xaml
  30. 19
      src/Android/Avalonia.Android/AndroidPlatform.cs
  31. 32
      src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs
  32. 23
      src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs
  33. 4
      src/Android/Avalonia.Android/Platform/ClipboardImpl.cs
  34. 17
      src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs
  35. 41
      src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs
  36. 11
      src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs
  37. 60
      src/Android/Avalonia.Android/Resources/Resource.Designer.cs
  38. 5
      src/Android/Avalonia.Android/SystemDialogImpl.cs
  39. 4
      src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj
  40. 4
      src/Android/Avalonia.AndroidTestApplication/MainActivity.cs
  41. 2
      src/Android/Avalonia.AndroidTestApplication/Properties/AndroidManifest.xml
  42. 17
      src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs
  43. 3
      src/Avalonia.Base/Avalonia.Base.csproj
  44. 25
      src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs
  45. 8
      src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs
  46. 5
      src/Avalonia.Base/Data/Core/SettableNode.cs
  47. 125
      src/Avalonia.Controls.DataGrid/DataGrid.cs
  48. 35
      src/Avalonia.Controls.DataGrid/Primitives/DataGridRowsPresenter.cs
  49. 25
      src/Avalonia.Controls.DataGrid/Utils/ValidationUtil.cs
  50. 32
      src/Avalonia.Controls/Calendar/CalendarDatePicker.cs
  51. 2
      src/Avalonia.Controls/ContextMenu.cs
  52. 33
      src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs
  53. 8
      src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs
  54. 11
      src/Avalonia.Controls/Platform/ITopLevelImplWithTextInputMethod.cs
  55. 25
      src/Avalonia.Controls/Presenters/TextPresenter.cs
  56. 6
      src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs
  57. 1
      src/Avalonia.Controls/Primitives/VisualLayerManager.cs
  58. 7
      src/Avalonia.Controls/TextBox.cs
  59. 41
      src/Avalonia.Controls/TextBoxTextInputMethodClient.cs
  60. 40
      src/Avalonia.Controls/ToolTip.cs
  61. 10
      src/Avalonia.Controls/ToolTipService.cs
  62. 6
      src/Avalonia.Controls/TopLevel.cs
  63. 8
      src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml
  64. 9
      src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs
  65. 110
      src/Avalonia.FreeDesktop/DBusCallQueue.cs
  66. 11
      src/Avalonia.FreeDesktop/DBusHelper.cs
  67. 288
      src/Avalonia.FreeDesktop/DBusIme/DBusTextInputMethodBase.cs
  68. 69
      src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxDBus.cs
  69. 67
      src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxEnums.cs
  70. 51
      src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxICWrapper.cs
  71. 149
      src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxX11TextInputMethod.cs
  72. 52
      src/Avalonia.FreeDesktop/DBusIme/IBus/IBusDBus.cs
  73. 45
      src/Avalonia.FreeDesktop/DBusIme/IBus/IBusEnums.cs
  74. 105
      src/Avalonia.FreeDesktop/DBusIme/IBus/IBusX11TextInputMethod.cs
  75. 53
      src/Avalonia.FreeDesktop/DBusIme/X11DBusImeHelper.cs
  76. 31
      src/Avalonia.FreeDesktop/IX11InputMethod.cs
  77. 35
      src/Avalonia.Input/InputElement.cs
  78. 6
      src/Avalonia.Input/KeyboardDevice.cs
  79. 60
      src/Avalonia.Input/TextInput/ITextInputMethodClient.cs
  80. 15
      src/Avalonia.Input/TextInput/ITextInputMethodImpl.cs
  81. 101
      src/Avalonia.Input/TextInput/InputMethodManager.cs
  82. 12
      src/Avalonia.Input/TextInput/TextInputContentType.cs
  83. 12
      src/Avalonia.Input/TextInput/TextInputMethodClientRequestedEventArgs.cs
  84. 32
      src/Avalonia.Input/TextInput/TextInputOptionsQueryEventArgs.cs
  85. 109
      src/Avalonia.Input/TextInput/TransformTrackingHelper.cs
  86. 87
      src/Avalonia.Layout/ElementManager.cs
  87. 4
      src/Avalonia.OpenGL/Egl/EglInterface.cs
  88. 2
      src/Avalonia.OpenGL/GlInterface.cs
  89. 2
      src/Avalonia.Themes.Default/CalendarDatePicker.xaml
  90. 2
      src/Avalonia.Themes.Default/CaptionButtons.xaml
  91. 4
      src/Avalonia.Themes.Default/NumericUpDown.xaml
  92. 3
      src/Avalonia.Themes.Fluent/Controls/Button.xaml
  93. 10
      src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml
  94. 2
      src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml
  95. 4
      src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml
  96. 2
      src/Avalonia.Themes.Fluent/Controls/Expander.xaml
  97. 6
      src/Avalonia.Themes.Fluent/Controls/FocusAdorner.xaml
  98. 2
      src/Avalonia.Themes.Fluent/Controls/ListBoxItem.xaml
  99. 2
      src/Avalonia.Themes.Fluent/Controls/Menu.xaml
  100. 6
      src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml

31
.github/ISSUE_TEMPLATE/bug_report.md

@ -0,0 +1,31 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: ''
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. Windows, Mac, Linux (State distribution)]
- Version [e.g. 0.10.0-rc1 or 0.9.12]
**Additional context**
Add any other context about the problem here.

20
.github/ISSUE_TEMPLATE/feature_request.md

@ -0,0 +1,20 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: ''
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

3
Avalonia.sln.DotSettings

@ -38,4 +38,5 @@
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypeParameters/@EntryIndexedValue">&lt;Policy Inspect="False" Prefix="T" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=TypesAndNamespaces/@EntryIndexedValue">&lt;Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:Boolean x:Key="/Default/Environment/SettingsMigration/IsMigratorApplied/=JetBrains_002EReSharper_002EFeature_002EServices_002EDaemon_002ESettings_002EMigration_002ESwaWarningsModeSettingsMigrate/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Avalonia/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Avalonia/@EntryIndexedValue">True</s:Boolean>
<s:Boolean x:Key="/Default/UserDictionary/Words/=Fcitx/@EntryIndexedValue">True</s:Boolean></wpf:ResourceDictionary>

6
azure-pipelines.yml

@ -27,7 +27,7 @@ jobs:
variables:
SolutionDir: '$(Build.SourcesDirectory)'
pool:
vmImage: 'macOS-10.14'
vmImage: 'macOS-10.15'
steps:
- task: UseDotNet@2
displayName: 'Use .NET Core SDK 3.1.401'
@ -51,10 +51,10 @@ jobs:
inputs:
actions: 'build'
scheme: ''
sdk: 'macosx10.14'
sdk: 'macosx11.1'
configuration: 'Release'
xcWorkspacePath: '**/*.xcodeproj/project.xcworkspace'
xcodeVersion: '10' # Options: 8, 9, default, specifyPath
xcodeVersion: '12' # Options: 8, 9, default, specifyPath
args: '-derivedDataPath ./'
- task: CmdLine@2

2
build/AndroidWorkarounds.props

@ -2,7 +2,7 @@
<ItemGroup Condition="'$(AndroidApplication)' == 'true'">
<!-- WORKAROUND: The packages below are transitively referenced by System.Memory,
but Xamarin.Android applications need the newest versions directly referenced for the linker. -->
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.5.1" />
<PackageReference Include="System.Runtime.CompilerServices.Unsafe" Version="4.6.0" />
<PackageReference Include="System.Buffers" Version="4.5.0" />
</ItemGroup>
<Target Name="_RemoveNonExistingResgenFile" BeforeTargets="CoreCompile" Condition="'$(_SdkSetAndroidResgenFile)' == 'true' And '$(AndroidResgenFile)' != '' And !Exists('$(AndroidResgenFile)')">

2
build/ApiDiff.props

@ -1,6 +1,6 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<ApiContractPackageVersion>0.10.0-rc1</ApiContractPackageVersion>
<ApiContractPackageVersion>0.10.0</ApiContractPackageVersion>
<NugetPackageName Condition="'$(PackageId)' != ''">$(PackageId)</NugetPackageName>
<NugetPackageName Condition="'$(PackageId)' == ''">Avalonia</NugetPackageName>
</PropertyGroup>

4
build/HarfBuzzSharp.props

@ -1,6 +1,6 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ItemGroup>
<PackageReference Include="HarfBuzzSharp" Version="2.6.1.6" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.6.1.6" />
<PackageReference Include="HarfBuzzSharp" Version="2.6.1.7" />
<PackageReference Condition="'$(IncludeLinuxSkia)' == 'true'" Include="HarfBuzzSharp.NativeAssets.Linux" Version="2.6.1.7" />
</ItemGroup>
</Project>

4
build/SharedVersion.props

@ -3,7 +3,7 @@
<PropertyGroup>
<Product>Avalonia</Product>
<Version>0.10.999</Version>
<Copyright>Copyright 2020 &#169; The AvaloniaUI Project</Copyright>
<Copyright>Copyright 2021 &#169; The AvaloniaUI Project</Copyright>
<PackageProjectUrl>https://avaloniaui.net</PackageProjectUrl>
<RepositoryUrl>https://github.com/AvaloniaUI/Avalonia/</RepositoryUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
@ -21,6 +21,6 @@
</PropertyGroup>
<ItemGroup Label="PackageIcon">
<None Include="$(MSBuildThisFileDirectory)/Assets/Icon.png" Pack="true" PackagePath=""/>
<None Include="$(MSBuildThisFileDirectory)/Assets/Icon.png" Pack="true" Visible="false" PackagePath=""/>
</ItemGroup>
</Project>

23
build/SourceLink.props

@ -1,5 +1,26 @@
<Project>
<PropertyGroup>
<PublishRepositoryUrl>true</PublishRepositoryUrl>
<IncludeSymbols>false</IncludeSymbols>
<EmbedUntrackedSources>true</EmbedUntrackedSources>
<DebugType>embedded</DebugType>
<AllowedOutputExtensionsInPackageBuildOutputFolder>$(AllowedOutputExtensionsInPackageBuildOutputFolder);.pdb</AllowedOutputExtensionsInPackageBuildOutputFolder>
</PropertyGroup>
<PropertyGroup Condition="'$(TF_BUILD)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="SourceLink.Create.CommandLine" Version="2.8.3" PrivateAssets="All" />
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="1.0.0" PrivateAssets="All"/>
</ItemGroup>
<!-- Workaround for https://github.com/dotnet/sdk/issues/11105 -->
<ItemGroup>
<SourceRoot Include="$(NuGetPackageRoot)" Condition="'$(NuGetPackageRoot)' != ''" />
</ItemGroup>
</Project>

12
native/Avalonia.Native/src/OSX/rendertarget.mm

@ -111,7 +111,11 @@
if(_renderbuffer != 0)
glDeleteRenderbuffers(1, &_renderbuffer);
}
CFRelease(surface);
if(surface != nullptr)
{
CFRelease(surface);
}
}
@end
@ -145,6 +149,12 @@ static IAvnGlSurfaceRenderTarget* CreateGlRenderTarget(IOSurfaceRenderTarget* ta
}
- (void)resize:(AvnPixelSize)size withScale: (float) scale{
if(size.Height <= 0)
size.Height = 1;
if(size.Width <= 0)
size.Width = 1;
@synchronized (lock) {
if(surface == nil
|| surface->size.Width != size.Width

12
packages/Avalonia/Avalonia.csproj

@ -6,7 +6,9 @@
<ItemGroup>
<ProjectReference Include="../../src/Avalonia.Remote.Protocol/Avalonia.Remote.Protocol.csproj" />
<ProjectReference Include="../../src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj" />
<ProjectReference Include="../../src/Avalonia.Build.Tasks/Avalonia.Build.Tasks.csproj" >
<PrivateAssets>all</PrivateAssets>
</ProjectReference>
</ItemGroup>
<PropertyGroup>
@ -29,21 +31,23 @@
</_PackageFiles>
</ItemGroup>
</Target>
<ItemGroup>
<Content Include="*.props">
<Pack>true</Pack>
<PackagePath>build\</PackagePath>
<PackagePath>build\;buildTransitive\</PackagePath>
</Content>
<Content Include="*.targets">
<Pack>true</Pack>
<PackagePath>build\</PackagePath>
<PackagePath>build\;buildTransitive\</PackagePath>
</Content>
<Content Include="AvaloniaItemSchema.xaml">
<Pack>true</Pack>
<PackagePath>build\</PackagePath>
<PackagePath>build\;buildTransitive\</PackagePath>
</Content>
</ItemGroup>
<Import Project="..\..\build\SharedVersion.props" />
<Import Project="..\..\build\NetFX.props" />
<Import Project="..\..\build\CoreLibraries.props" />
<Import Project="..\..\build\SourceLink.props" Condition="'$(DisableSourceLink)' == ''" />
</Project>

11
readme.md

@ -10,7 +10,7 @@ Avalonia is a cross-platform XAML-based UI framework providing a flexible stylin
<img src="https://user-images.githubusercontent.com/6759207/84751662-7c79da00-afc5-11ea-8780-dda28db70b76.png" width="700" />
> **Note:** The UI theme you see in the picture above is still work-in-progress and will be available in the upcoming Avalonia 0.10.0 release. However, you can connect to our nightly build feed and install latest pre-release versions of Avalonia NuGet packages, if you are willing to help out with the development and testing. See [Using nightly build feed](https://github.com/AvaloniaUI/Avalonia/wiki/Using-nightly-build-feed) for more info.
([Xaml Control Gallery](https://github.com/AvaloniaUI/xamlcontrolsgallery))
To see the status of some of our features, please see our [Roadmap](https://github.com/AvaloniaUI/Avalonia/issues/2239). You can also see what [breaking changes](https://github.com/AvaloniaUI/Avalonia/issues/3538) we have planned and what our [past breaking changes](https://github.com/AvaloniaUI/Avalonia/wiki/Breaking-Changes) have been. [Awesome Avalonia](https://github.com/AvaloniaCommunity/awesome-avalonia) is community-curated list of awesome Avalonia UI tools, libraries, projects and resources. Go and see what people are building with Avalonia!
@ -31,15 +31,22 @@ Install-Package Avalonia.Desktop
Examples of UIs built with Avalonia
![image](https://user-images.githubusercontent.com/4672627/84707589-5b69a880-af35-11ea-87a6-7ad57a31d314.png)
([Synfonia](https://github.com/jmacato/Synfonia))
![image](https://user-images.githubusercontent.com/4672627/85069644-d8419000-b18a-11ea-8732-be9055bb61fd.PNG)
([Xaml Control Gallery](https://github.com/AvaloniaUI/xamlcontrolsgallery))
![image](https://user-images.githubusercontent.com/4672627/85069659-dc6dad80-b18a-11ea-8375-39ef95315b5c.PNG)
([Xaml Control Gallery](https://github.com/AvaloniaUI/xamlcontrolsgallery))
![image](https://user-images.githubusercontent.com/4672627/84708947-c3b98980-af37-11ea-8c9d-503334615bbf.png)
([Xaml Control Gallery](https://github.com/AvaloniaUI/xamlcontrolsgallery))
## JetBrains Rider
If you need to develop Avalonia app with JetBrains Rider you can use latest Rider [preview builds](https://www.jetbrains.com/rider/nextversion/).
[JetBrains Rider](https://www.jetbrains.com/rider/whatsnew/?mkt_tok=eyJpIjoiTURBNU1HSmhNV0kwTUdFMiIsInQiOiJtNnU2VEc1TlNLa1ZRVkROYmdZYVpYREJsaU1qdUhmS3dxSzRHczdYWHl0RVlTNDMwSFwvNUs3VENTNVM0bVcyNFdaRmVYZzVWTTF1N3VrQWNGTkJreEhlam1hMlB4UVVWcHBGM1dNOUxoXC95YnRQdGgyUXl1YmZCM3h3d3BVWWdBIn0%3D#avalonia-support) now has official support for Avalonia.
Code completion, inspections and refactorings are supported out of the box, for XAML previewer add `https://plugins.jetbrains.com/plugins/dev/14839` to plugin repositories and install [AvaloniaRider](https://github.com/ForNeVeR/AvaloniaRider) plugin.
## Bleeding Edge Builds

14
samples/ControlCatalog.Android/ControlCatalog.Android.csproj

@ -16,7 +16,7 @@
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidUseLatestPlatformSdk>False</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v9.0</TargetFrameworkVersion>
<TargetFrameworkVersion>v10.0</TargetFrameworkVersion>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
@ -71,21 +71,23 @@
<Compile Include="MainActivity.cs" />
<Compile Include="Resources\Resource.Designer.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="SplashActivity.cs" />
</ItemGroup>
<ItemGroup>
<None Include="Resources\AboutResources.txt" />
<None Include="Assets\AboutAssets.txt" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\layout\Main.axml">
<SubType>Designer</SubType>
</AndroidResource>
<AndroidResource Include="Resources\drawable\splash_screen.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\values\Strings.xml" />
<AndroidResource Include="Resources\values\colors.xml" />
<AndroidResource Include="Resources\values\styles.xml" />
</ItemGroup>
<ItemGroup>
<AndroidResource Include="Resources\drawable\Icon.png" />
<AndroidResource Include="..\..\build\Assets\Icon.png">
<Link>Resources\drawable\Icon.png</Link>
</AndroidResource>
</ItemGroup>
<ItemGroup>
<None Include="Properties\AndroidManifest.xml" />

21
samples/ControlCatalog.Android/MainActivity.cs

@ -1,31 +1,18 @@
using System;
using Android.App;
using Android.App;
using Android.OS;
using Android.Content.PM;
using Avalonia.Android;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Styling;
using Avalonia.Themes.Default;
using Avalonia;
namespace ControlCatalog.Android
{
[Activity(Label = "ControlCatalog.Android", MainLauncher = true, Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleInstance)]
[Activity(Label = "ControlCatalog.Android", Theme = "@style/MyTheme.NoActionBar", Icon = "@drawable/icon", LaunchMode = LaunchMode.SingleInstance)]
public class MainActivity : AvaloniaActivity
{
protected override void OnCreate(Bundle savedInstanceState)
{
if (Avalonia.Application.Current == null)
{
AppBuilder.Configure<App>()
.UseAndroid()
.SetupWithoutStarting();
Content = new MainView();
}
base.OnCreate(savedInstanceState);
Content = new MainView();
}
}
}

2
samples/ControlCatalog.Android/Properties/AndroidManifest.xml

@ -1,5 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="ControlCatalog.Android" android:versionCode="1" android:versionName="1.0" android:installLocation="auto">
<uses-sdk />
<uses-sdk android:targetSdkVersion="29" />
<application android:label="ControlCatalog.Android"></application>
</manifest>

59
samples/ControlCatalog.Android/Resources/Resource.Designer.cs

@ -2,7 +2,6 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@ -15,7 +14,7 @@ namespace ControlCatalog.Android
{
[System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
public partial class Resource
{
@ -26,8 +25,6 @@ namespace ControlCatalog.Android
public static void UpdateIdValues()
{
global::Avalonia.Android.Resource.String.ApplicationName = global::ControlCatalog.Android.Resource.String.ApplicationName;
global::Avalonia.Android.Resource.String.Hello = global::ControlCatalog.Android.Resource.String.Hello;
}
public partial class Attribute
@ -43,69 +40,59 @@ namespace ControlCatalog.Android
}
}
public partial class Drawable
public partial class Color
{
// aapt resource value: 0x7f020000
public const int Icon = 2130837504;
// aapt resource value: 0x7F010000
public const int splash_background = 2130771968;
static Drawable()
static Color()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private Drawable()
private Color()
{
}
}
public partial class Id
public partial class Drawable
{
// aapt resource value: 0x7f050000
public const int MyButton = 2131034112;
static Id()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private Id()
{
}
}
public partial class Layout
{
// aapt resource value: 0x7F020000
public const int Icon = 2130837504;
// aapt resource value: 0x7f030000
public const int Main = 2130903040;
// aapt resource value: 0x7F020001
public const int splash_screen = 2130837505;
static Layout()
static Drawable()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private Layout()
private Drawable()
{
}
}
public partial class String
public partial class Style
{
// aapt resource value: 0x7f040001
public const int ApplicationName = 2130968577;
// aapt resource value: 0x7F030000
public const int MyTheme = 2130903040;
// aapt resource value: 0x7F030001
public const int MyTheme_NoActionBar = 2130903041;
// aapt resource value: 0x7f040000
public const int Hello = 2130968576;
// aapt resource value: 0x7F030002
public const int MyTheme_Splash = 2130903042;
static String()
static Style()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private String()
private Style()
{
}
}

BIN
samples/ControlCatalog.Android/Resources/drawable/Icon.png

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.0 KiB

13
samples/ControlCatalog.Android/Resources/drawable/splash_screen.xml

@ -0,0 +1,13 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item>
<color android:color="@color/splash_background"/>
</item>
<item android:drawable="@drawable/icon"
android:width="120dp"
android:height="120dp"
android:gravity="center" />
</layer-list>

13
samples/ControlCatalog.Android/Resources/layout/Main.axml

@ -1,13 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
<Button
android:id="@+id/MyButton"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/Hello"
/>
</LinearLayout>

5
samples/ControlCatalog.Android/Resources/values/Strings.xml

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="Hello">Hello World, Click Me!</string>
<string name="ApplicationName">ControlCatalog.Android</string>
</resources>

4
samples/ControlCatalog.Android/Resources/values/colors.xml

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="splash_background">#FFFFFF</color>
</resources>

17
samples/ControlCatalog.Android/Resources/values/styles.xml

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8" ?>
<resources>
<style name="MyTheme">
</style>
<style name="MyTheme.NoActionBar">
<item name="android:windowActionBar">false</item>
<item name="android:windowNoTitle">true</item>
</style>
<style name="MyTheme.Splash" parent ="MyTheme.NoActionBar">
<item name="android:windowBackground">@drawable/splash_screen</item>
<item name="android:windowContentOverlay">@null</item>
</style>
</resources>

32
samples/ControlCatalog.Android/SplashActivity.cs

@ -0,0 +1,32 @@
using Android.App;
using Android.Content;
using Android.OS;
using Application = Android.App.Application;
using Avalonia;
namespace ControlCatalog.Android
{
[Activity(Theme = "@style/MyTheme.Splash", MainLauncher = true, NoHistory = true)]
public class SplashActivity : Activity
{
protected override void OnCreate(Bundle savedInstanceState)
{
base.OnCreate(savedInstanceState);
}
protected override void OnResume()
{
base.OnResume();
if (Avalonia.Application.Current == null)
{
AppBuilder.Configure<App>()
.UseAndroid()
.SetupWithoutStarting();
}
StartActivity(new Intent(Application.Context, typeof(MainActivity)));
}
}
}

3
samples/ControlCatalog.NetCore/Program.cs

@ -109,7 +109,8 @@ namespace ControlCatalog.NetCore
.With(new X11PlatformOptions
{
EnableMultiTouch = true,
UseDBusMenu = true
UseDBusMenu = true,
EnableIme = true,
})
.With(new Win32PlatformOptions
{

BIN
samples/ControlCatalog/Assets/Fonts/WenQuanYiMicroHei-01.ttf

Binary file not shown.

4
samples/ControlCatalog/Pages/DataGridPage.xaml

@ -38,8 +38,8 @@
<DataGrid.Columns>
<DataGridTextColumn Header="Country" Binding="{Binding Name}" Width="6*" />
<DataGridTextColumn Header="Region" Binding="{Binding Region}" Width="4*" />
<DataGridTextColumn Header="Population" Binding="{Binding Population}" Width="3*" />
<DataGridTextColumn Header="Area" Binding="{Binding Area}" Width="3*" />
<DataGridTextColumn DisplayIndex="3" Header="Population" Binding="{Binding Population}" Width="3*" />
<DataGridTextColumn DisplayIndex="2" Header="Area" Binding="{Binding Area}" Width="3*" />
<DataGridTextColumn Header="GDP" Binding="{Binding GDP}" Width="3*" />
</DataGrid.Columns>
</DataGrid>

14
samples/ControlCatalog/Pages/SliderPage.xaml

@ -22,6 +22,20 @@
IsSnapToTickEnabled="True"
Ticks="0,20,25,40,75,100"
Width="300" />
<Slider Name="SliderWithTooltip"
Value="0"
Minimum="0"
Maximum="100"
Width="300">
<Slider.Styles>
<Style Selector="Slider /template/ Thumb">
<Setter Property="ToolTip.Tip" Value="{Binding $parent[Slider].Value, Mode=OneWay, StringFormat='Value \{0:f\}'}" />
<Setter Property="ToolTip.Placement" Value="Top" />
<Setter Property="ToolTip.VerticalOffset" Value="-10" />
<Setter Property="ToolTip.HorizontalOffset" Value="-30" />
</Style>
</Slider.Styles>
</Slider>
<Slider Value="0"
Minimum="0"
Maximum="100"

3
samples/ControlCatalog/Pages/TextBoxPage.xaml

@ -64,5 +64,8 @@
<TextBox Width="200" Text="Custom font italic bold" FontWeight="Bold" FontStyle="Italic" FontFamily="/Assets/Fonts/SourceSansPro-*.ttf#Source Sans Pro"/>
</StackPanel>
</StackPanel>
<TextBox AcceptsReturn="True" TextWrapping="Wrap" Height="200" MaxWidth="400"
FontFamily="avares://ControlCatalog/Assets/Fonts#WenQuanYi Micro Hei"
Text="计算机科学(是系统性研究信息与计算的理论基础以及它们在计算机系统中如何实现与应用的实用技术的学科。它通常被形容为对那些创造、描述以及转换信息的算法处理的系统研究。计算机科学包含很多分支领域;有些强调特定结果的计算,比如计算机图形学;而有些是探討计算问题的性质,比如计算复杂性理论;还有一些领域專注于怎样实现计算,比如程式語言理論是研究描述计算的方法,而程式设计是应用特定的程式語言解决特定的计算问题,人机交互则是專注于怎样使计算机和计算变得有用、好用,以及随时随地为人所用。&#xD;&#xD;有时公众会误以为计算机科学就是解决计算机问题的事业(比如信息技术),或者只是与使用计算机的经验有关,如玩游戏、上网或者文字处理。其实计算机科学所关注的,不仅仅是去理解实现类似游戏、浏览器这些软件的程序的性质,更要通过现有的知识创造新的程序或者改进已有的程序。" />
</StackPanel>
</UserControl>

27
samples/ControlCatalog/Pages/ToolTipPage.xaml

@ -6,7 +6,7 @@
<TextBlock Classes="h1">ToolTip</TextBlock>
<TextBlock Classes="h2">A control which pops up a hint when a control is hovered</TextBlock>
<Grid RowDefinitions="Auto,Auto"
<Grid RowDefinitions="Auto,Auto,Auto"
ColumnDefinitions="Auto,Auto"
Margin="0,16,0,0"
HorizontalAlignment="Center">
@ -38,6 +38,31 @@
</ToolTip.Tip>
<TextBlock>ToolTip bottom placement</TextBlock>
</Border>
<Border Grid.Row="2"
Grid.ColumnSpan="2"
Background="{DynamicResource SystemAccentColor}"
Margin="5"
Padding="50"
ToolTip.Tip="Hello"
ToolTip.Placement="Top">
<Border.Styles>
<Style Selector="Border">
<Style.Animations>
<Animation Duration="0:0:2" IterationCount="Infinite">
<KeyFrame KeyTime="0:0:0">
<Setter Property="ToolTip.HorizontalOffset" Value="0" />
<Setter Property="ToolTip.VerticalOffset" Value="-50" />
</KeyFrame>
<KeyFrame KeyTime="0:0:2" >
<Setter Property="ToolTip.HorizontalOffset" Value="100" />
<Setter Property="ToolTip.VerticalOffset" Value="50" />
</KeyFrame>
</Animation>
</Style.Animations>
</Style>
</Border.Styles>
<TextBlock>Moving offset</TextBlock>
</Border>
</Grid>
</StackPanel>
</UserControl>

19
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -1,11 +1,13 @@
using System;
using Avalonia.Android;
using Avalonia.Android.Platform;
using Avalonia.Android.Platform.Input;
using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.OpenGL.Egl;
using Avalonia.Platform;
using Avalonia.Rendering;
using Avalonia.Shared.PlatformSupport;
@ -17,7 +19,8 @@ namespace Avalonia
{
public static T UseAndroid<T>(this T builder) where T : AppBuilderBase<T>, new()
{
builder.UseWindowingSubsystem(() => Android.AndroidPlatform.Initialize(builder.ApplicationType), "Android");
var options = AvaloniaLocator.Current.GetService<AndroidPlatformOptions>() ?? new AndroidPlatformOptions();
builder.UseWindowingSubsystem(() => AndroidPlatform.Initialize(builder.ApplicationType, options), "Android");
builder.UseSkia();
return builder;
}
@ -41,7 +44,7 @@ namespace Avalonia.Android
_scalingFactor = global::Android.App.Application.Context.Resources.DisplayMetrics.ScaledDensity;
}
public static void Initialize(Type appType)
public static void Initialize(Type appType, AndroidPlatformOptions options)
{
AvaloniaLocator.CurrentMutable
.Bind<IClipboard>().ToTransient<ClipboardImpl>()
@ -60,6 +63,11 @@ namespace Avalonia.Android
SkiaPlatform.Initialize();
((global::Android.App.Application) global::Android.App.Application.Context.ApplicationContext)
.RegisterActivityLifecycleCallbacks(new ActivityTracker());
if (options.UseGpu)
{
EglPlatformOpenGlInterface.TryInitialize();
}
}
public IWindowImpl CreateWindow()
@ -72,4 +80,9 @@ namespace Avalonia.Android
throw new NotSupportedException();
}
}
public sealed class AndroidPlatformOptions
{
public bool UseGpu { get; set; } = true;
}
}

32
src/Android/Avalonia.Android/OpenGL/GlPlatformSurface.cs

@ -0,0 +1,32 @@
using System.Linq;
using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;
namespace Avalonia.Android.OpenGL
{
internal sealed class GlPlatformSurface : EglGlPlatformSurfaceBase
{
private readonly EglPlatformOpenGlInterface _egl;
private readonly IEglWindowGlPlatformSurfaceInfo _info;
private GlPlatformSurface(EglPlatformOpenGlInterface egl, IEglWindowGlPlatformSurfaceInfo info)
{
_egl = egl;
_info = info;
}
public override IGlPlatformSurfaceRenderTarget CreateGlRenderTarget() =>
new GlRenderTarget(_egl, _info, _egl.CreateWindowSurface(_info.Handle));
public static GlPlatformSurface TryCreate(IEglWindowGlPlatformSurfaceInfo info)
{
if (EglPlatformOpenGlInterface.TryCreate() is EglPlatformOpenGlInterface egl)
{
return new GlPlatformSurface(egl, info);
}
return null;
}
}
}

23
src/Android/Avalonia.Android/OpenGL/GlRenderTarget.cs

@ -0,0 +1,23 @@
using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;
namespace Avalonia.Android.OpenGL
{
internal sealed class GlRenderTarget : EglPlatformSurfaceRenderTargetBase
{
private readonly EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo _info;
private readonly EglSurface _surface;
public GlRenderTarget(
EglPlatformOpenGlInterface egl,
EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo info,
EglSurface surface)
: base(egl)
{
_info = info;
_surface = surface;
}
public override IGlPlatformSurfaceRenderingSession BeginDraw() => BeginDraw(_surface, _info);
}
}

4
src/Android/Avalonia.Android/Platform/ClipboardImpl.cs

@ -1,7 +1,11 @@
using System;
using System.Threading.Tasks;
using Android.Content;
using Android.Runtime;
using Android.Views;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Platform;

17
src/Android/Avalonia.Android/Platform/SkiaPlatform/FramebufferManager.cs

@ -0,0 +1,17 @@
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Platform;
namespace Avalonia.Android.Platform.SkiaPlatform
{
internal sealed class FramebufferManager : IFramebufferPlatformSurface
{
private readonly TopLevelImpl _topLevel;
public FramebufferManager(TopLevelImpl topLevel)
{
_topLevel = topLevel;
}
public ILockedFramebuffer Lock() => new AndroidFramebuffer(_topLevel.InternalView.Holder.Surface);
}
}

41
src/Android/Avalonia.Android/Platform/SkiaPlatform/TopLevelImpl.cs

@ -2,20 +2,29 @@ using System;
using System.Collections.Generic;
using Android.Content;
using Android.Graphics;
using Android.Runtime;
using Android.Views;
using Avalonia.Android.OpenGL;
using Avalonia.Android.Platform.Input;
using Avalonia.Android.Platform.Specific;
using Avalonia.Android.Platform.Specific.Helpers;
using Avalonia.Controls;
using Avalonia.Controls.Platform.Surfaces;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.OpenGL.Egl;
using Avalonia.OpenGL.Surfaces;
using Avalonia.Platform;
using Avalonia.Rendering;
namespace Avalonia.Android.Platform.SkiaPlatform
{
class TopLevelImpl : IAndroidView, ITopLevelImpl, IFramebufferPlatformSurface
class TopLevelImpl : IAndroidView, ITopLevelImpl, EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo
{
private readonly IGlPlatformSurface _gl;
private readonly IFramebufferPlatformSurface _framebuffer;
private readonly AndroidKeyboardEventsHelper<TopLevelImpl> _keyboardHelper;
private readonly AndroidTouchEventsHelper<TopLevelImpl> _touchHelper;
@ -28,7 +37,8 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_touchHelper = new AndroidTouchEventsHelper<TopLevelImpl>(this, () => InputRoot,
p => GetAvaloniaPointFromEvent(p));
Surfaces = new object[] { this };
_gl = GlPlatformSurface.TryCreate(this);
_framebuffer = new FramebufferManager(this);
MaxClientSize = new Size(_view.Resources.DisplayMetrics.WidthPixels,
_view.Resources.DisplayMetrics.HeightPixels);
@ -47,7 +57,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
_keyboardHelper.HandleEvents = _handleEvents;
}
}
public virtual Point GetAvaloniaPointFromEvent(MotionEvent e) => new Point(e.GetX(), e.GetY());
public IInputRoot InputRoot { get; private set; }
@ -62,7 +72,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
}
set
{
}
}
@ -82,9 +92,11 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public View View => _view;
internal InvalidationAwareSurfaceView InternalView => _view;
public IPlatformHandle Handle => _view;
public IEnumerable<object> Surfaces { get; }
public IEnumerable<object> Surfaces => new object[] { _gl, _framebuffer };
public IRenderer CreateRenderer(IRenderRoot root)
{
@ -95,7 +107,7 @@ namespace Avalonia.Android.Platform.SkiaPlatform
{
_view.Visibility = ViewStates.Invisible;
}
public void Invalidate(Rect rect)
{
if (_view.Holder?.Surface?.IsValid == true) _view.Invalidate();
@ -196,7 +208,22 @@ namespace Avalonia.Android.Platform.SkiaPlatform
public IPopupImpl CreatePopup() => null;
public Action LostFocus { get; set; }
public Action<WindowTransparencyLevel> TransparencyLevelChanged { get; set; }
ILockedFramebuffer IFramebufferPlatformSurface.Lock()=>new AndroidFramebuffer(_view.Holder.Surface);
public WindowTransparencyLevel TransparencyLevel => WindowTransparencyLevel.None;
public AcrylicPlatformCompensationLevels AcrylicCompensationLevels => new AcrylicPlatformCompensationLevels(1, 1, 1);
IntPtr EglGlPlatformSurfaceBase.IEglWindowGlPlatformSurfaceInfo.Handle =>
AndroidFramebuffer.ANativeWindow_fromSurface(JNIEnv.Handle, _view.Holder.Surface.Handle);
public PixelSize Size => new PixelSize(_view.Holder.SurfaceFrame.Width(), _view.Holder.SurfaceFrame.Height());
public double Scaling => RenderScaling;
public void SetTransparencyLevelHint(WindowTransparencyLevel transparencyLevel)
{
throw new NotImplementedException();
}
}
}

11
src/Android/Avalonia.Android/Platform/Specific/Helpers/AndroidKeyboardEventsHelper.cs

@ -5,14 +5,14 @@ using Android.Runtime;
using Android.Views;
using Android.Views.InputMethods;
using Avalonia.Android.Platform.Input;
using Avalonia.Android.Platform.SkiaPlatform;
using Avalonia.Controls;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Platform;
namespace Avalonia.Android.Platform.Specific.Helpers
{
public class AndroidKeyboardEventsHelper<TView> : IDisposable where TView :ITopLevelImpl, IAndroidView
internal class AndroidKeyboardEventsHelper<TView> : IDisposable where TView : TopLevelImpl, IAndroidView
{
private TView _view;
private IInputElement _lastFocusedElement;
@ -46,9 +46,11 @@ namespace Avalonia.Android.Platform.Specific.Helpers
var rawKeyEvent = new RawKeyEventArgs(
AndroidKeyboardDevice.Instance,
Convert.ToUInt32(e.EventTime),
Convert.ToUInt64(e.EventTime),
_view.InputRoot,
e.Action == KeyEventActions.Down ? RawKeyEventType.KeyDown : RawKeyEventType.KeyUp,
AndroidKeyboardDevice.ConvertKey(e.KeyCode), GetModifierKeys(e));
AndroidKeyboardDevice.ConvertKey(e.KeyCode), GetModifierKeys(e));
_view.Input(rawKeyEvent);
if (e.Action == KeyEventActions.Down && e.UnicodeChar >= 32)
@ -56,6 +58,7 @@ namespace Avalonia.Android.Platform.Specific.Helpers
var rawTextEvent = new RawTextInputEventArgs(
AndroidKeyboardDevice.Instance,
Convert.ToUInt32(e.EventTime),
_view.InputRoot,
Convert.ToChar(e.UnicodeChar).ToString()
);
_view.Input(rawTextEvent);

60
src/Android/Avalonia.Android/Resources/Resource.Designer.cs

@ -1,60 +0,0 @@
#pragma warning disable 1591
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
[assembly: global::Android.Runtime.ResourceDesignerAttribute("Avalonia.Android.Resource", IsApplication=false)]
namespace Avalonia.Android
{
[System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
public partial class Resource
{
static Resource()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
public partial class Attribute
{
static Attribute()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private Attribute()
{
}
}
public partial class String
{
// aapt resource value: 0x7f020001
public static int ApplicationName = 2130837505;
// aapt resource value: 0x7f020000
public static int Hello = 2130837504;
static String()
{
global::Android.Runtime.ResourceIdManager.UpdateIdValues();
}
private String()
{
}
}
}
}
#pragma warning restore 1591

5
src/Android/Avalonia.Android/SystemDialogImpl.cs

@ -2,18 +2,17 @@ using System;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Platform;
namespace Avalonia.Android
{
internal class SystemDialogImpl : ISystemDialogImpl
{
public Task<string[]> ShowFileDialogAsync(FileDialog dialog, IWindowImpl parent)
public Task<string[]> ShowFileDialogAsync(FileDialog dialog, Window parent)
{
throw new NotImplementedException();
}
public Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, IWindowImpl parent)
public Task<string> ShowFolderDialogAsync(OpenFolderDialog dialog, Window parent)
{
throw new NotImplementedException();
}

4
src/Android/Avalonia.AndroidTestApplication/Avalonia.AndroidTestApplication.csproj

@ -16,7 +16,7 @@
<AndroidResgenFile>Resources\Resource.Designer.cs</AndroidResgenFile>
<GenerateSerializationAssemblies>Off</GenerateSerializationAssemblies>
<AndroidUseLatestPlatformSdk>False</AndroidUseLatestPlatformSdk>
<TargetFrameworkVersion>v9.0</TargetFrameworkVersion>
<TargetFrameworkVersion>v10.0</TargetFrameworkVersion>
<AndroidManifest>Properties\AndroidManifest.xml</AndroidManifest>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
@ -150,4 +150,4 @@
<Import Project="..\..\..\build\System.Memory.props" />
<Import Project="..\..\..\build\AndroidWorkarounds.props" />
<Import Project="..\..\..\build\LegacyProject.targets" />
</Project>
</Project>

4
src/Android/Avalonia.AndroidTestApplication/MainActivity.cs

@ -4,7 +4,6 @@ using Android.Content.PM;
using Android.OS;
using Avalonia.Android;
using Avalonia.Controls;
using Avalonia.Controls.Templates;
using Avalonia.Markup.Xaml;
using Avalonia.Media;
using Avalonia.Styling;
@ -38,8 +37,7 @@ namespace Avalonia.AndroidTestApplication
{
Styles.Add(new DefaultTheme());
var loader = new AvaloniaXamlLoader();
var baseLight = (IStyle)loader.Load(
var baseLight = (IStyle)AvaloniaXamlLoader.Load(
new Uri("resm:Avalonia.Themes.Default.Accents.BaseLight.xaml?assembly=Avalonia.Themes.Default"));
Styles.Add(baseLight);

2
src/Android/Avalonia.AndroidTestApplication/Properties/AndroidManifest.xml

@ -1,6 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="Avalonia.AndroidTestApplication" android:versionCode="1" android:versionName="1.0" android:installLocation="auto">
<uses-sdk />
<uses-sdk android:targetSdkVersion="29" />
<application android:label="Avalonia.AndroidTestApplication" android:icon="@drawable/Icon" android:hardwareAccelerated="true"></application>
<uses-permission android:name="android.permission.INTERNET" />
</manifest>

17
src/Android/Avalonia.AndroidTestApplication/Resources/Resource.Designer.cs

@ -2,7 +2,6 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
// Runtime Version:4.0.30319.42000
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
@ -15,7 +14,7 @@ namespace Avalonia.AndroidTestApplication
{
[System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
[global::System.CodeDom.Compiler.GeneratedCodeAttribute("Xamarin.Android.Build.Tasks", "1.0.0.0")]
public partial class Resource
{
@ -26,8 +25,6 @@ namespace Avalonia.AndroidTestApplication
public static void UpdateIdValues()
{
global::Avalonia.Android.Resource.String.ApplicationName = global::Avalonia.AndroidTestApplication.Resource.String.ApplicationName;
global::Avalonia.Android.Resource.String.Hello = global::Avalonia.AndroidTestApplication.Resource.String.Hello;
}
public partial class Attribute
@ -46,8 +43,8 @@ namespace Avalonia.AndroidTestApplication
public partial class Drawable
{
// aapt resource value: 0x7f020000
public const int Icon = 2130837504;
// aapt resource value: 0x7F010000
public const int Icon = 2130771968;
static Drawable()
{
@ -62,11 +59,11 @@ namespace Avalonia.AndroidTestApplication
public partial class String
{
// aapt resource value: 0x7f030001
public const int ApplicationName = 2130903041;
// aapt resource value: 0x7F020000
public const int ApplicationName = 2130837504;
// aapt resource value: 0x7f030000
public const int Hello = 2130903040;
// aapt resource value: 0x7F020001
public const int Hello = 2130837505;
static String()
{

3
src/Avalonia.Base/Avalonia.Base.csproj

@ -5,9 +5,6 @@
<RootNamespace>Avalonia</RootNamespace>
<AllowUnsafeBlocks>True</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\Avalonia.Build.Tasks\Avalonia.Build.Tasks.csproj"/>
</ItemGroup>
<Import Project="..\..\build\Base.props" />
<Import Project="..\..\build\Binding.props" />
<Import Project="..\..\build\Rx.props" />

25
src/Avalonia.Base/Data/Converters/MethodToCommandConverter.cs

@ -63,7 +63,7 @@ namespace Avalonia.Data.Converters
}
}
void OnPropertyChanged(object sender,PropertyChangedEventArgs args)
void OnPropertyChanged(object sender, PropertyChangedEventArgs args)
{
if (string.IsNullOrWhiteSpace(args.PropertyName)
|| dependencyProperties?.Contains(args.PropertyName) == true)
@ -88,12 +88,7 @@ namespace Avalonia.Data.Converters
var parameter = Expression.Parameter(typeof(object), "parameter");
var instance = Expression.Convert
(
Expression.Constant(target),
method.DeclaringType
);
var instance = ConvertTarget(target, method);
var call = Expression.Call
(
@ -114,11 +109,7 @@ namespace Avalonia.Data.Converters
var parameter = Expression.Parameter(typeof(object), "parameter");
var instance = Expression.Convert
(
Expression.Constant(target),
method.DeclaringType
);
var instance = ConvertTarget(target, method);
Expression body;
@ -167,11 +158,7 @@ namespace Avalonia.Data.Converters
, System.Reflection.MethodInfo method)
{
var parameter = Expression.Parameter(typeof(object), "parameter");
var instance = Expression.Convert
(
Expression.Constant(target),
method.DeclaringType
);
var instance = ConvertTarget(target, method);
var call = Expression.Call
(
instance,
@ -183,6 +170,8 @@ namespace Avalonia.Data.Converters
.Compile();
}
private static Expression? ConvertTarget(object? target, MethodInfo method) =>
target is null ? null : Expression.Convert(Expression.Constant(target), method.DeclaringType);
internal class WeakPropertyChangedProxy
{
@ -224,7 +213,7 @@ namespace Avalonia.Data.Converters
else
Unsubscribe();
}
}
}
}

8
src/Avalonia.Base/Data/Core/Plugins/InpcPropertyAccessorPlugin.cs

@ -14,7 +14,7 @@ namespace Avalonia.Data.Core.Plugins
{
private readonly Dictionary<(Type, string), PropertyInfo> _propertyLookup =
new Dictionary<(Type, string), PropertyInfo>();
/// <inheritdoc/>
public bool Match(object obj, string propertyName) => GetFirstPropertyWithName(obj.GetType(), propertyName) != null;
@ -51,7 +51,7 @@ namespace Avalonia.Data.Core.Plugins
private PropertyInfo GetFirstPropertyWithName(Type type, string propertyName)
{
var key = (type, propertyName);
if (!_propertyLookup.TryGetValue(key, out PropertyInfo propertyInfo))
{
propertyInfo = TryFindAndCacheProperty(type, propertyName);
@ -59,7 +59,7 @@ namespace Avalonia.Data.Core.Plugins
return propertyInfo;
}
private PropertyInfo TryFindAndCacheProperty(Type type, string propertyName)
{
PropertyInfo found = null;
@ -90,7 +90,7 @@ namespace Avalonia.Data.Core.Plugins
private readonly PropertyInfo _property;
private bool _eventRaised;
public Accessor(WeakReference<object> reference, PropertyInfo property)
public Accessor(WeakReference<object> reference, PropertyInfo property)
{
Contract.Requires<ArgumentNullException>(reference != null);
Contract.Requires<ArgumentNullException>(property != null);

5
src/Avalonia.Base/Data/Core/SettableNode.cs

@ -15,7 +15,8 @@ namespace Avalonia.Data.Core
private bool ShouldNotSet(object value)
{
if (PropertyType == null)
var propertyType = PropertyType;
if (propertyType == null)
{
return false;
}
@ -37,7 +38,7 @@ namespace Avalonia.Data.Core
return false;
}
if (PropertyType.IsValueType)
if (propertyType.IsValueType)
{
return lastValue.Equals(value);
}

125
src/Avalonia.Controls.DataGrid/DataGrid.cs

@ -1,6 +1,6 @@
// This source is subject to the Microsoft Public License (Ms-PL).
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
// All other rights reserved.
using Avalonia.Collections;
using Avalonia.Controls.Primitives;
@ -92,7 +92,7 @@ namespace Avalonia.Controls
private ContentControl _topRightCornerHeader;
private Control _frozenColumnScrollBarSpacer;
// the sum of the widths in pixels of the scrolling columns preceding
// the sum of the widths in pixels of the scrolling columns preceding
// the first displayed scrolling column
private double _horizontalOffset;
@ -143,7 +143,7 @@ namespace Avalonia.Controls
private object _uneditedValue; // Represents the original current cell value at the time it enters editing mode.
private ICellEditBinding _currentCellEditBinding;
// An approximation of the sum of the heights in pixels of the scrolling rows preceding
// An approximation of the sum of the heights in pixels of the scrolling rows preceding
// the first displayed scrolling row. Since the scrolled off rows are discarded, the grid
// does not know their actual height. The heights used for the approximation are the ones
// set as the rows were scrolled off.
@ -162,7 +162,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<DataGrid, bool>(nameof(CanUserReorderColumns));
/// <summary>
/// Gets or sets a value that indicates whether the user can change
/// Gets or sets a value that indicates whether the user can change
/// the column display order by dragging column headers with the mouse.
/// </summary>
public bool CanUserReorderColumns
@ -247,8 +247,8 @@ namespace Avalonia.Controls
/// Gets or sets the <see cref="T:System.Windows.Media.Brush" /> that is used to paint the background of odd-numbered rows.
/// </summary>
/// <returns>
/// The brush that is used to paint the background of odd-numbered rows. The default is a
/// <see cref="T:System.Windows.Media.SolidColorBrush" /> with a
/// The brush that is used to paint the background of odd-numbered rows. The default is a
/// <see cref="T:System.Windows.Media.SolidColorBrush" /> with a
/// <see cref="P:System.Windows.Media.SolidColorBrush.Color" /> value of white (ARGB value #00FFFFFF).
/// </returns>
public IBrush AlternatingRowBackground
@ -379,8 +379,8 @@ namespace Avalonia.Controls
public bool IsValid
{
get { return _isValid; }
internal set
{
internal set
{
SetAndRaise(IsValidProperty, ref _isValid, value);
PseudoClasses.Set(":invalid", !value);
}
@ -398,7 +398,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// Gets or sets the maximum width of columns in the <see cref="T:Avalonia.Controls.DataGrid" /> .
/// Gets or sets the maximum width of columns in the <see cref="T:Avalonia.Controls.DataGrid" /> .
/// </summary>
public double MaxColumnWidth
{
@ -418,7 +418,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// Gets or sets the minimum width of columns in the <see cref="T:Avalonia.Controls.DataGrid" />.
/// Gets or sets the minimum width of columns in the <see cref="T:Avalonia.Controls.DataGrid" />.
/// </summary>
public double MinColumnWidth
{
@ -496,7 +496,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<DataGrid, IBrush>(nameof(VerticalGridLinesBrush));
/// <summary>
/// Gets or sets the <see cref="T:System.Windows.Media.Brush" /> that is used to paint grid lines separating columns.
/// Gets or sets the <see cref="T:System.Windows.Media.Brush" /> that is used to paint grid lines separating columns.
/// </summary>
public IBrush VerticalGridLinesBrush
{
@ -542,7 +542,7 @@ namespace Avalonia.Controls
/// </summary>
/// <returns>
/// The index of the current selection, or -1 if the selection is empty.
/// </returns>
/// </returns>
public int SelectedIndex
{
get { return _selectedIndex; }
@ -582,7 +582,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<DataGrid, bool>(nameof(AutoGenerateColumns));
/// <summary>
/// Gets or sets a value that indicates whether columns are created
/// Gets or sets a value that indicates whether columns are created
/// automatically when the <see cref="P:Avalonia.Controls.DataGrid.ItemsSource" /> property is set.
/// </summary>
public bool AutoGenerateColumns
@ -626,7 +626,7 @@ namespace Avalonia.Controls
AvaloniaProperty.Register<DataGrid, bool>(nameof(AreRowDetailsFrozen));
/// <summary>
/// Gets or sets a value that indicates whether the row details sections remain
/// Gets or sets a value that indicates whether the row details sections remain
/// fixed at the width of the display area or can scroll horizontally.
/// </summary>
public bool AreRowDetailsFrozen
@ -881,7 +881,7 @@ namespace Avalonia.Controls
{
int index = (int)e.NewValue;
// GetDataItem returns null if index is >= Count, we do not check newValue
// GetDataItem returns null if index is >= Count, we do not check newValue
// against Count here to avoid enumerating through an Enumerable twice
// Setting SelectedItem coerces the finally value of the SelectedIndex
object newSelectedItem = (index < 0) ? null : DataConnection.GetDataItem(index);
@ -1168,14 +1168,14 @@ namespace Avalonia.Controls
}
/// <summary>
/// Occurs one time for each public, non-static property in the bound data type when the
/// <see cref="P:Avalonia.Controls.DataGrid.ItemsSource" /> property is changed and the
/// Occurs one time for each public, non-static property in the bound data type when the
/// <see cref="P:Avalonia.Controls.DataGrid.ItemsSource" /> property is changed and the
/// <see cref="P:Avalonia.Controls.DataGrid.AutoGenerateColumns" /> property is true.
/// </summary>
public event EventHandler<DataGridAutoGeneratingColumnEventArgs> AutoGeneratingColumn;
/// <summary>
/// Occurs before a cell or row enters editing mode.
/// Occurs before a cell or row enters editing mode.
/// </summary>
public event EventHandler<DataGridBeginningEditEventArgs> BeginningEdit;
@ -1195,7 +1195,7 @@ namespace Avalonia.Controls
public event EventHandler<DataGridCellPointerPressedEventArgs> CellPointerPressed;
/// <summary>
/// Occurs when the <see cref="P:Avalonia.Controls.DataGridColumn.DisplayIndex" />
/// Occurs when the <see cref="P:Avalonia.Controls.DataGridColumn.DisplayIndex" />
/// property of a column changes.
/// </summary>
public event EventHandler<DataGridColumnEventArgs> ColumnDisplayIndexChanged;
@ -1218,14 +1218,14 @@ namespace Avalonia.Controls
public event EventHandler<EventArgs> CurrentCellChanged;
/// <summary>
/// Occurs after a <see cref="T:Avalonia.Controls.DataGridRow" />
/// Occurs after a <see cref="T:Avalonia.Controls.DataGridRow" />
/// is instantiated, so that you can customize it before it is used.
/// </summary>
public event EventHandler<DataGridRowEventArgs> LoadingRow;
/// <summary>
/// Occurs when a cell in a <see cref="T:Avalonia.Controls.DataGridTemplateColumn" /> enters editing mode.
///
///
/// </summary>
public event EventHandler<DataGridPreparingCellForEditEventArgs> PreparingCellForEdit;
@ -1243,7 +1243,7 @@ namespace Avalonia.Controls
RoutedEvent.Register<DataGrid, SelectionChangedEventArgs>(nameof(SelectionChanged), RoutingStrategies.Bubble);
/// <summary>
/// Occurs when the <see cref="P:Avalonia.Controls.DataGrid.SelectedItem" /> or
/// Occurs when the <see cref="P:Avalonia.Controls.DataGrid.SelectedItem" /> or
/// <see cref="P:Avalonia.Controls.DataGrid.SelectedItems" /> property value changes.
/// </summary>
public event EventHandler<SelectionChangedEventArgs> SelectionChanged
@ -1258,19 +1258,19 @@ namespace Avalonia.Controls
public event EventHandler<DataGridColumnEventArgs> Sorting;
/// <summary>
/// Occurs when a <see cref="T:Avalonia.Controls.DataGridRow" />
/// Occurs when a <see cref="T:Avalonia.Controls.DataGridRow" />
/// object becomes available for reuse.
/// </summary>
public event EventHandler<DataGridRowEventArgs> UnloadingRow;
/// <summary>
/// Occurs when a new row details template is applied to a row, so that you can customize
/// Occurs when a new row details template is applied to a row, so that you can customize
/// the details section before it is used.
/// </summary>
public event EventHandler<DataGridRowDetailsEventArgs> LoadingRowDetails;
/// <summary>
/// Occurs when the <see cref="P:Avalonia.Controls.DataGrid.RowDetailsVisibilityMode" />
/// Occurs when the <see cref="P:Avalonia.Controls.DataGrid.RowDetailsVisibilityMode" />
/// property value changes.
/// </summary>
public event EventHandler<DataGridRowDetailsEventArgs> RowDetailsVisibilityChanged;
@ -1282,7 +1282,7 @@ namespace Avalonia.Controls
/// <summary>
/// Gets a collection that contains all the columns in the control.
/// </summary>
/// </summary>
public ObservableCollection<DataGridColumn> Columns
{
get
@ -1456,7 +1456,7 @@ namespace Avalonia.Controls
}
// Height currently available for cells this value is smaller. This height is reduced by the existence of ColumnHeaders
// or a horizontal scrollbar. Layout is asynchronous so changes to the ColumnHeaders or the horizontal scrollbar are
// or a horizontal scrollbar. Layout is asynchronous so changes to the ColumnHeaders or the horizontal scrollbar are
// not reflected immediately.
internal double CellsHeight
{
@ -1555,7 +1555,7 @@ namespace Avalonia.Controls
internal static double HorizontalGridLinesThickness => DATAGRID_horizontalGridLinesThickness;
// the sum of the widths in pixels of the scrolling columns preceding
// the sum of the widths in pixels of the scrolling columns preceding
// the first displayed scrolling column
internal double HorizontalOffset
{
@ -2083,20 +2083,20 @@ namespace Avalonia.Controls
}
/// <summary>
/// Measures the children of a <see cref="T:Avalonia.Controls.DataGridRow" /> to prepare for
/// arranging them during the
/// <see cref="M:Avalonia.Controls.DataGridRow.ArrangeOverride(System.Windows.Size)" /> pass.
/// Measures the children of a <see cref="T:Avalonia.Controls.DataGridRow" /> to prepare for
/// arranging them during the
/// <see cref="M:Avalonia.Controls.DataGridRow.ArrangeOverride(System.Windows.Size)" /> pass.
/// </summary>
/// <returns>
/// The size that the <see cref="T:Avalonia.Controls.DataGridRow" /> determines it needs during layout, based on its calculations of child object allocated sizes.
/// </returns>
/// <param name="availableSize">
/// The available size that this element can give to child elements. Indicates an upper limit that
/// The available size that this element can give to child elements. Indicates an upper limit that
/// child elements should not exceed.
/// </param>
protected override Size MeasureOverride(Size availableSize)
{
// Delay layout until after the initial measure to avoid invalid calculations when the
// Delay layout until after the initial measure to avoid invalid calculations when the
// DataGrid is not part of the visual tree
if (!_measured)
{
@ -2285,6 +2285,17 @@ namespace Avalonia.Controls
}
}
/// <summary>
/// Comparator class so we can sort list by the display index
/// </summary>
public class DisplayIndexComparer : IComparer<DataGridColumn>
{
int IComparer<DataGridColumn>.Compare(DataGridColumn x, DataGridColumn y)
{
return (x.DisplayIndexWithFiller < y.DisplayIndexWithFiller) ? -1 : 1;
}
}
/// <summary>
/// Builds the visual tree for the column header when a new template is applied.
/// </summary>
@ -2309,8 +2320,11 @@ namespace Avalonia.Controls
ColumnsInternal.FillerColumn.IsRepresented = false;
}
_columnHeadersPresenter.OwningGrid = this;
// Columns were added before before our Template was applied, add the ColumnHeaders now
foreach (DataGridColumn column in ColumnsItemsInternal)
// Columns were added before our Template was applied, add the ColumnHeaders now
List<DataGridColumn> sortedInternal = new List<DataGridColumn>(ColumnsItemsInternal);
sortedInternal.Sort(new DisplayIndexComparer());
foreach (DataGridColumn column in sortedInternal)
{
InsertDisplayedColumnHeader(column);
}
@ -3006,7 +3020,7 @@ namespace Avalonia.Controls
/// If the editing element has focus, this method will set focus to the DataGrid itself
/// in order to force the element to lose focus. It will then wait for the editing element's
/// LostFocus event, at which point it will perform the specified action.
///
///
/// NOTE: It is important to understand that the specified action will be performed when the editing
/// element loses focus only if this method returns true. If it returns false, then the action
/// will not be performed later on, and should instead be performed by the caller, if necessary.
@ -3065,7 +3079,7 @@ namespace Avalonia.Controls
{
if (!_scrollingByHeight)
{
// Update layout when RowDetails are expanded or collapsed, just updating the vertical scroll bar is not enough
// Update layout when RowDetails are expanded or collapsed, just updating the vertical scroll bar is not enough
// since rows could be added or removed
InvalidateMeasure();
}
@ -3278,7 +3292,7 @@ namespace Avalonia.Controls
{
// Current cell was reset because the commit deleted row(s).
// Since the user wants to change the current cell, we don't
// want to end up with no current cell. We pick the last row
// want to end up with no current cell. We pick the last row
// in the grid which may be the 'new row'.
int lastSlot = LastVisibleSlot;
if (forCurrentCellChange &&
@ -3336,7 +3350,7 @@ namespace Avalonia.Controls
if (_ignoreNextScrollBarsLayout)
{
_ignoreNextScrollBarsLayout = false;
//
//
}
@ -3393,7 +3407,7 @@ namespace Avalonia.Controls
}
// Now cellsWidth is the width potentially available for displaying data cells.
// Now cellsHeight is the height potentially available for displaying data cells.
// Now cellsHeight is the height potentially available for displaying data cells.
bool needHorizScrollbar = false;
bool needVertScrollbar = false;
@ -3418,7 +3432,7 @@ namespace Avalonia.Controls
Debug.Assert(cellsHeight >= 0);
needHorizScrollbarWithoutVertScrollbar = needHorizScrollbar = true;
if (vertScrollBarWidth > 0 &&
if (vertScrollBarWidth > 0 &&
allowVertScrollbar && (MathUtilities.LessThanOrClose(totalVisibleWidth - cellsWidth, vertScrollBarWidth) ||
MathUtilities.LessThanOrClose(cellsWidth - totalVisibleFrozenWidth, vertScrollBarWidth)))
{
@ -3458,7 +3472,7 @@ namespace Avalonia.Controls
// we compute the number of visible columns only after we set up the vertical scroll bar.
ComputeDisplayedColumns();
if ((vertScrollBarWidth > 0 || horizScrollBarHeight > 0) &&
if ((vertScrollBarWidth > 0 || horizScrollBarHeight > 0) &&
allowHorizScrollbar &&
needVertScrollbar && !needHorizScrollbar &&
MathUtilities.GreaterThan(totalVisibleWidth, cellsWidth) &&
@ -3963,7 +3977,8 @@ namespace Avalonia.Controls
{
var errorList =
binding.ValidationErrors
.SelectMany(ex => ValidationUtil.UnpackException(ex))
.SelectMany(ValidationUtil.UnpackException)
.Select(ValidationUtil.UnpackDataValidationException)
.ToList();
DataValidationErrors.SetErrors(editingElement, errorList);
@ -4124,7 +4139,7 @@ namespace Avalonia.Controls
}
/// <summary>
/// Exits editing mode without trying to commit or revert the editing, and
/// Exits editing mode without trying to commit or revert the editing, and
/// without repopulating the edited row's cell.
/// </summary>
//TODO TabStop
@ -5103,9 +5118,9 @@ namespace Avalonia.Controls
{
if (ctrl || _editingColumnIndex == -1 || IsReadOnly)
{
//Go to the next/previous control on the page when
//Go to the next/previous control on the page when
// - Ctrl key is used
// - Potential current cell is not edited, or the datagrid is read-only.
// - Potential current cell is not edited, or the datagrid is read-only.
return false;
}
@ -5516,11 +5531,11 @@ namespace Avalonia.Controls
// v---v
//|<|_____|###|>|
// ^ ^
// min max
// min max
// we want to make the relative size of the thumb reflect the relative size of the viewing area
// viewportSize / (max + viewportSize) = cellsWidth / max
// -> viewportSize = max * cellsWidth / (max - cellsWidth)
// -> viewportSize = max * cellsWidth / (max - cellsWidth)
// always zero
_hScrollBar.Minimum = 0;
@ -5572,7 +5587,7 @@ namespace Avalonia.Controls
_hScrollBar.Maximum = 0;
if (_hScrollBar.IsVisible)
{
// This will trigger a call to this method via Cells_SizeChanged for
// This will trigger a call to this method via Cells_SizeChanged for
// which no processing is needed.
_hScrollBar.IsVisible = false;
_ignoreNextScrollBarsLayout = true;
@ -5591,14 +5606,14 @@ namespace Avalonia.Controls
// v---v
//|<|_____|###|>|
// ^ ^
// min max
// min max
// we want to make the relative size of the thumb reflect the relative size of the viewing area
// viewportSize / (max + viewportSize) = cellsWidth / max
// -> viewportSize = max * cellsHeight / (totalVisibleHeight - cellsHeight)
// -> = max * cellsHeight / (totalVisibleHeight - cellsHeight)
// -> = max * cellsHeight / max
// -> = cellsHeight
// -> = cellsHeight
// always zero
_vScrollBar.Minimum = 0;
@ -5621,7 +5636,7 @@ namespace Avalonia.Controls
if (!_vScrollBar.IsVisible)
{
// This will trigger a call to this method via Cells_SizeChanged for
// This will trigger a call to this method via Cells_SizeChanged for
// which no processing is needed.
_vScrollBar.IsVisible = true;
if (_vScrollBar.DesiredSize.Width == 0)
@ -5637,7 +5652,7 @@ namespace Avalonia.Controls
_vScrollBar.Maximum = 0;
if (_vScrollBar.IsVisible)
{
// This will trigger a call to this method via Cells_SizeChanged for
// This will trigger a call to this method via Cells_SizeChanged for
// which no processing is needed.
_vScrollBar.IsVisible = false;
_ignoreNextScrollBarsLayout = true;
@ -5660,8 +5675,8 @@ namespace Avalonia.Controls
Debug.Assert(slot >= 0);
// Before changing selection, check if the current cell needs to be committed, and
// check if the current row needs to be committed. If any of those two operations are required and fail,
// do not change selection, and do not change current cell.
// check if the current row needs to be committed. If any of those two operations are required and fail,
// do not change selection, and do not change current cell.
bool wasInEdit = EditingColumnIndex != -1;

35
src/Avalonia.Controls.DataGrid/Primitives/DataGridRowsPresenter.cs

@ -3,14 +3,15 @@
// Please see http://go.microsoft.com/fwlink/?LinkID=131993 for details.
// All other rights reserved.
using Avalonia.Media;
using System;
using System.Diagnostics;
using Avalonia.Layout;
using Avalonia.Media;
namespace Avalonia.Controls.Primitives
{
/// <summary>
/// Used within the template of a <see cref="T:Avalonia.Controls.DataGrid" /> to specify the
/// Used within the template of a <see cref="T:Avalonia.Controls.DataGrid" /> to specify the
/// location in the control's visual tree where the rows are to be added.
/// </summary>
public sealed class DataGridRowsPresenter : Panel
@ -22,25 +23,10 @@ namespace Avalonia.Controls.Primitives
}
private double _measureHeightOffset = 0;
private double _effectiveViewPortHeight = 0;
public DataGridRowsPresenter()
{
EffectiveViewportChanged += OnEffectiveViewportChanged;
}
private void OnEffectiveViewportChanged(object sender, Layout.EffectiveViewportChangedEventArgs e)
{
if (_effectiveViewPortHeight != e.EffectiveViewport.Height)
{
_effectiveViewPortHeight = e.EffectiveViewport.Height;
InvalidateMeasure();
}
}
private double CalculateEstimatedAvailableHeight(Size availableSize)
{
if(!Double.IsPositiveInfinity(availableSize.Height))
if (!Double.IsPositiveInfinity(availableSize.Height))
{
return availableSize.Height + _measureHeightOffset;
}
@ -66,10 +52,10 @@ namespace Avalonia.Controls.Primitives
return base.ArrangeOverride(finalSize);
}
if(OwningGrid.RowsPresenterAvailableSize.HasValue)
if (OwningGrid.RowsPresenterAvailableSize.HasValue)
{
var availableHeight = OwningGrid.RowsPresenterAvailableSize.Value.Height;
if(!Double.IsPositiveInfinity(availableHeight))
if (!Double.IsPositiveInfinity(availableHeight))
{
_measureHeightOffset = finalSize.Height - availableHeight;
OwningGrid.RowsPresenterEstimatedAvailableHeight = finalSize.Height;
@ -126,7 +112,14 @@ namespace Avalonia.Controls.Primitives
{
if (double.IsInfinity(availableSize.Height))
{
availableSize = availableSize.WithHeight(_effectiveViewPortHeight);
if (VisualRoot is TopLevel topLevel)
{
double maxHeight = topLevel.IsArrangeValid ?
topLevel.Bounds.Height :
LayoutHelper.ApplyLayoutConstraints(topLevel, availableSize).Height;
availableSize = availableSize.WithHeight(maxHeight);
}
}
if (availableSize.Height == 0 || OwningGrid == null)

25
src/Avalonia.Controls.DataGrid/Utils/ValidationUtil.cs

@ -80,19 +80,24 @@ namespace Avalonia.Controls.Utils
{
if (exception != null)
{
var aggregate = exception as AggregateException;
var exceptions = aggregate == null ?
(IEnumerable<Exception>)new[] { exception } :
aggregate.InnerExceptions;
var filtered = exceptions.Where(x => !(x is BindingChainException)).ToList();
var exceptions = exception is AggregateException aggregate ?
aggregate.InnerExceptions :
(IEnumerable<Exception>)new[] { exception };
if (filtered.Count > 0)
{
return filtered;
}
return exceptions.Where(x => !(x is BindingChainException)).ToList();
}
return null;
return Array.Empty<Exception>();
}
public static object UnpackDataValidationException(Exception exception)
{
if (exception is DataValidationException dataValidationException)
{
return dataValidationException.ErrorData;
}
return exception;
}
/// <summary>

32
src/Avalonia.Controls/Calendar/CalendarDatePicker.cs

@ -11,6 +11,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
namespace Avalonia.Controls
{
@ -209,6 +210,18 @@ namespace Avalonia.Controls
TextBox.UseFloatingWatermarkProperty.AddOwner<CalendarDatePicker>();
/// <summary>
/// Defines the <see cref="HorizontalContentAlignment"/> property.
/// </summary>
public static readonly StyledProperty<HorizontalAlignment> HorizontalContentAlignmentProperty =
ContentControl.HorizontalContentAlignmentProperty.AddOwner<CalendarDatePicker>();
/// <summary>
/// Defines the <see cref="VerticalContentAlignment"/> property.
/// </summary>
public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty =
ContentControl.VerticalContentAlignmentProperty.AddOwner<CalendarDatePicker>();
/// <summary>
/// Gets or sets the date to display.
/// </summary>
@ -364,6 +377,25 @@ namespace Avalonia.Controls
set { SetValue(UseFloatingWatermarkProperty, value); }
}
/// <summary>
/// Gets or sets the horizontal alignment of the content within the control.
/// </summary>
public HorizontalAlignment HorizontalContentAlignment
{
get => GetValue(HorizontalContentAlignmentProperty);
set => SetValue(HorizontalContentAlignmentProperty, value);
}
/// <summary>
/// Gets or sets the vertical alignment of the content within the control.
/// </summary>
public VerticalAlignment VerticalContentAlignment
{
get => GetValue(VerticalContentAlignmentProperty);
set => SetValue(VerticalContentAlignmentProperty, value);
}
/// <summary>
/// Occurs when the drop-down
/// <see cref="T:Avalonia.Controls.Calendar" /> is closed.

2
src/Avalonia.Controls/ContextMenu.cs

@ -246,7 +246,7 @@ namespace Avalonia.Controls
/// <summary>
/// Opens the menu.
/// </summary>
public override void Open() => throw new NotSupportedException();
public override void Open() => Open(null);
/// <summary>
/// Opens a context menu on the specified control.

33
src/Avalonia.Controls/NumericUpDown/NumericUpDown.cs

@ -6,6 +6,7 @@ using Avalonia.Controls.Primitives;
using Avalonia.Data;
using Avalonia.Input;
using Avalonia.Interactivity;
using Avalonia.Layout;
using Avalonia.Threading;
using Avalonia.Utilities;
@ -105,6 +106,19 @@ namespace Avalonia.Controls
public static readonly StyledProperty<string> WatermarkProperty =
AvaloniaProperty.Register<NumericUpDown, string>(nameof(Watermark));
/// <summary>
/// Defines the <see cref="HorizontalContentAlignment"/> property.
/// </summary>
public static readonly StyledProperty<HorizontalAlignment> HorizontalContentAlignmentProperty =
ContentControl.HorizontalContentAlignmentProperty.AddOwner<NumericUpDown>();
/// <summary>
/// Defines the <see cref="VerticalContentAlignment"/> property.
/// </summary>
public static readonly StyledProperty<VerticalAlignment> VerticalContentAlignmentProperty =
ContentControl.VerticalContentAlignmentProperty.AddOwner<NumericUpDown>();
private IDisposable _textBoxTextChangedSubscription;
private double _value;
@ -256,6 +270,25 @@ namespace Avalonia.Controls
set { SetValue(WatermarkProperty, value); }
}
/// <summary>
/// Gets or sets the horizontal alignment of the content within the control.
/// </summary>
public HorizontalAlignment HorizontalContentAlignment
{
get => GetValue(HorizontalContentAlignmentProperty);
set => SetValue(HorizontalContentAlignmentProperty, value);
}
/// <summary>
/// Gets or sets the vertical alignment of the content within the control.
/// </summary>
public VerticalAlignment VerticalContentAlignment
{
get => GetValue(VerticalContentAlignmentProperty);
set => SetValue(VerticalContentAlignmentProperty, value);
}
/// <summary>
/// Initializes new instance of <see cref="NumericUpDown"/> class.
/// </summary>

8
src/Avalonia.Controls/Platform/DefaultMenuInteractionHandler.cs

@ -355,7 +355,13 @@ namespace Avalonia.Controls.Platform
}
else if (!item.IsPointerOverSubMenu)
{
item.IsSubMenuOpen = false;
DelayRun(() =>
{
if (!item.IsPointerOverSubMenu)
{
item.IsSubMenuOpen = false;
}
}, MenuShowDelay);
}
}
}

11
src/Avalonia.Controls/Platform/ITopLevelImplWithTextInputMethod.cs

@ -0,0 +1,11 @@
using Avalonia.Input;
using Avalonia.Input.TextInput;
using Avalonia.Platform;
namespace Avalonia.Controls.Platform
{
public interface ITopLevelImplWithTextInputMethod : ITopLevelImpl
{
public ITextInputMethodImpl TextInputMethod { get; }
}
}

25
src/Avalonia.Controls/Presenters/TextPresenter.cs

@ -1,5 +1,6 @@
using System;
using System.Reactive.Linq;
using Avalonia.Input.TextInput;
using Avalonia.Media;
using Avalonia.Metadata;
using Avalonia.Threading;
@ -378,19 +379,23 @@ namespace Avalonia.Controls.Presenters
if (_caretBlink)
{
var charPos = FormattedText.HitTestTextPosition(CaretIndex);
var x = Math.Floor(charPos.X) + 0.5;
var y = Math.Floor(charPos.Y) + 0.5;
var b = Math.Ceiling(charPos.Bottom) - 0.5;
var (p1, p2) = GetCaretPoints();
context.DrawLine(
new Pen(caretBrush, 1),
new Point(x, y),
new Point(x, b));
p1, p2);
}
}
}
(Point, Point) GetCaretPoints()
{
var charPos = FormattedText.HitTestTextPosition(CaretIndex);
var x = Math.Floor(charPos.X) + 0.5;
var y = Math.Floor(charPos.Y) + 0.5;
var b = Math.Ceiling(charPos.Bottom) - 0.5;
return (new Point(x, y), new Point(x, b));
}
public void ShowCaret()
{
_caretBlink = true;
@ -538,5 +543,11 @@ namespace Avalonia.Controls.Presenters
_caretBlink = !_caretBlink;
InvalidateVisual();
}
internal Rect GetCursorRectangle()
{
var (p1, p2) = GetCaretPoints();
return new Rect(p1, p2);
}
}
}

6
src/Avalonia.Controls/Primitives/LightDismissOverlayLayer.cs

@ -2,6 +2,7 @@ using System;
using System.Linq;
using Avalonia.Controls.Templates;
using Avalonia.Input;
using Avalonia.Media;
using Avalonia.Rendering;
using Avalonia.Styling;
using Avalonia.VisualTree;
@ -17,6 +18,11 @@ namespace Avalonia.Controls.Primitives
{
public IInputElement? InputPassThroughElement { get; set; }
static LightDismissOverlayLayer()
{
BackgroundProperty.OverrideDefaultValue<LightDismissOverlayLayer>(Brushes.Transparent);
}
/// <summary>
/// Returns the light dismiss overlay for a specified visual.
/// </summary>

1
src/Avalonia.Controls/Primitives/VisualLayerManager.cs

@ -74,7 +74,6 @@ namespace Avalonia.Controls.Primitives
{
rv = new LightDismissOverlayLayer
{
Background = Brushes.Transparent,
IsVisible = false
};

7
src/Avalonia.Controls/TextBox.cs

@ -149,6 +149,7 @@ namespace Avalonia.Controls
private int _selectionStart;
private int _selectionEnd;
private TextPresenter _presenter;
private TextBoxTextInputMethodClient _imClient = new TextBoxTextInputMethodClient();
private UndoRedoHelper<UndoRedoState> _undoRedoHelper;
private bool _isUndoingRedoing;
private bool _ignoreTextChanges;
@ -161,6 +162,10 @@ namespace Avalonia.Controls
static TextBox()
{
FocusableProperty.OverrideDefaultValue(typeof(TextBox), true);
TextInputMethodClientRequestedEvent.AddClassHandler<TextBox>((tb, e) =>
{
e.Client = tb._imClient;
});
}
public TextBox()
@ -437,7 +442,7 @@ namespace Avalonia.Controls
protected override void OnApplyTemplate(TemplateAppliedEventArgs e)
{
_presenter = e.NameScope.Get<TextPresenter>("PART_TextPresenter");
_imClient.SetPresenter(_presenter);
if (IsFocused)
{
_presenter?.ShowCaret();

41
src/Avalonia.Controls/TextBoxTextInputMethodClient.cs

@ -0,0 +1,41 @@
using System;
using Avalonia.Controls.Presenters;
using Avalonia.Input.TextInput;
using Avalonia.VisualTree;
namespace Avalonia.Controls
{
internal class TextBoxTextInputMethodClient : ITextInputMethodClient
{
private TextPresenter _presenter;
private IDisposable _subscription;
public Rect CursorRectangle => _presenter?.GetCursorRectangle() ?? default;
public event EventHandler CursorRectangleChanged;
public IVisual TextViewVisual => _presenter;
public event EventHandler TextViewVisualChanged;
public bool SupportsPreedit => false;
public void SetPreeditText(string text) => throw new NotSupportedException();
public bool SupportsSurroundingText => false;
public TextInputMethodSurroundingText SurroundingText => throw new NotSupportedException();
public event EventHandler SurroundingTextChanged;
public string TextBeforeCursor => null;
public string TextAfterCursor => null;
private void OnCaretIndexChanged(int index) => CursorRectangleChanged?.Invoke(this, EventArgs.Empty);
public void SetPresenter(TextPresenter presenter)
{
_subscription?.Dispose();
_subscription = null;
_presenter = presenter;
if (_presenter != null)
{
_subscription = _presenter.GetObservable(TextPresenter.CaretIndexProperty)
.Subscribe(OnCaretIndexChanged);
}
TextViewVisualChanged?.Invoke(this, EventArgs.Empty);
CursorRectangleChanged?.Invoke(this, EventArgs.Empty);
}
}
}

40
src/Avalonia.Controls/ToolTip.cs

@ -1,3 +1,4 @@
#nullable enable
using System;
using System.Reactive.Linq;
using Avalonia.Controls.Metadata;
@ -21,8 +22,8 @@ namespace Avalonia.Controls
/// <summary>
/// Defines the ToolTip.Tip attached property.
/// </summary>
public static readonly AttachedProperty<object> TipProperty =
AvaloniaProperty.RegisterAttached<ToolTip, Control, object>("Tip");
public static readonly AttachedProperty<object?> TipProperty =
AvaloniaProperty.RegisterAttached<ToolTip, Control, object?>("Tip");
/// <summary>
/// Defines the ToolTip.IsOpen attached property.
@ -57,10 +58,10 @@ namespace Avalonia.Controls
/// <summary>
/// Stores the current <see cref="ToolTip"/> instance in the control.
/// </summary>
internal static readonly AttachedProperty<ToolTip> ToolTipProperty =
AvaloniaProperty.RegisterAttached<ToolTip, Control, ToolTip>("ToolTip");
internal static readonly AttachedProperty<ToolTip?> ToolTipProperty =
AvaloniaProperty.RegisterAttached<ToolTip, Control, ToolTip?>("ToolTip");
private IPopupHost _popup;
private IPopupHost? _popup;
/// <summary>
/// Initializes static members of the <see cref="ToolTip"/> class.
@ -70,6 +71,10 @@ namespace Avalonia.Controls
TipProperty.Changed.Subscribe(ToolTipService.Instance.TipChanged);
IsOpenProperty.Changed.Subscribe(ToolTipService.Instance.TipOpenChanged);
IsOpenProperty.Changed.Subscribe(IsOpenChanged);
HorizontalOffsetProperty.Changed.Subscribe(RecalculatePositionOnPropertyChanged);
VerticalOffsetProperty.Changed.Subscribe(RecalculatePositionOnPropertyChanged);
PlacementProperty.Changed.Subscribe(RecalculatePositionOnPropertyChanged);
}
/// <summary>
@ -79,7 +84,7 @@ namespace Avalonia.Controls
/// <returns>
/// The content to be displayed in the control's tooltip.
/// </returns>
public static object GetTip(Control element)
public static object? GetTip(Control element)
{
return element.GetValue(TipProperty);
}
@ -89,7 +94,7 @@ namespace Avalonia.Controls
/// </summary>
/// <param name="element">The control to get the property from.</param>
/// <param name="value">The content to be displayed in the control's tooltip.</param>
public static void SetTip(Control element, object value)
public static void SetTip(Control element, object? value)
{
element.SetValue(TipProperty, value);
}
@ -207,8 +212,8 @@ namespace Avalonia.Controls
private static void IsOpenChanged(AvaloniaPropertyChangedEventArgs e)
{
var control = (Control)e.Sender;
var newValue = (bool)e.NewValue;
ToolTip toolTip;
var newValue = (bool)e.NewValue!;
ToolTip? toolTip;
if (newValue)
{
@ -235,6 +240,23 @@ namespace Avalonia.Controls
toolTip?.UpdatePseudoClasses(newValue);
}
private static void RecalculatePositionOnPropertyChanged(AvaloniaPropertyChangedEventArgs args)
{
var control = (Control)args.Sender;
var tooltip = control.GetValue(ToolTipProperty);
if (tooltip == null)
{
return;
}
tooltip.RecalculatePosition(control);
}
internal void RecalculatePosition(Control control)
{
_popup?.ConfigurePosition(control, GetPlacement(control), new Point(GetHorizontalOffset(control), GetVerticalOffset(control)));
}
private void Open(Control control)
{
Close();

10
src/Avalonia.Controls/ToolTipService.cs

@ -51,10 +51,12 @@ namespace Avalonia.Controls
if (e.OldValue is false && e.NewValue is true)
{
control.DetachedFromVisualTree += ControlDetaching;
control.EffectiveViewportChanged += ControlEffectiveViewportChanged;
}
else if(e.OldValue is true && e.NewValue is false)
{
control.DetachedFromVisualTree -= ControlDetaching;
control.EffectiveViewportChanged -= ControlEffectiveViewportChanged;
}
}
@ -62,6 +64,7 @@ namespace Avalonia.Controls
{
var control = (Control)sender;
control.DetachedFromVisualTree -= ControlDetaching;
control.EffectiveViewportChanged -= ControlEffectiveViewportChanged;
Close(control);
}
@ -97,6 +100,13 @@ namespace Avalonia.Controls
Close(control);
}
private void ControlEffectiveViewportChanged(object sender, Layout.EffectiveViewportChangedEventArgs e)
{
var control = (Control)sender;
var toolTip = control.GetValue(ToolTip.ToolTipProperty);
toolTip?.RecalculatePosition(control);
}
private void StartShowTimer(int showDelay, Control control)
{
_timer = new DispatcherTimer { Interval = TimeSpan.FromMilliseconds(showDelay) };

6
src/Avalonia.Controls/TopLevel.cs

@ -1,8 +1,10 @@
using System;
using System.Reactive.Linq;
using Avalonia.Controls.Platform;
using Avalonia.Controls.Primitives;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.Layout;
using Avalonia.Logging;
using Avalonia.LogicalTree;
@ -31,6 +33,7 @@ namespace Avalonia.Controls
ICloseable,
IStyleHost,
ILogicalRoot,
ITextInputMethodRoot,
IWeakSubscriber<ResourcesChangedEventArgs>
{
/// <summary>
@ -489,5 +492,8 @@ namespace Avalonia.Controls
if (focused == this)
KeyboardDevice.Instance.SetFocusedElement(null, NavigationMethod.Unspecified, KeyModifiers.None);
}
ITextInputMethodImpl ITextInputMethodRoot.InputMethod =>
(PlatformImpl as ITopLevelImplWithTextInputMethod)?.TextInputMethod;
}
}

8
src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml

@ -79,8 +79,8 @@
<DrawingPresenter Drawing="{DynamicResource AvaloniaLogo}" />
</Border>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center" Margin="10,-10,0,0">
<TextBlock Text="Avalonia 0.9" FontSize="40" Foreground="White" />
<TextBlock Text="Development Build" Margin="0,-10,0,0" FontSize="15" Foreground="White" />
<TextBlock Text="{Binding Version, StringFormat=Avalonia {0}}" FontSize="40" Foreground="White" />
<TextBlock Text="Development Build" IsVisible="{Binding IsDevelopmentBuild}" Margin="0,-10,0,0" FontSize="15" Foreground="White" />
</StackPanel>
</StackPanel>
<StackPanel HorizontalAlignment="Stretch" VerticalAlignment="Center" Spacing="20" Margin="10 60 10 0">
@ -99,7 +99,7 @@
</StackPanel>
</StackPanel>
<StackPanel VerticalAlignment="Bottom" Margin="10">
<TextBlock Text="© 2020 The Avalonia Project" TextWrapping="Wrap" HorizontalAlignment="Center" />
<TextBlock Text="© 2021 The Avalonia Project" TextWrapping="Wrap" HorizontalAlignment="Center" />
</StackPanel>
</Grid>
</Window>
</Window>

9
src/Avalonia.Dialogs/AboutAvaloniaDialog.xaml.cs

@ -1,3 +1,4 @@
using System;
using System.Diagnostics;
using System.Runtime.InteropServices;
using Avalonia.Controls;
@ -7,12 +8,18 @@ namespace Avalonia.Dialogs
{
public class AboutAvaloniaDialog : Window
{
private static readonly Version s_version = typeof(AboutAvaloniaDialog).Assembly.GetName().Version;
public static string Version { get; } = s_version.ToString(2);
public static bool IsDevelopmentBuild { get; } = s_version.Revision == 999;
public AboutAvaloniaDialog()
{
AvaloniaXamlLoader.Load(this);
DataContext = this;
}
public static void OpenBrowser(string url)
{
if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))

110
src/Avalonia.FreeDesktop/DBusCallQueue.cs

@ -0,0 +1,110 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace Avalonia.FreeDesktop
{
class DBusCallQueue
{
private readonly Func<Exception, Task> _errorHandler;
class Item
{
public Func<Task> Callback;
public Action<Exception> OnFinish;
}
private Queue<Item> _q = new Queue<Item>();
private bool _processing;
public DBusCallQueue(Func<Exception, Task> errorHandler)
{
_errorHandler = errorHandler;
}
public void Enqueue(Func<Task> cb)
{
_q.Enqueue(new Item
{
Callback = cb
});
Process();
}
public Task EnqueueAsync(Func<Task> cb)
{
var tcs = new TaskCompletionSource<int>(TaskCreationOptions.RunContinuationsAsynchronously);
_q.Enqueue(new Item
{
Callback = cb,
OnFinish = e =>
{
if (e == null)
tcs.TrySetResult(0);
else
tcs.TrySetException(e);
}
});
Process();
return tcs.Task;
}
public Task<T> EnqueueAsync<T>(Func<Task<T>> cb)
{
var tcs = new TaskCompletionSource<T>(TaskCreationOptions.RunContinuationsAsynchronously);
_q.Enqueue(new Item
{
Callback = async () =>
{
var res = await cb();
tcs.TrySetResult(res);
},
OnFinish = e =>
{
if (e != null)
tcs.TrySetException(e);
}
});
Process();
return tcs.Task;
}
async void Process()
{
if(_processing)
return;
_processing = true;
try
{
while (_q.Count > 0)
{
var item = _q.Dequeue();
try
{
await item.Callback();
item.OnFinish?.Invoke(null);
}
catch(Exception e)
{
if (item.OnFinish != null)
item.OnFinish(e);
else
await _errorHandler(e);
}
}
}
finally
{
_processing = false;
}
}
public void FailAll()
{
while (_q.Count>0)
{
var item = _q.Dequeue();
item.OnFinish?.Invoke(new OperationCanceledException());
}
}
}
}

11
src/Avalonia.FreeDesktop/DBusHelper.cs

@ -1,5 +1,6 @@
using System;
using System.Threading;
using Avalonia.Logging;
using Avalonia.Threading;
using Tmds.DBus;
@ -48,8 +49,10 @@ namespace Avalonia.FreeDesktop
}
public static Connection Connection { get; private set; }
public static Exception TryInitialize(string dbusAddress = null)
public static Connection TryInitialize(string dbusAddress = null)
{
if (Connection != null)
return Connection;
var oldContext = SynchronizationContext.Current;
try
{
@ -70,13 +73,15 @@ namespace Avalonia.FreeDesktop
}
catch (Exception e)
{
return e;
Logger.TryGet(LogEventLevel.Error, "DBUS")
?.Log(null, "Unable to connect to DBus: " + e);
}
finally
{
SynchronizationContext.SetSynchronizationContext(oldContext);
}
return null;
return Connection;
}
}
}

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

@ -0,0 +1,288 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Reflection;
using System.Threading.Tasks;
using Avalonia.FreeDesktop.DBusIme.Fcitx;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.Logging;
using Tmds.DBus;
namespace Avalonia.FreeDesktop.DBusIme
{
internal class DBusInputMethodFactory<T> : IX11InputMethodFactory where T : ITextInputMethodImpl, IX11InputMethodControl
{
private readonly Func<IntPtr, T> _factory;
public DBusInputMethodFactory(Func<IntPtr, T> factory)
{
_factory = factory;
}
public (ITextInputMethodImpl method, IX11InputMethodControl control) CreateClient(IntPtr xid)
{
var im = _factory(xid);
return (im, im);
}
}
internal abstract class DBusTextInputMethodBase : IX11InputMethodControl, ITextInputMethodImpl
{
private List<IDisposable> _disposables = new List<IDisposable>();
private Queue<string> _onlineNamesQueue = new Queue<string>();
protected Connection Connection { get; }
private readonly string[] _knownNames;
private bool _connecting;
private string _currentName;
private DBusCallQueue _queue;
private bool _controlActive, _windowActive;
private bool? _imeActive;
private Rect _logicalRect;
private PixelRect? _lastReportedRect;
private double _scaling = 1;
private PixelPoint _windowPosition;
protected bool IsConnected => _currentName != null;
public DBusTextInputMethodBase(Connection connection, params string[] knownNames)
{
_queue = new DBusCallQueue(QueueOnError);
Connection = connection;
_knownNames = knownNames;
Watch();
}
async void Watch()
{
foreach (var name in _knownNames)
_disposables.Add(await Connection.ResolveServiceOwnerAsync(name, OnNameChange));
}
protected abstract Task<bool> Connect(string name);
protected string GetAppName() =>
Application.Current.Name ?? Assembly.GetEntryAssembly()?.GetName()?.Name ?? "Avalonia";
private async void OnNameChange(ServiceOwnerChangedEventArgs args)
{
if (args.NewOwner != null && _currentName == null)
{
_onlineNamesQueue.Enqueue(args.ServiceName);
if(!_connecting)
{
_connecting = true;
try
{
while (_onlineNamesQueue.Count > 0)
{
var name = _onlineNamesQueue.Dequeue();
try
{
if (await Connect(name))
{
_onlineNamesQueue.Clear();
_currentName = name;
return;
}
}
catch (Exception e)
{
Logger.TryGet(LogEventLevel.Error, "IME")
?.Log(this, "Unable to create IME input context:\n" + e);
}
}
}
finally
{
_connecting = false;
}
}
}
// IME has crashed
if (args.NewOwner == null && args.ServiceName == _currentName)
{
_currentName = null;
foreach(var s in _disposables)
s.Dispose();
_disposables.Clear();
OnDisconnected();
Reset();
// Watch again
Watch();
}
}
protected virtual Task Disconnect()
{
return Task.CompletedTask;
}
protected virtual void OnDisconnected()
{
}
protected virtual void Reset()
{
_lastReportedRect = null;
_imeActive = null;
}
async Task QueueOnError(Exception e)
{
Logger.TryGet(LogEventLevel.Error, "IME")
?.Log(this, "Error:\n" + e);
try
{
await Disconnect();
}
catch (Exception ex)
{
Logger.TryGet(LogEventLevel.Error, "IME")
?.Log(this, "Error while destroying the context:\n" + ex);
}
OnDisconnected();
_currentName = null;
}
protected void Enqueue(Func<Task> cb) => _queue.Enqueue(cb);
protected void AddDisposable(IDisposable d) => _disposables.Add(d);
public void Dispose()
{
foreach(var d in _disposables)
d.Dispose();
_disposables.Clear();
try
{
Disconnect().ContinueWith(_ => { });
}
catch
{
// fire and forget
}
_currentName = null;
}
protected abstract Task SetCursorRectCore(PixelRect rect);
protected abstract Task SetActiveCore(bool active);
protected abstract Task ResetContextCore();
protected abstract Task<bool> HandleKeyCore(RawKeyEventArgs args, int keyVal, int keyCode);
void UpdateActive()
{
_queue.Enqueue(async () =>
{
if(!IsConnected)
return;
var active = _windowActive && _controlActive;
if (active != _imeActive)
{
_imeActive = active;
await SetActiveCore(active);
}
});
}
void IX11InputMethodControl.SetWindowActive(bool active)
{
_windowActive = active;
UpdateActive();
}
void ITextInputMethodImpl.SetActive(bool active)
{
_controlActive = active;
UpdateActive();
}
bool IX11InputMethodControl.IsEnabled => IsConnected && _imeActive == true;
async ValueTask<bool> IX11InputMethodControl.HandleEventAsync(RawKeyEventArgs args, int keyVal, int keyCode)
{
try
{
return await _queue.EnqueueAsync(async () => await HandleKeyCore(args, keyVal, keyCode));
}
// Disconnected
catch (OperationCanceledException)
{
return false;
}
// Error, disconnect
catch (Exception e)
{
await QueueOnError(e);
return false;
}
}
private Action<string> _onCommit;
event Action<string> IX11InputMethodControl.Commit
{
add => _onCommit += value;
remove => _onCommit -= value;
}
protected void FireCommit(string s) => _onCommit?.Invoke(s);
private Action<X11InputMethodForwardedKey> _onForward;
event Action<X11InputMethodForwardedKey> IX11InputMethodControl.ForwardKey
{
add => _onForward += value;
remove => _onForward -= value;
}
protected void FireForward(X11InputMethodForwardedKey k) => _onForward?.Invoke(k);
void UpdateCursorRect()
{
_queue.Enqueue(async () =>
{
if(!IsConnected)
return;
var cursorRect = PixelRect.FromRect(_logicalRect, _scaling);
cursorRect = cursorRect.Translate(_windowPosition);
if (cursorRect != _lastReportedRect)
{
_lastReportedRect = cursorRect;
await SetCursorRectCore(cursorRect);
}
});
}
void IX11InputMethodControl.UpdateWindowInfo(PixelPoint position, double scaling)
{
_windowPosition = position;
_scaling = scaling;
UpdateCursorRect();
}
void ITextInputMethodImpl.SetCursorRect(Rect rect)
{
_logicalRect = rect;
UpdateCursorRect();
}
public abstract void SetOptions(TextInputOptionsQueryEventArgs options);
void ITextInputMethodImpl.Reset()
{
Reset();
_queue.Enqueue(async () =>
{
if (!IsConnected)
return;
await ResetContextCore();
});
}
}
}

69
src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxDBus.cs

@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Threading.Tasks;
using Tmds.DBus;
[assembly: InternalsVisibleTo(Tmds.DBus.Connection.DynamicAssemblyName)]
namespace Avalonia.FreeDesktop.DBusIme.Fcitx
{
[DBusInterface("org.fcitx.Fcitx.InputMethod")]
interface IFcitxInputMethod : IDBusObject
{
Task<(int icid, bool enable, uint keyval1, uint state1, uint keyval2, uint state2)> CreateICv3Async(
string Appname, int Pid);
}
[DBusInterface("org.fcitx.Fcitx.InputContext")]
interface IFcitxInputContext : IDBusObject
{
Task EnableICAsync();
Task CloseICAsync();
Task FocusInAsync();
Task FocusOutAsync();
Task ResetAsync();
Task MouseEventAsync(int X);
Task SetCursorLocationAsync(int X, int Y);
Task SetCursorRectAsync(int X, int Y, int W, int H);
Task SetCapacityAsync(uint Caps);
Task SetSurroundingTextAsync(string Text, uint Cursor, uint Anchor);
Task SetSurroundingTextPositionAsync(uint Cursor, uint Anchor);
Task DestroyICAsync();
Task<int> ProcessKeyEventAsync(uint Keyval, uint Keycode, uint State, int Type, uint Time);
Task<IDisposable> WatchEnableIMAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchCloseIMAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchCommitStringAsync(Action<string> handler, Action<Exception> onError = null);
Task<IDisposable> WatchCurrentIMAsync(Action<(string name, string uniqueName, string langCode)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchUpdatePreeditAsync(Action<(string str, int cursorpos)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchUpdateFormattedPreeditAsync(Action<((string, int)[] str, int cursorpos)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchUpdateClientSideUIAsync(Action<(string auxup, string auxdown, string preedit, string candidateword, string imname, int cursorpos)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchForwardKeyAsync(Action<(uint keyval, uint state, int type)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchar)> handler, Action<Exception> onError = null);
}
[DBusInterface("org.fcitx.Fcitx.InputContext1")]
interface IFcitxInputContext1 : IDBusObject
{
Task FocusInAsync();
Task FocusOutAsync();
Task ResetAsync();
Task SetCursorRectAsync(int X, int Y, int W, int H);
Task SetCapabilityAsync(ulong Caps);
Task SetSurroundingTextAsync(string Text, uint Cursor, uint Anchor);
Task SetSurroundingTextPositionAsync(uint Cursor, uint Anchor);
Task DestroyICAsync();
Task<bool> ProcessKeyEventAsync(uint Keyval, uint Keycode, uint State, bool Type, uint Time);
Task<IDisposable> WatchCommitStringAsync(Action<string> handler, Action<Exception> onError = null);
Task<IDisposable> WatchCurrentIMAsync(Action<(string name, string uniqueName, string langCode)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchUpdateFormattedPreeditAsync(Action<((string, int)[] str, int cursorpos)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchForwardKeyAsync(Action<(uint keyval, uint state, bool type)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchar)> handler, Action<Exception> onError = null);
}
[DBusInterface("org.fcitx.Fcitx.InputMethod1")]
interface IFcitxInputMethod1 : IDBusObject
{
Task<(ObjectPath path, byte[] data)> CreateInputContextAsync((string, string)[] arg0);
}
}

67
src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxEnums.cs

@ -0,0 +1,67 @@
using System;
namespace Avalonia.FreeDesktop.DBusIme.Fcitx
{
enum FcitxKeyEventType
{
FCITX_PRESS_KEY,
FCITX_RELEASE_KEY
};
[Flags]
enum FcitxCapabilityFlags
{
CAPACITY_NONE = 0,
CAPACITY_CLIENT_SIDE_UI = (1 << 0),
CAPACITY_PREEDIT = (1 << 1),
CAPACITY_CLIENT_SIDE_CONTROL_STATE = (1 << 2),
CAPACITY_PASSWORD = (1 << 3),
CAPACITY_FORMATTED_PREEDIT = (1 << 4),
CAPACITY_CLIENT_UNFOCUS_COMMIT = (1 << 5),
CAPACITY_SURROUNDING_TEXT = (1 << 6),
CAPACITY_EMAIL = (1 << 7),
CAPACITY_DIGIT = (1 << 8),
CAPACITY_UPPERCASE = (1 << 9),
CAPACITY_LOWERCASE = (1 << 10),
CAPACITY_NOAUTOUPPERCASE = (1 << 11),
CAPACITY_URL = (1 << 12),
CAPACITY_DIALABLE = (1 << 13),
CAPACITY_NUMBER = (1 << 14),
CAPACITY_NO_ON_SCREEN_KEYBOARD = (1 << 15),
CAPACITY_SPELLCHECK = (1 << 16),
CAPACITY_NO_SPELLCHECK = (1 << 17),
CAPACITY_WORD_COMPLETION = (1 << 18),
CAPACITY_UPPERCASE_WORDS = (1 << 19),
CAPACITY_UPPERCASE_SENTENCES = (1 << 20),
CAPACITY_ALPHA = (1 << 21),
CAPACITY_NAME = (1 << 22),
CAPACITY_GET_IM_INFO_ON_FOCUS = (1 << 23),
CAPACITY_RELATIVE_CURSOR_RECT = (1 << 24),
};
[Flags]
enum FcitxKeyState
{
FcitxKeyState_None = 0,
FcitxKeyState_Shift = 1 << 0,
FcitxKeyState_CapsLock = 1 << 1,
FcitxKeyState_Ctrl = 1 << 2,
FcitxKeyState_Alt = 1 << 3,
FcitxKeyState_Alt_Shift = FcitxKeyState_Alt | FcitxKeyState_Shift,
FcitxKeyState_Ctrl_Shift = FcitxKeyState_Ctrl | FcitxKeyState_Shift,
FcitxKeyState_Ctrl_Alt = FcitxKeyState_Ctrl | FcitxKeyState_Alt,
FcitxKeyState_Ctrl_Alt_Shift =
FcitxKeyState_Ctrl | FcitxKeyState_Alt | FcitxKeyState_Shift,
FcitxKeyState_NumLock = 1 << 4,
FcitxKeyState_Super = 1 << 6,
FcitxKeyState_ScrollLock = 1 << 7,
FcitxKeyState_MousePressed = 1 << 8,
FcitxKeyState_HandledMask = 1 << 24,
FcitxKeyState_IgnoredMask = 1 << 25,
FcitxKeyState_Super2 = 1 << 26,
FcitxKeyState_Hyper = 1 << 27,
FcitxKeyState_Meta = 1 << 28,
FcitxKeyState_UsedMask = 0x5c001fff
};
}

51
src/Avalonia.FreeDesktop/DBusIme/Fcitx/FcitxICWrapper.cs

@ -0,0 +1,51 @@
using System;
using System.Threading.Tasks;
namespace Avalonia.FreeDesktop.DBusIme.Fcitx
{
internal class FcitxICWrapper
{
private readonly IFcitxInputContext1 _modern;
private readonly IFcitxInputContext _old;
public FcitxICWrapper(IFcitxInputContext old)
{
_old = old;
}
public FcitxICWrapper(IFcitxInputContext1 modern)
{
_modern = modern;
}
public Task FocusInAsync() => _old?.FocusInAsync() ?? _modern.FocusInAsync();
public Task FocusOutAsync() => _old?.FocusOutAsync() ?? _modern.FocusOutAsync();
public Task ResetAsync() => _old?.ResetAsync() ?? _modern.ResetAsync();
public Task SetCursorRectAsync(int x, int y, int w, int h) =>
_old?.SetCursorRectAsync(x, y, w, h) ?? _modern.SetCursorRectAsync(x, y, w, h);
public Task DestroyICAsync() => _old?.DestroyICAsync() ?? _modern.DestroyICAsync();
public async Task<bool> ProcessKeyEventAsync(uint keyVal, uint keyCode, uint state, int type, uint time)
{
if(_old!=null)
return await _old.ProcessKeyEventAsync(keyVal, keyCode, state, type, time) != 0;
return await _modern.ProcessKeyEventAsync(keyVal, keyCode, state, type > 0, time);
}
public Task<IDisposable> WatchCommitStringAsync(Action<string> handler) =>
_old?.WatchCommitStringAsync(handler) ?? _modern.WatchCommitStringAsync(handler);
public Task<IDisposable> WatchForwardKeyAsync(Action<(uint keyval, uint state, int type)> handler)
{
return _old?.WatchForwardKeyAsync(handler)
?? _modern.WatchForwardKeyAsync(ev =>
handler((ev.keyval, ev.state, ev.type ? 1 : 0)));
}
public Task SetCapacityAsync(uint flags) =>
_old?.SetCapacityAsync(flags) ?? _modern.SetCapabilityAsync(flags);
}
}

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

@ -0,0 +1,149 @@
using System;
using System.Diagnostics;
using System.Reactive.Concurrency;
using System.Reflection;
using System.Threading.Tasks;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Tmds.DBus;
namespace Avalonia.FreeDesktop.DBusIme.Fcitx
{
internal class FcitxX11TextInputMethod : DBusTextInputMethodBase
{
private FcitxICWrapper _context;
private FcitxCapabilityFlags? _lastReportedFlags;
public FcitxX11TextInputMethod(Connection connection) : base(connection,
"org.fcitx.Fcitx",
"org.freedesktop.portal.Fcitx"
)
{
}
protected override async Task<bool> Connect(string name)
{
if (name == "org.fcitx.Fcitx")
{
var method = Connection.CreateProxy<IFcitxInputMethod>(name, "/inputmethod");
var resp = await method.CreateICv3Async(GetAppName(),
Process.GetCurrentProcess().Id);
var proxy = Connection.CreateProxy<IFcitxInputContext>(name,
"/inputcontext_" + resp.icid);
_context = new FcitxICWrapper(proxy);
}
else
{
var method = Connection.CreateProxy<IFcitxInputMethod1>(name, "/inputmethod");
var resp = await method.CreateInputContextAsync(new[] { ("appName", GetAppName()) });
var proxy = Connection.CreateProxy<IFcitxInputContext1>(name, resp.path);
_context = new FcitxICWrapper(proxy);
}
AddDisposable(await _context.WatchCommitStringAsync(OnCommitString));
AddDisposable(await _context.WatchForwardKeyAsync(OnForward));
return true;
}
protected override Task Disconnect() => _context.DestroyICAsync();
protected override void OnDisconnected() => _context = null;
protected override void Reset()
{
_lastReportedFlags = null;
base.Reset();
}
protected override Task SetCursorRectCore(PixelRect cursorRect) =>
_context.SetCursorRectAsync(cursorRect.X, cursorRect.Y, Math.Max(1, cursorRect.Width),
Math.Max(1, cursorRect.Height));
protected override Task SetActiveCore(bool active)
{
if (active)
return _context.FocusInAsync();
else
return _context.FocusOutAsync();
}
protected override Task ResetContextCore() => _context.ResetAsync();
protected override async Task<bool> HandleKeyCore(RawKeyEventArgs args, int keyVal, int keyCode)
{
FcitxKeyState state = default;
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Control))
state |= FcitxKeyState.FcitxKeyState_Ctrl;
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Alt))
state |= FcitxKeyState.FcitxKeyState_Alt;
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Shift))
state |= FcitxKeyState.FcitxKeyState_Shift;
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Meta))
state |= FcitxKeyState.FcitxKeyState_Super;
var type = args.Type == RawKeyEventType.KeyDown ?
FcitxKeyEventType.FCITX_PRESS_KEY :
FcitxKeyEventType.FCITX_RELEASE_KEY;
return await _context.ProcessKeyEventAsync((uint)keyVal, (uint)keyCode, (uint)state, (int)type,
(uint)args.Timestamp).ConfigureAwait(false);
}
public override void SetOptions(TextInputOptionsQueryEventArgs options) =>
Enqueue(async () =>
{
if(_context == null)
return;
FcitxCapabilityFlags flags = default;
if (options.Lowercase)
flags |= FcitxCapabilityFlags.CAPACITY_LOWERCASE;
if (options.Uppercase)
flags |= FcitxCapabilityFlags.CAPACITY_UPPERCASE;
if (!options.AutoCapitalization)
flags |= FcitxCapabilityFlags.CAPACITY_NOAUTOUPPERCASE;
if (options.ContentType == TextInputContentType.Email)
flags |= FcitxCapabilityFlags.CAPACITY_EMAIL;
else if (options.ContentType == TextInputContentType.Number)
flags |= FcitxCapabilityFlags.CAPACITY_NUMBER;
else if (options.ContentType == TextInputContentType.Password)
flags |= FcitxCapabilityFlags.CAPACITY_PASSWORD;
else if (options.ContentType == TextInputContentType.Phone)
flags |= FcitxCapabilityFlags.CAPACITY_DIALABLE;
else if (options.ContentType == TextInputContentType.Url)
flags |= FcitxCapabilityFlags.CAPACITY_URL;
if (flags != _lastReportedFlags)
{
_lastReportedFlags = flags;
await _context.SetCapacityAsync((uint)flags);
}
});
private void OnForward((uint keyval, uint state, int type) ev)
{
var state = (FcitxKeyState)ev.state;
KeyModifiers mods = default;
if (state.HasFlagCustom(FcitxKeyState.FcitxKeyState_Ctrl))
mods |= KeyModifiers.Control;
if (state.HasFlagCustom(FcitxKeyState.FcitxKeyState_Alt))
mods |= KeyModifiers.Alt;
if (state.HasFlagCustom(FcitxKeyState.FcitxKeyState_Shift))
mods |= KeyModifiers.Shift;
if (state.HasFlagCustom(FcitxKeyState.FcitxKeyState_Super))
mods |= KeyModifiers.Meta;
FireForward(new X11InputMethodForwardedKey
{
Modifiers = mods,
KeyVal = (int)ev.keyval,
Type = ev.type == (int)FcitxKeyEventType.FCITX_PRESS_KEY ?
RawKeyEventType.KeyDown :
RawKeyEventType.KeyUp
});
}
private void OnCommitString(string s) => FireCommit(s);
}
}

52
src/Avalonia.FreeDesktop/DBusIme/IBus/IBusDBus.cs

@ -0,0 +1,52 @@
using System;
using System.Collections.Generic;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Threading.Tasks;
using Tmds.DBus;
[assembly: InternalsVisibleTo(Connection.DynamicAssemblyName)]
namespace Avalonia.FreeDesktop.DBusIme.IBus
{
[DBusInterface("org.freedesktop.IBus.InputContext")]
interface IIBusInputContext : IDBusObject
{
Task<bool> ProcessKeyEventAsync(uint Keyval, uint Keycode, uint State);
Task SetCursorLocationAsync(int X, int Y, int W, int H);
Task FocusInAsync();
Task FocusOutAsync();
Task ResetAsync();
Task SetCapabilitiesAsync(uint Caps);
Task PropertyActivateAsync(string Name, int State);
Task SetEngineAsync(string Name);
Task<object> GetEngineAsync();
Task DestroyAsync();
Task SetSurroundingTextAsync(object Text, uint CursorPos, uint AnchorPos);
Task<IDisposable> WatchCommitTextAsync(Action<object> cb, Action<Exception> onError = null);
Task<IDisposable> WatchForwardKeyEventAsync(Action<(uint keyval, uint keycode, uint state)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchRequireSurroundingTextAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchDeleteSurroundingTextAsync(Action<(int offset, uint nchars)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchUpdatePreeditTextAsync(Action<(object text, uint cursorPos, bool visible)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchShowPreeditTextAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchHidePreeditTextAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchUpdateAuxiliaryTextAsync(Action<(object text, bool visible)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchShowAuxiliaryTextAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchHideAuxiliaryTextAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchUpdateLookupTableAsync(Action<(object table, bool visible)> handler, Action<Exception> onError = null);
Task<IDisposable> WatchShowLookupTableAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchHideLookupTableAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchPageUpLookupTableAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchPageDownLookupTableAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchCursorUpLookupTableAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchCursorDownLookupTableAsync(Action handler, Action<Exception> onError = null);
Task<IDisposable> WatchRegisterPropertiesAsync(Action<object> handler, Action<Exception> onError = null);
Task<IDisposable> WatchUpdatePropertyAsync(Action<object> handler, Action<Exception> onError = null);
}
[DBusInterface("org.freedesktop.IBus.Portal")]
interface IIBusPortal : IDBusObject
{
Task<ObjectPath> CreateInputContextAsync(string Name);
}
}

45
src/Avalonia.FreeDesktop/DBusIme/IBus/IBusEnums.cs

@ -0,0 +1,45 @@
using System;
namespace Avalonia.FreeDesktop.DBusIme.IBus
{
[Flags]
internal enum IBusModifierMask
{
ShiftMask = 1 << 0,
LockMask = 1 << 1,
ControlMask = 1 << 2,
Mod1Mask = 1 << 3,
Mod2Mask = 1 << 4,
Mod3Mask = 1 << 5,
Mod4Mask = 1 << 6,
Mod5Mask = 1 << 7,
Button1Mask = 1 << 8,
Button2Mask = 1 << 9,
Button3Mask = 1 << 10,
Button4Mask = 1 << 11,
Button5Mask = 1 << 12,
HandledMask = 1 << 24,
ForwardMask = 1 << 25,
IgnoredMask = ForwardMask,
SuperMask = 1 << 26,
HyperMask = 1 << 27,
MetaMask = 1 << 28,
ReleaseMask = 1 << 30,
ModifierMask = 0x5c001fff
}
[Flags]
internal enum IBusCapability
{
CapPreeditText = 1 << 0,
CapAuxiliaryText = 1 << 1,
CapLookupTable = 1 << 2,
CapFocus = 1 << 3,
CapProperty = 1 << 4,
CapSurroundingText = 1 << 5,
}
}

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

@ -0,0 +1,105 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Tmds.DBus;
namespace Avalonia.FreeDesktop.DBusIme.IBus
{
internal class IBusX11TextInputMethod : DBusTextInputMethodBase
{
private IIBusInputContext _context;
public IBusX11TextInputMethod(Connection connection) : base(connection,
"org.freedesktop.portal.IBus")
{
}
protected override async Task<bool> Connect(string name)
{
var path =
await Connection.CreateProxy<IIBusPortal>(name, "/org/freedesktop/IBus")
.CreateInputContextAsync(GetAppName());
_context = Connection.CreateProxy<IIBusInputContext>(name, path);
AddDisposable(await _context.WatchCommitTextAsync(OnCommitText));
AddDisposable(await _context.WatchForwardKeyEventAsync(OnForwardKey));
Enqueue(() => _context.SetCapabilitiesAsync((uint)IBusCapability.CapFocus));
return true;
}
private void OnForwardKey((uint keyval, uint keycode, uint state) k)
{
var state = (IBusModifierMask)k.state;
KeyModifiers mods = default;
if (state.HasFlagCustom(IBusModifierMask.ControlMask))
mods |= KeyModifiers.Control;
if (state.HasFlagCustom(IBusModifierMask.Mod1Mask))
mods |= KeyModifiers.Alt;
if (state.HasFlagCustom(IBusModifierMask.ShiftMask))
mods |= KeyModifiers.Shift;
if (state.HasFlagCustom(IBusModifierMask.Mod4Mask))
mods |= KeyModifiers.Meta;
FireForward(new X11InputMethodForwardedKey
{
KeyVal = (int)k.keyval,
Type = state.HasFlagCustom(IBusModifierMask.ReleaseMask) ? RawKeyEventType.KeyUp : RawKeyEventType.KeyDown,
Modifiers = mods
});
}
private void OnCommitText(object wtf)
{
// Hello darkness, my old friend
var prop = wtf.GetType().GetField("Item3");
if (prop != null)
{
var text = (string)prop.GetValue(wtf);
if (!string.IsNullOrEmpty(text))
FireCommit(text);
}
}
protected override Task Disconnect() => _context.DestroyAsync();
protected override void OnDisconnected()
{
_context = null;
base.OnDisconnected();
}
protected override Task SetCursorRectCore(PixelRect rect)
=> _context.SetCursorLocationAsync(rect.X, rect.Y, rect.Width, rect.Height);
protected override Task SetActiveCore(bool active)
=> active ? _context.FocusInAsync() : _context.FocusOutAsync();
protected override Task ResetContextCore()
=> _context.ResetAsync();
protected override Task<bool> HandleKeyCore(RawKeyEventArgs args, int keyVal, int keyCode)
{
IBusModifierMask state = default;
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Control))
state |= IBusModifierMask.ControlMask;
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Alt))
state |= IBusModifierMask.Mod1Mask;
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Shift))
state |= IBusModifierMask.ShiftMask;
if (args.Modifiers.HasFlagCustom(RawInputModifiers.Meta))
state |= IBusModifierMask.Mod4Mask;
if (args.Type == RawKeyEventType.KeyUp)
state |= IBusModifierMask.ReleaseMask;
return _context.ProcessKeyEventAsync((uint)keyVal, (uint)keyCode, (uint)state);
}
public override void SetOptions(TextInputOptionsQueryEventArgs options)
{
// No-op, because ibus
}
}
}

53
src/Avalonia.FreeDesktop/DBusIme/X11DBusImeHelper.cs

@ -0,0 +1,53 @@
using System;
using System.Collections.Generic;
using Avalonia.FreeDesktop.DBusIme.Fcitx;
using Avalonia.FreeDesktop.DBusIme.IBus;
using Tmds.DBus;
namespace Avalonia.FreeDesktop.DBusIme
{
public class X11DBusImeHelper
{
private static readonly Dictionary<string, Func<Connection, IX11InputMethodFactory>> KnownMethods =
new Dictionary<string, Func<Connection, IX11InputMethodFactory>>
{
["fcitx"] = conn =>
new DBusInputMethodFactory<FcitxX11TextInputMethod>(_ => new FcitxX11TextInputMethod(conn)),
["ibus"] = conn =>
new DBusInputMethodFactory<IBusX11TextInputMethod>(_ => new IBusX11TextInputMethod(conn))
};
static Func<Connection, IX11InputMethodFactory> DetectInputMethod()
{
foreach (var name in new[] { "AVALONIA_IM_MODULE", "GTK_IM_MODULE", "QT_IM_MODULE" })
{
var value = Environment.GetEnvironmentVariable(name);
if (value == "none")
return null;
if (value != null && KnownMethods.TryGetValue(value, out var factory))
return factory;
}
return null;
}
public static bool DetectAndRegister()
{
var factory = DetectInputMethod();
if (factory != null)
{
var conn = DBusHelper.TryInitialize();
if (conn != null)
{
AvaloniaLocator.CurrentMutable.Bind<IX11InputMethodFactory>().ToConstant(factory(conn));
return true;
}
}
return false;
}
}
}

31
src/Avalonia.FreeDesktop/IX11InputMethod.cs

@ -0,0 +1,31 @@
using System;
using System.Threading.Tasks;
using Avalonia.Input;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
namespace Avalonia.FreeDesktop
{
public interface IX11InputMethodFactory
{
(ITextInputMethodImpl method, IX11InputMethodControl control) CreateClient(IntPtr xid);
}
public struct X11InputMethodForwardedKey
{
public int KeyVal { get; set; }
public KeyModifiers Modifiers { get; set; }
public RawKeyEventType Type { get; set; }
}
public interface IX11InputMethodControl : IDisposable
{
void SetWindowActive(bool active);
bool IsEnabled { get; }
ValueTask<bool> HandleEventAsync(RawKeyEventArgs args, int keyVal, int keyCode);
event Action<string> Commit;
event Action<X11InputMethodForwardedKey> ForwardKey;
void UpdateWindowInfo(PixelPoint position, double scaling);
}
}

35
src/Avalonia.Input/InputElement.cs

@ -5,6 +5,7 @@ using Avalonia.Controls;
using Avalonia.Controls.Metadata;
using Avalonia.Data;
using Avalonia.Input.GestureRecognizers;
using Avalonia.Input.TextInput;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
@ -103,6 +104,22 @@ namespace Avalonia.Input
RoutedEvent.Register<InputElement, TextInputEventArgs>(
"TextInput",
RoutingStrategies.Tunnel | RoutingStrategies.Bubble);
/// <summary>
/// Defines the <see cref="TextInputMethodClientRequested"/> event.
/// </summary>
public static readonly RoutedEvent<TextInputMethodClientRequestedEventArgs> TextInputMethodClientRequestedEvent =
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.
@ -243,6 +260,24 @@ namespace Avalonia.Input
add { AddHandler(TextInputEvent, value); }
remove { RemoveHandler(TextInputEvent, value); }
}
/// <summary>
/// Occurs when an input element gains input focus and input method is looking for the corresponding client
/// </summary>
public event EventHandler<TextInputMethodClientRequestedEventArgs> TextInputMethodClientRequested
{
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.

6
src/Avalonia.Input/KeyboardDevice.cs

@ -1,6 +1,7 @@
using System.ComponentModel;
using System.Runtime.CompilerServices;
using Avalonia.Input.Raw;
using Avalonia.Input.TextInput;
using Avalonia.Interactivity;
using Avalonia.VisualTree;
@ -18,6 +19,10 @@ namespace Avalonia.Input
public IInputManager InputManager => AvaloniaLocator.Current.GetService<IInputManager>();
public IFocusManager FocusManager => AvaloniaLocator.Current.GetService<IFocusManager>();
// This should live in the FocusManager, but with the current outdated architecture
// the source of truth about the input focus is in KeyboardDevice
private readonly TextInputMethodManager _textInputManager = new TextInputMethodManager();
public IInputElement? FocusedElement
{
@ -40,6 +45,7 @@ namespace Avalonia.Input
}
RaisePropertyChanged();
_textInputManager.SetFocusedElement(value);
}
}

60
src/Avalonia.Input/TextInput/ITextInputMethodClient.cs

@ -0,0 +1,60 @@
using System;
using Avalonia.VisualTree;
namespace Avalonia.Input.TextInput
{
public interface ITextInputMethodClient
{
/// <summary>
/// The cursor rectangle relative to the TextViewVisual
/// </summary>
Rect CursorRectangle { get; }
/// <summary>
/// Should be fired when cursor rectangle is changed inside the TextViewVisual
/// </summary>
event EventHandler CursorRectangleChanged;
/// <summary>
/// The visual that's showing the text
/// </summary>
IVisual TextViewVisual { get; }
/// <summary>
/// Should be fired when text-hosting visual is changed
/// </summary>
event EventHandler TextViewVisualChanged;
/// <summary>
/// Indicates if TextViewVisual is capable of displaying non-commited input on the cursor position
/// </summary>
bool SupportsPreedit { get; }
/// <summary>
/// Sets the non-commited input string
/// </summary>
void SetPreeditText(string text);
/// <summary>
/// Indicates if text input client is capable of providing the text around the cursor
/// </summary>
bool SupportsSurroundingText { get; }
/// <summary>
/// Returns the text around the cursor, usually the current paragraph, the cursor position inside that text and selection start position
/// </summary>
TextInputMethodSurroundingText SurroundingText { get; }
/// <summary>
/// Should be fired when surrounding text changed
/// </summary>
event EventHandler SurroundingTextChanged;
/// <summary>
/// Returns the text before the cursor. Must return a non-empty string if cursor is not at the beginning of the text entry
/// </summary>
string TextBeforeCursor { get; }
/// <summary>
/// Returns the text before the cursor. Must return a non-empty string if cursor is not at the end of the text entry
/// </summary>
string TextAfterCursor { get; }
}
public struct TextInputMethodSurroundingText
{
public string Text { get; set; }
public int CursorOffset { get; set; }
public int AnchorOffset { get; set; }
}
}

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

@ -0,0 +1,15 @@
namespace Avalonia.Input.TextInput
{
public interface ITextInputMethodImpl
{
void SetActive(bool active);
void SetCursorRect(Rect rect);
void SetOptions(TextInputOptionsQueryEventArgs options);
void Reset();
}
public interface ITextInputMethodRoot : IInputRoot
{
ITextInputMethodImpl InputMethod { get; }
}
}

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

@ -0,0 +1,101 @@
using System;
using Avalonia.VisualTree;
namespace Avalonia.Input.TextInput
{
internal class TextInputMethodManager
{
private ITextInputMethodImpl? _im;
private IInputElement? _focusedElement;
private ITextInputMethodClient? _client;
private readonly TransformTrackingHelper _transformTracker = new TransformTrackingHelper();
public TextInputMethodManager() => _transformTracker.MatrixChanged += UpdateCursorRect;
private ITextInputMethodClient? Client
{
get => _client;
set
{
if(_client == value)
return;
if (_client != null)
{
_client.CursorRectangleChanged -= OnCursorRectangleChanged;
_client.TextViewVisualChanged -= OnTextViewVisualChanged;
}
_client = value;
if (_client != null)
{
_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);
UpdateCursorRect();
_im?.SetActive(true);
}
else
{
_im?.SetActive(false);
_transformTracker.SetVisual(null);
}
}
}
private void OnTextViewVisualChanged(object sender, EventArgs e)
=> _transformTracker.SetVisual(_client?.TextViewVisual);
private void UpdateCursorRect()
{
if (_im == null || _client == null || _focusedElement?.VisualRoot == null)
return;
var transform = _focusedElement.TransformToVisual(_focusedElement.VisualRoot);
if (transform == null)
_im.SetCursorRect(default);
else
_im.SetCursorRect(_client.CursorRectangle.TransformToAABB(transform.Value));
}
private void OnCursorRectangleChanged(object sender, EventArgs e)
{
if (sender == _client)
UpdateCursorRect();
}
public void SetFocusedElement(IInputElement? element)
{
if(_focusedElement == element)
return;
_focusedElement = element;
var inputMethod = (element?.VisualRoot as ITextInputMethodRoot)?.InputMethod;
if(_im != inputMethod)
_im?.SetActive(false);
_im = inputMethod;
if (_focusedElement == null || _im == null)
{
Client = null;
_im?.SetActive(false);
return;
}
var clientQuery = new TextInputMethodClientRequestedEventArgs
{
RoutedEvent = InputElement.TextInputMethodClientRequestedEvent
};
_focusedElement.RaiseEvent(clientQuery);
Client = clientQuery.Client;
}
}
}

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

@ -0,0 +1,12 @@
namespace Avalonia.Input.TextInput
{
public enum TextInputContentType
{
Normal = 0,
Email = 1,
Phone = 2,
Number = 3,
Url = 4,
Password = 5
}
}

12
src/Avalonia.Input/TextInput/TextInputMethodClientRequestedEventArgs.cs

@ -0,0 +1,12 @@
using Avalonia.Interactivity;
namespace Avalonia.Input.TextInput
{
public class TextInputMethodClientRequestedEventArgs : RoutedEventArgs
{
/// <summary>
/// Set this property to a valid text input client to enable input method interaction
/// </summary>
public ITextInputMethodClient? Client { get; set; }
}
}

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

@ -0,0 +1,32 @@
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; }
}
}

109
src/Avalonia.Input/TextInput/TransformTrackingHelper.cs

@ -0,0 +1,109 @@
using System;
using System.Collections.Generic;
using Avalonia.Threading;
using Avalonia.VisualTree;
namespace Avalonia.Input.TextInput
{
class TransformTrackingHelper : IDisposable
{
private IVisual? _visual;
private bool _queuedForUpdate;
private readonly EventHandler<AvaloniaPropertyChangedEventArgs> _propertyChangedHandler;
private readonly List<Visual> _propertyChangedSubscriptions = new List<Visual>();
public TransformTrackingHelper()
{
_propertyChangedHandler = PropertyChangedHandler;
}
public void SetVisual(IVisual? visual)
{
Dispose();
_visual = visual;
if (visual != null)
{
visual.AttachedToVisualTree += OnAttachedToVisualTree;
visual.DetachedFromVisualTree -= OnDetachedFromVisualTree;
if (visual.IsAttachedToVisualTree)
SubscribeToParents();
UpdateMatrix();
}
}
public Matrix? Matrix { get; private set; }
public event Action? MatrixChanged;
public void Dispose()
{
if(_visual == null)
return;
UnsubscribeFromParents();
_visual.AttachedToVisualTree -= OnAttachedToVisualTree;
_visual.DetachedFromVisualTree -= OnDetachedFromVisualTree;
_visual = null;
}
private void SubscribeToParents()
{
var visual = _visual;
// ReSharper disable once ConditionIsAlwaysTrueOrFalse
// false positive
while (visual != null)
{
if (visual is Visual v)
{
v.PropertyChanged += _propertyChangedHandler;
_propertyChangedSubscriptions.Add(v);
}
visual = visual.VisualParent;
}
}
private void UnsubscribeFromParents()
{
foreach (var v in _propertyChangedSubscriptions)
v.PropertyChanged -= _propertyChangedHandler;
_propertyChangedSubscriptions.Clear();
}
void UpdateMatrix()
{
Matrix? matrix = null;
if (_visual != null && _visual.VisualRoot != null)
matrix = _visual.TransformToVisual(_visual.VisualRoot);
if (Matrix != matrix)
{
Matrix = matrix;
MatrixChanged?.Invoke();
}
}
private void OnAttachedToVisualTree(object sender, VisualTreeAttachmentEventArgs visualTreeAttachmentEventArgs)
{
SubscribeToParents();
UpdateMatrix();
}
private void EnqueueForUpdate()
{
if(_queuedForUpdate)
return;
_queuedForUpdate = true;
Dispatcher.UIThread.Post(UpdateMatrix, DispatcherPriority.Render);
}
private void PropertyChangedHandler(object sender, AvaloniaPropertyChangedEventArgs e)
{
if (e.IsEffectiveValueChange && e.Property == Visual.BoundsProperty)
EnqueueForUpdate();
}
private void OnDetachedFromVisualTree(object sender, VisualTreeAttachmentEventArgs visualTreeAttachmentEventArgs)
{
UnsubscribeFromParents();
UpdateMatrix();
}
}
}

87
src/Avalonia.Layout/ElementManager.cs

@ -129,7 +129,7 @@ namespace Avalonia.Layout
{
for (int i = 0; i < count; i++)
{
// Clear from the edges so that ItemsRepeater can optimize on maintaining
// Clear from the edges so that ItemsRepeater can optimize on maintaining
// realized indices without walking through all the children every time.
int index = realizedIndex == 0 ? realizedIndex + i : (realizedIndex + count - 1) - i;
var elementRef = _realizedElements[index];
@ -212,7 +212,7 @@ namespace Avalonia.Layout
public ILayoutable GetRealizedElement(int dataIndex)
{
return IsVirtualizingContext ?
GetAt(GetRealizedRangeIndexFromDataIndex(dataIndex)) :
GetAt(GetRealizedRangeIndexFromDataIndex(dataIndex)) :
_context.GetOrCreateElementAt(
dataIndex,
ElementRealizationOptions.ForceCreate | ElementRealizationOptions.SuppressAutoRecycle);
@ -252,7 +252,6 @@ namespace Avalonia.Layout
(orientation == ScrollOrientation.Vertical ? ScrollOrientation.Horizontal : ScrollOrientation.Vertical) :
orientation;
var windowStart = effectiveOrientation == ScrollOrientation.Vertical ? window.Y : window.X;
var windowEnd = effectiveOrientation == ScrollOrientation.Vertical ? window.Y + window.Height : window.X + window.Width;
var firstElementStart = effectiveOrientation == ScrollOrientation.Vertical ? firstElementBounds.Y : firstElementBounds.X;
@ -273,53 +272,53 @@ namespace Avalonia.Layout
switch (args.Action)
{
case NotifyCollectionChangedAction.Add:
{
OnItemsAdded(args.NewStartingIndex, args.NewItems.Count);
}
break;
{
OnItemsAdded(args.NewStartingIndex, args.NewItems.Count);
}
break;
case NotifyCollectionChangedAction.Replace:
{
int oldSize = args.OldItems.Count;
int newSize = args.NewItems.Count;
int oldStartIndex = args.OldStartingIndex;
int newStartIndex = args.NewStartingIndex;
if (oldSize == newSize &&
oldStartIndex == newStartIndex &&
IsDataIndexRealized(oldStartIndex) &&
IsDataIndexRealized(oldStartIndex + oldSize -1))
{
// Straight up replace of n items within the realization window.
// Removing and adding might causes us to lose the anchor causing us
// to throw away all containers and start from scratch.
// Instead, we can just clear those items and set the element to
// null (sentinel) and let the next measure get new containers for them.
var startRealizedIndex = GetRealizedRangeIndexFromDataIndex(oldStartIndex);
for (int realizedIndex = startRealizedIndex; realizedIndex < startRealizedIndex + oldSize; realizedIndex++)
int oldSize = args.OldItems.Count;
int newSize = args.NewItems.Count;
int oldStartIndex = args.OldStartingIndex;
int newStartIndex = args.NewStartingIndex;
if (oldSize == newSize &&
oldStartIndex == newStartIndex &&
IsDataIndexRealized(oldStartIndex) &&
IsDataIndexRealized(oldStartIndex + oldSize - 1))
{
var elementRef = _realizedElements[realizedIndex];
if (elementRef != null)
// Straight up replace of n items within the realization window.
// Removing and adding might causes us to lose the anchor causing us
// to throw away all containers and start from scratch.
// Instead, we can just clear those items and set the element to
// null (sentinel) and let the next measure get new containers for them.
var startRealizedIndex = GetRealizedRangeIndexFromDataIndex(oldStartIndex);
for (int realizedIndex = startRealizedIndex; realizedIndex < startRealizedIndex + oldSize; realizedIndex++)
{
_context.RecycleElement(elementRef);
_realizedElements[realizedIndex] = null;
var elementRef = _realizedElements[realizedIndex];
if (elementRef != null)
{
_context.RecycleElement(elementRef);
_realizedElements[realizedIndex] = null;
}
}
}
else
{
OnItemsRemoved(oldStartIndex, oldSize);
OnItemsAdded(newStartIndex, newSize);
}
}
else
{
OnItemsRemoved(oldStartIndex, oldSize);
OnItemsAdded(newStartIndex, newSize);
}
}
break;
break;
case NotifyCollectionChangedAction.Remove:
{
OnItemsRemoved(args.OldStartingIndex, args.OldItems.Count);
}
break;
{
OnItemsRemoved(args.OldStartingIndex, args.OldItems.Count);
}
break;
case NotifyCollectionChangedAction.Reset:
ClearRealizedRange();
@ -376,7 +375,7 @@ namespace Avalonia.Layout
int backCutoffIndex = realizedRangeSize;
for (int i = 0;
i<realizedRangeSize &&
i < realizedRangeSize &&
!Intersects(window, _realizedElementLayoutBounds[i], orientation);
++i)
{
@ -391,7 +390,7 @@ namespace Avalonia.Layout
--backCutoffIndex;
}
if (backCutoffIndex<realizedRangeSize - 1)
if (backCutoffIndex < realizedRangeSize - 1)
{
ClearRealizedRange(backCutoffIndex + 1, realizedRangeSize - backCutoffIndex - 1);
}
@ -419,14 +418,14 @@ namespace Avalonia.Layout
// to insert items.
int lastRealizedDataIndex = _firstRealizedDataIndex + GetRealizedElementCount() - 1;
int newStartingIndex = index;
if (newStartingIndex > _firstRealizedDataIndex &&
if (newStartingIndex >= _firstRealizedDataIndex &&
newStartingIndex <= lastRealizedDataIndex)
{
// Inserted within the realized range
int insertRangeStartIndex = newStartingIndex - _firstRealizedDataIndex;
for (int i = 0; i < count; i++)
{
// Insert null (sentinel) here instead of an element, that way we dont
// Insert null (sentinel) here instead of an element, that way we dont
// end up creating a lot of elements only to be thrown out in the next layout.
int insertRangeIndex = insertRangeStartIndex + i;
int dataIndex = newStartingIndex + i;

4
src/Avalonia.OpenGL/Egl/EglInterface.cs

@ -30,8 +30,10 @@ namespace Avalonia.OpenGL.Egl
static Func<string, IntPtr> Load()
{
var os = AvaloniaLocator.Current.GetService<IRuntimePlatform>().GetRuntimeInfo().OperatingSystem;
if(os == OperatingSystemType.Linux || os == OperatingSystemType.Android)
if(os == OperatingSystemType.Linux)
return Load("libEGL.so.1");
if (os == OperatingSystemType.Android)
return Load("libEGL.so");
throw new PlatformNotSupportedException();
}

2
src/Avalonia.OpenGL/GlInterface.cs

@ -128,7 +128,7 @@ namespace Avalonia.OpenGL
int dstY1,
int mask,
int filter);
[GlMinVersionEntryPoint("glBlitFramebuffer", 3, 0)]
[GlMinVersionEntryPoint("glBlitFramebuffer", 3, 0), GlOptionalEntryPoint]
public GlBlitFramebuffer BlitFramebuffer { get; }
public delegate void GlGenRenderbuffers(int count, int[] res);

2
src/Avalonia.Themes.Default/CalendarDatePicker.xaml

@ -93,6 +93,8 @@
Watermark="{TemplateBinding Watermark}"
UseFloatingWatermark="{TemplateBinding UseFloatingWatermark}"
DataValidationErrors.Errors="{TemplateBinding (DataValidationErrors.Errors)}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
Grid.Column="0"/>
<Button Name="PART_Button"

2
src/Avalonia.Themes.Default/CaptionButtons.xaml

@ -57,7 +57,7 @@
<Style Selector="CaptionButtons:maximized Panel#PART_RestoreButton Path">
<Setter Property="Data" Value="M2048 410h-410v-410h-1638v1638h410v410h1638v-1638zM1434 1434h-1229v-1229h1229v1229zM1843 1843h-1229v-205h1024v-1024h205v1229z" />
</Style>
<Style Selector="CaptionButtons Panel#PART_FullScreenButton Path">
<Style Selector="CaptionButtons Panel#PART_FullScreenButton">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="CaptionButtons Panel#PART_FullScreenButton Path">

4
src/Avalonia.Themes.Default/NumericUpDown.xaml

@ -23,6 +23,8 @@
Watermark="{TemplateBinding Watermark}"
DataValidationErrors.Errors="{TemplateBinding (DataValidationErrors.Errors)}"
IsReadOnly="{TemplateBinding IsReadOnly}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
Text="{TemplateBinding Text}"
AcceptsReturn="False"
TextWrapping="NoWrap">
@ -35,4 +37,4 @@
<Setter Property="Margin" Value="4"/>
<Setter Property="MinWidth" Value="20"/>
</Style>
</Styles>
</Styles>

3
src/Avalonia.Themes.Fluent/Controls/Button.xaml

@ -16,8 +16,7 @@
<Setter Property="Foreground" Value="{DynamicResource ButtonForeground}" />
<Setter Property="BorderBrush" Value="{DynamicResource ButtonBorderBrush}" />
<Setter Property="BorderThickness" Value="{DynamicResource ButtonBorderThemeThickness}" />
<Setter Property="Padding" Value="8,5,8,5" />
<Setter Property="Padding" Value="{StaticResource ButtonPadding}" />
<Setter Property="Padding" Value="{DynamicResource ButtonPadding}" />
<Setter Property="HorizontalAlignment" Value="Left" />
<Setter Property="VerticalAlignment" Value="Center" />
<Setter Property="FontWeight" Value="Normal" />

10
src/Avalonia.Themes.Fluent/Controls/CalendarDatePicker.xaml

@ -9,8 +9,10 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:sys="clr-namespace:System;assembly=netstandard">
<Design.PreviewWith>
<Border Margin="20">
<CalendarDatePicker/>
<Border Margin="20, 20, 20, 200">
<CalendarDatePicker Width="200"
VerticalContentAlignment="Center"
HorizontalContentAlignment="Center" />
</Border>
</Design.PreviewWith>
@ -74,7 +76,7 @@
Grid.Row="1"
Grid.ColumnSpan="4"
Grid.RowSpan="3"
FontSize="{StaticResource CalendarDatePickerCurrentDayFontSize}"
FontSize="{DynamicResource CalendarDatePickerCurrentDayFontSize}"
Text="{Binding Source={x:Static sys:DateTime.Today}, Path=Day}"/>
<Ellipse HorizontalAlignment="Center" VerticalAlignment="Center" Fill="{DynamicResource SystemControlBackgroundChromeBlackHighBrush}" StrokeThickness="0" Grid.ColumnSpan="4" Width="3" Height="3"/>
@ -104,6 +106,8 @@
Watermark="{TemplateBinding Watermark}"
UseFloatingWatermark="{TemplateBinding UseFloatingWatermark}"
DataValidationErrors.Errors="{TemplateBinding (DataValidationErrors.Errors)}"
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}"
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"
Grid.Column="0"/>
<Button Name="PART_Button"

2
src/Avalonia.Themes.Fluent/Controls/CaptionButtons.xaml

@ -56,7 +56,7 @@
<Style Selector="CaptionButtons:maximized Panel#PART_RestoreButton Path">
<Setter Property="Data" Value="M2048 410h-410v-410h-1638v1638h410v410h1638v-1638zM1434 1434h-1229v-1229h1229v1229zM1843 1843h-1229v-205h1024v-1024h205v1229z" />
</Style>
<Style Selector="CaptionButtons Panel#PART_FullScreenButton Path">
<Style Selector="CaptionButtons Panel#PART_FullScreenButton">
<Setter Property="IsVisible" Value="False" />
</Style>
<Style Selector="CaptionButtons Panel#PART_FullScreenButton Path">

4
src/Avalonia.Themes.Fluent/Controls/DatePicker.xaml

@ -141,8 +141,8 @@
BorderBrush="{TemplateBinding BorderBrush}"
BorderThickness="{TemplateBinding BorderThickness}"
IsEnabled="{TemplateBinding IsEnabled}"
MinWidth="{StaticResource DatePickerThemeMinWidth}"
MaxWidth="{StaticResource DatePickerThemeMaxWidth}"
MinWidth="{DynamicResource DatePickerThemeMinWidth}"
MaxWidth="{DynamicResource DatePickerThemeMaxWidth}"
HorizontalAlignment="Stretch"
VerticalAlignment="Stretch"
HorizontalContentAlignment="Stretch"

2
src/Avalonia.Themes.Fluent/Controls/Expander.xaml

@ -108,7 +108,7 @@
</Setter>
</Style>
<Style Selector="Expander /template/ ToggleButton#PART_toggle:pointerover /template/ Border">
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlTransientBorderBrush}" />
<Setter Property="BorderBrush" Value="{DynamicResource SystemControlHighlightBaseMediumBrush}" />
</Style>
<Style Selector="Expander:down:expanded /template/ ToggleButton#PART_toggle /template/ Path">
<Setter Property="RenderTransform">

6
src/Avalonia.Themes.Fluent/Controls/FocusAdorner.xaml

@ -10,10 +10,10 @@
<Style Selector=":is(Control)">
<Setter Property="FocusAdorner">
<FocusAdornerTemplate>
<Border BorderThickness="{StaticResource SystemControlFocusVisualPrimaryThickness}"
<Border BorderThickness="{DynamicResource SystemControlFocusVisualPrimaryThickness}"
BorderBrush="{DynamicResource SystemControlFocusVisualPrimaryBrush}"
Margin="{StaticResource SystemControlFocusVisualMargin}">
<Border BorderThickness="{StaticResource SystemControlFocusVisualSecondaryThickness}"
Margin="{DynamicResource SystemControlFocusVisualMargin}">
<Border BorderThickness="{DynamicResource SystemControlFocusVisualSecondaryThickness}"
BorderBrush="{DynamicResource SystemControlFocusVisualSecondaryBrush}" />
</Border>
</FocusAdornerTemplate>

2
src/Avalonia.Themes.Fluent/Controls/ListBoxItem.xaml

@ -17,7 +17,7 @@
</Styles.Resources>
<Style Selector="ListBoxItem">
<Setter Property="Background" Value="Transparent" />
<Setter Property="Padding" Value="{StaticResource ListBoxItemPadding}" />
<Setter Property="Padding" Value="{DynamicResource ListBoxItemPadding}" />
<Setter Property="HorizontalContentAlignment" Value="Left" />
<Setter Property="Template">
<ControlTemplate>

2
src/Avalonia.Themes.Fluent/Controls/Menu.xaml

@ -16,7 +16,7 @@
</Style.Resources>
<Setter Property="Background" Value="Transparent" />
<Setter Property="Height" Value="{StaticResource MenuBarHeight}" />
<Setter Property="Height" Value="{DynamicResource MenuBarHeight}" />
<Setter Property="Template">
<ControlTemplate>
<Border Background="{TemplateBinding Background}"

6
src/Avalonia.Themes.Fluent/Controls/MenuItem.xaml

@ -112,7 +112,7 @@
<Popup Name="PART_Popup"
WindowManagerAddShadowHint="False"
PlacementMode="Right"
HorizontalOffset="{StaticResource MenuFlyoutSubItemPopupHorizontalOffset}"
HorizontalOffset="{DynamicResource MenuFlyoutSubItemPopupHorizontalOffset}"
IsLightDismissEnabled="True"
IsOpen="{TemplateBinding IsSubMenuOpen, Mode=TwoWay}">
<Border Background="{DynamicResource MenuFlyoutPresenterBackground}"
@ -189,12 +189,12 @@
<Style Selector="MenuItem">
<!-- Narrow padding should be used for mouse input, when non-narrow one should be used for touch input in future. -->
<Setter Property="Padding" Value="{StaticResource MenuFlyoutItemThemePaddingNarrow}" />
<Setter Property="Padding" Value="{DynamicResource MenuFlyoutItemThemePaddingNarrow}" />
</Style>
<Style Selector="Menu > MenuItem">
<!-- Custom padding for Menu > MenuItem -->
<Setter Property="Padding" Value="{StaticResource MenuBarItemPadding}" />
<Setter Property="Padding" Value="{DynamicResource MenuBarItemPadding}" />
</Style>
<Style Selector="MenuItem /template/ ContentPresenter#PART_IconPresenter">

Some files were not shown because too many files changed in this diff

Loading…
Cancel
Save