Browse Source

fixes(DevTools): Merge conflicts with master branch

pull/6769/head
Giuseppe Lippolis 5 years ago
parent
commit
4d163633d6
  1. 27
      Avalonia.sln
  2. 6
      azure-pipelines.yml
  3. 35
      build/MicroCom.targets
  4. 8
      nukebuild/MicroComGen.cs
  5. 6
      nukebuild/_build.csproj
  6. 12
      src/Android/Avalonia.Android/AndroidPlatform.cs
  7. 3
      src/Avalonia.Base/ApiCompatBaseline.txt
  8. 9
      src/Avalonia.Base/Threading/Dispatcher.cs
  9. 9
      src/Avalonia.Base/Threading/IDispatcher.cs
  10. 83
      src/Avalonia.Base/Threading/JobRunner.cs
  11. 19
      src/Avalonia.Controls/TextBox.cs
  12. 4
      src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs
  13. 17
      src/Avalonia.Diagnostics/Diagnostics/Convetions.cs
  14. 10
      src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs
  15. 17
      src/Avalonia.Diagnostics/Diagnostics/IScreenshotHandler.cs
  16. 29
      src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs
  17. 85
      src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs
  18. 34
      src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs
  19. 30
      src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml
  20. 4
      src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml
  21. 71
      src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs
  22. 4
      src/Avalonia.Headless/HeadlessPlatformStubs.cs
  23. 11
      src/Avalonia.Input/TouchDevice.cs
  24. 6
      src/Avalonia.MicroCom/MicroComRuntime.cs
  25. 5
      src/Avalonia.MicroCom/MicroComVtblBase.cs
  26. 5
      src/Avalonia.Native/Avalonia.Native.csproj
  27. 28
      src/Avalonia.Native/AvaloniaNativePlatform.cs
  28. 6
      src/Avalonia.Visuals/ApiCompatBaseline.txt
  29. 10
      src/Avalonia.Visuals/Platform/IPlatformSettings.cs
  30. 12
      src/Avalonia.X11/Stubs.cs
  31. 1
      src/Avalonia.X11/X11Platform.cs
  32. 6
      src/Directory.Build.props
  33. 4
      src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs
  34. 10
      src/Shared/ModuleInitializer.cs
  35. 3
      src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs
  36. 7
      src/Windows/Avalonia.Win32/Avalonia.Win32.csproj
  37. 5
      src/Windows/Avalonia.Win32/Win32Platform.cs
  38. 15
      src/iOS/Avalonia.iOS/Platform.cs
  39. 241
      src/tools/MicroComGenerator/Ast.cs
  40. 232
      src/tools/MicroComGenerator/AstParser.cs
  41. 484
      src/tools/MicroComGenerator/CSharpGen.InterfaceGen.cs
  42. 111
      src/tools/MicroComGenerator/CSharpGen.Utils.cs
  43. 155
      src/tools/MicroComGenerator/CSharpGen.cs
  44. 119
      src/tools/MicroComGenerator/CppGen.cs
  45. 97
      src/tools/MicroComGenerator/Extensions.cs
  46. 10
      src/tools/MicroComGenerator/MicroComGenerator.csproj
  47. 27
      src/tools/MicroComGenerator/ParseException.cs
  48. 52
      src/tools/MicroComGenerator/Program.cs
  49. 417
      src/tools/MicroComGenerator/TokenParser.cs
  50. 2
      tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs
  51. 6
      tests/Avalonia.UnitTests/ImmediateDispatcher.cs

27
Avalonia.sln

@ -221,8 +221,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup.Xaml.Loader
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sandbox", "samples\Sandbox\Sandbox.csproj", "{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sandbox", "samples\Sandbox\Sandbox.csproj", "{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MicroComGenerator", "src\tools\MicroComGenerator\MicroComGenerator.csproj", "{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.MicroCom", "src\Avalonia.MicroCom\Avalonia.MicroCom.csproj", "{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.MicroCom", "src\Avalonia.MicroCom\Avalonia.MicroCom.csproj", "{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}"
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MiniMvvm", "samples\MiniMvvm\MiniMvvm.csproj", "{BC594FD5-4AF2-409E-A1E6-04123F54D7C5}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MiniMvvm", "samples\MiniMvvm\MiniMvvm.csproj", "{BC594FD5-4AF2-409E-A1E6-04123F54D7C5}"
@ -2027,30 +2025,6 @@ Global
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhone.Build.0 = Release|Any CPU {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhone.Build.0 = Release|Any CPU
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhoneSimulator.Build.0 = Release|Any CPU {11BE52AF-E2DD-4CF0-B19A-05285ACAF571}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhone.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Ad-Hoc|iPhoneSimulator.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|Any CPU.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|Any CPU.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhone.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhone.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.AppStore|iPhoneSimulator.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhone.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhone.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|Any CPU.Build.0 = Release|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhone.ActiveCfg = Release|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhone.Build.0 = Release|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD}.Release|iPhoneSimulator.Build.0 = Release|Any CPU
{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|Any CPU.ActiveCfg = Debug|Any CPU
{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|Any CPU.Build.0 = Debug|Any CPU
{FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU {FE2F3E5E-1E34-4972-8DC1-5C2C588E5ECE}.Ad-Hoc|iPhone.ActiveCfg = Debug|Any CPU
@ -2253,7 +2227,6 @@ Global
{3C84E04B-36CF-4D0D-B965-C26DD649D1F3} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9} {3C84E04B-36CF-4D0D-B965-C26DD649D1F3} = {A0CC0258-D18C-4AB3-854F-7101680FC3F9}
{909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C} {909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C}
{11BE52AF-E2DD-4CF0-B19A-05285ACAF571} = {9B9E3891-2366-4253-A952-D08BCEB71098} {11BE52AF-E2DD-4CF0-B19A-05285ACAF571} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{AEC9031E-06EA-4A9E-9E7F-7D7C719404DD} = {4ED8B739-6F4E-4CD4-B993-545E6B5CE637}
{BC594FD5-4AF2-409E-A1E6-04123F54D7C5} = {9B9E3891-2366-4253-A952-D08BCEB71098} {BC594FD5-4AF2-409E-A1E6-04123F54D7C5} = {9B9E3891-2366-4253-A952-D08BCEB71098}
{25831348-EB2A-483E-9576-E8F6528674A5} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268} {25831348-EB2A-483E-9576-E8F6528674A5} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268}
{C08E9894-AA92-426E-BF56-033E262CAD3E} = {9B9E3891-2366-4253-A952-D08BCEB71098} {C08E9894-AA92-426E-BF56-033E262CAD3E} = {9B9E3891-2366-4253-A952-D08BCEB71098}

6
azure-pipelines.yml

@ -58,8 +58,10 @@ jobs:
displayName: 'Generate avalonia-native' displayName: 'Generate avalonia-native'
inputs: inputs:
script: | script: |
export PATH="`pwd`/sdk:$PATH" export COREHOST_TRACE=0
cd src/tools/MicroComGenerator; dotnet run -f net6.0 -i ../../Avalonia.Native/avn.idl --cpp ../../../native/Avalonia.Native/inc/avalonia-native.h export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1
export DOTNET_CLI_TELEMETRY_OPTOUT=1
./build.sh --target GenerateCppHeaders --configuration Release
- task: Xcode@5 - task: Xcode@5
inputs: inputs:

35
build/MicroCom.targets

@ -1,35 +0,0 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<!-- Ensure that code generator is actually built -->
<ItemGroup>
<ProjectReference Include="$(MSBuildThisFileDirectory)\..\src\tools\MicroComGenerator\MicroComGenerator.csproj">
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
<ExcludeAssets>all</ExcludeAssets>
<SkipGetTargetFrameworkProperties>true</SkipGetTargetFrameworkProperties>
<SetTargetFramework>TargetFramework=net6.0</SetTargetFramework>
</ProjectReference>
</ItemGroup>
<Target Name="GenerateAvaloniaNativeComInterop"
BeforeTargets="CoreCompile"
DependsOnTargets="ResolveReferences"
Inputs="@(AvnComIdl);$(MSBuildThisFileDirectory)../src/tools/MicroComGenerator/**/*.cs"
Outputs="%(AvnComIdl.OutputFile)">
<Message Importance="high" Text="Generating file %(AvnComIdl.OutputFile) from @(AvnComIdl)" />
<Exec Command="dotnet &quot;$(MSBuildThisFileDirectory)../src/tools/MicroComGenerator/bin/$(Configuration)/net6.0/MicroComGenerator.dll&quot; -i @(AvnComIdl) --cs %(AvnComIdl.OutputFile)"
LogStandardErrorAsError="true" />
<ItemGroup>
<!-- Remove and re-add generated file, this is needed for the clean build -->
<Compile Remove="%(AvnComIdl.OutputFile)"/>
<Compile Include="%(AvnComIdl.OutputFile)"/>
</ItemGroup>
</Target>
<ItemGroup>
<UpToDateCheckInput Include="@(AvnComIdl)"/>
<UpToDateCheckInput Include="$(MSBuildThisFileDirectory)/../src/tools/MicroComGenerator/**/*.cs"/>
</ItemGroup>
<PropertyGroup>
<_AvaloniaPatchComInterop>true</_AvaloniaPatchComInterop>
</PropertyGroup>
<Import Project="$(MSBuildThisFileDirectory)/BuildTargets.targets" />
</Project>

8
nukebuild/MicroComGen.cs

@ -1,14 +1,14 @@
using System.IO; using System.IO;
using MicroComGenerator; using MicroCom.CodeGenerator;
using Nuke.Common; using Nuke.Common;
partial class Build : NukeBuild partial class Build : NukeBuild
{ {
Target GenerateCppHeaders => _ => _.Executes(() => Target GenerateCppHeaders => _ => _.Executes(() =>
{ {
var text = File.ReadAllText(RootDirectory / "src" / "Avalonia.Native" / "avn.idl"); var file = MicroComCodeGenerator.Parse(
var ast = AstParser.Parse(text); File.ReadAllText(RootDirectory / "src" / "Avalonia.Native" / "avn.idl"));
File.WriteAllText(RootDirectory / "native" / "Avalonia.Native" / "inc" / "avalonia-native.h", File.WriteAllText(RootDirectory / "native" / "Avalonia.Native" / "inc" / "avalonia-native.h",
CppGen.GenerateCpp(ast)); file.GenerateCppHeader());
}); });
} }

6
nukebuild/_build.csproj

@ -15,7 +15,7 @@
<PackageReference Include="JetBrains.dotMemoryUnit" Version="3.0.20171219.105559" /> <PackageReference Include="JetBrains.dotMemoryUnit" Version="3.0.20171219.105559" />
<PackageReference Include="vswhere" Version="2.6.7" Condition=" '$(OS)' == 'Windows_NT' " /> <PackageReference Include="vswhere" Version="2.6.7" Condition=" '$(OS)' == 'Windows_NT' " />
<PackageReference Include="ILRepack.NETStandard" Version="2.0.4" /> <PackageReference Include="ILRepack.NETStandard" Version="2.0.4" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.7.0" /> <PackageReference Include="MicroCom.CodeGenerator" Version="0.10.4" />
<!-- Keep in sync with Avalonia.Build.Tasks --> <!-- Keep in sync with Avalonia.Build.Tasks -->
<PackageReference Include="Mono.Cecil" Version="0.11.2" /> <PackageReference Include="Mono.Cecil" Version="0.11.2" />
</ItemGroup> </ItemGroup>
@ -37,10 +37,6 @@
<None Include="..\GitVersion.yml" Condition="Exists('..\GitVersion.yml')" /> <None Include="..\GitVersion.yml" Condition="Exists('..\GitVersion.yml')" />
<Compile Remove="Numerge/**/*.*" /> <Compile Remove="Numerge/**/*.*" />
<Compile Include="Numerge/Numerge/**/*.cs" /> <Compile Include="Numerge/Numerge/**/*.cs" />
<Compile Include="..\src\tools\MicroComGenerator\**\*.cs" Exclude="..\src\tools\MicroComGenerator\obj\**">
<Link>MicroComGenerator\%(Filename)%(Extension)</Link>
</Compile>
<Compile Remove="..\src\tools\MicroComGenerator\Program.cs" />
</ItemGroup> </ItemGroup>
</Project> </Project>

12
src/Android/Avalonia.Android/AndroidPlatform.cs

@ -33,8 +33,16 @@ namespace Avalonia.Android
{ {
public static readonly AndroidPlatform Instance = new AndroidPlatform(); public static readonly AndroidPlatform Instance = new AndroidPlatform();
public static AndroidPlatformOptions Options { get; private set; } public static AndroidPlatformOptions Options { get; private set; }
public Size DoubleClickSize => new Size(4, 4);
public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(200); /// <inheritdoc cref="IPlatformSettings.TouchDoubleClickSize"/>
public Size TouchDoubleClickSize => new Size(4, 4);
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickTime"/>
public TimeSpan TouchDoubleClickTime => TimeSpan.FromMilliseconds(200);
public Size DoubleClickSize => TouchDoubleClickSize;
public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500);
public static void Initialize(Type appType, AndroidPlatformOptions options) public static void Initialize(Type appType, AndroidPlatformOptions options)
{ {

3
src/Avalonia.Base/ApiCompatBaseline.txt

@ -0,0 +1,3 @@
Compat issues with assembly Avalonia.Base:
InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Threading.IDispatcher.Post<T>(System.Action<T>, T, Avalonia.Threading.DispatcherPriority)' is present in the implementation but not in the contract.
Total Issues: 1

9
src/Avalonia.Base/Threading/Dispatcher.cs

@ -81,7 +81,7 @@ namespace Avalonia.Threading
_ = action ?? throw new ArgumentNullException(nameof(action)); _ = action ?? throw new ArgumentNullException(nameof(action));
return _jobRunner.InvokeAsync(action, priority); return _jobRunner.InvokeAsync(action, priority);
} }
/// <inheritdoc/> /// <inheritdoc/>
public Task<TResult> InvokeAsync<TResult>(Func<TResult> function, DispatcherPriority priority = DispatcherPriority.Normal) public Task<TResult> InvokeAsync<TResult>(Func<TResult> function, DispatcherPriority priority = DispatcherPriority.Normal)
{ {
@ -110,6 +110,13 @@ namespace Avalonia.Threading
_jobRunner.Post(action, priority); _jobRunner.Post(action, priority);
} }
/// <inheritdoc/>
public void Post<T>(Action<T> action, T arg, DispatcherPriority priority = DispatcherPriority.Normal)
{
_ = action ?? throw new ArgumentNullException(nameof(action));
_jobRunner.Post(action, arg, priority);
}
/// <summary> /// <summary>
/// This is needed for platform backends that don't have internal priority system (e. g. win32) /// This is needed for platform backends that don't have internal priority system (e. g. win32)
/// To ensure that there are no jobs with higher priority /// To ensure that there are no jobs with higher priority

9
src/Avalonia.Base/Threading/IDispatcher.cs

@ -26,6 +26,15 @@ namespace Avalonia.Threading
/// <param name="priority">The priority with which to invoke the method.</param> /// <param name="priority">The priority with which to invoke the method.</param>
void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal); void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal);
/// <summary>
/// Posts an action that will be invoked on the dispatcher thread.
/// </summary>
/// <typeparam name="T">type of argument</typeparam>
/// <param name="action">The method to call.</param>
/// <param name="arg">The argument of method to call.</param>
/// <param name="priority">The priority with which to invoke the method.</param>
void Post<T>(Action<T> action, T arg, DispatcherPriority priority = DispatcherPriority.Normal);
/// <summary> /// <summary>
/// Invokes a action on the dispatcher thread. /// Invokes a action on the dispatcher thread.
/// </summary> /// </summary>

83
src/Avalonia.Base/Threading/JobRunner.cs

@ -13,7 +13,7 @@ namespace Avalonia.Threading
{ {
private IPlatformThreadingInterface? _platform; private IPlatformThreadingInterface? _platform;
private readonly Queue<IJob>[] _queues = Enumerable.Range(0, (int) DispatcherPriority.MaxValue + 1) private readonly Queue<IJob>[] _queues = Enumerable.Range(0, (int)DispatcherPriority.MaxValue + 1)
.Select(_ => new Queue<IJob>()).ToArray(); .Select(_ => new Queue<IJob>()).ToArray();
public JobRunner(IPlatformThreadingInterface? platform) public JobRunner(IPlatformThreadingInterface? platform)
@ -59,7 +59,7 @@ namespace Avalonia.Threading
/// <returns>A task that can be used to track the method's execution.</returns> /// <returns>A task that can be used to track the method's execution.</returns>
public Task<TResult> InvokeAsync<TResult>(Func<TResult> function, DispatcherPriority priority) public Task<TResult> InvokeAsync<TResult>(Func<TResult> function, DispatcherPriority priority)
{ {
var job = new Job<TResult>(function, priority); var job = new JobWithResult<TResult>(function, priority);
AddJob(job); AddJob(job);
return job.Task; return job.Task;
} }
@ -75,6 +75,17 @@ namespace Avalonia.Threading
AddJob(new Job(action, priority, true)); AddJob(new Job(action, priority, true));
} }
/// <summary>
/// Post action that will be invoked on main thread
/// </summary>
/// <param name="action">The method to call.</param>
/// <param name="parameter">The parameter of method to call.</param>
/// <param name="priority">The priority with which to invoke the method.</param>
internal void Post<T>(Action<T> action, T parameter, DispatcherPriority priority)
{
AddJob(new Job<T>(action, parameter, priority, true));
}
/// <summary> /// <summary>
/// Allows unit tests to change the platform threading interface. /// Allows unit tests to change the platform threading interface.
/// </summary> /// </summary>
@ -86,7 +97,7 @@ namespace Avalonia.Threading
private void AddJob(IJob job) private void AddJob(IJob job)
{ {
bool needWake; bool needWake;
var queue = _queues[(int) job.Priority]; var queue = _queues[(int)job.Priority];
lock (queue) lock (queue)
{ {
needWake = queue.Count == 0; needWake = queue.Count == 0;
@ -98,7 +109,7 @@ namespace Avalonia.Threading
private IJob? GetNextJob(DispatcherPriority minimumPriority) private IJob? GetNextJob(DispatcherPriority minimumPriority)
{ {
for (int c = (int) DispatcherPriority.MaxValue; c >= (int) minimumPriority; c--) for (int c = (int)DispatcherPriority.MaxValue; c >= (int)minimumPriority; c--)
{ {
var q = _queues[c]; var q = _queues[c];
lock (q) lock (q)
@ -109,14 +120,14 @@ namespace Avalonia.Threading
} }
return null; return null;
} }
private interface IJob private interface IJob
{ {
/// <summary> /// <summary>
/// Gets the job priority. /// Gets the job priority.
/// </summary> /// </summary>
DispatcherPriority Priority { get; } DispatcherPriority Priority { get; }
/// <summary> /// <summary>
/// Runs the job. /// Runs the job.
/// </summary> /// </summary>
@ -177,11 +188,61 @@ namespace Avalonia.Threading
} }
} }
} }
/// <summary> /// <summary>
/// A job to run. /// A typed job to run.
/// </summary>
/// <typeparam name="T">Type of job parameter</typeparam>
private sealed class Job<T> : IJob
{
private readonly Action<T> _action;
private readonly T _parameter;
private readonly TaskCompletionSource<bool>? _taskCompletionSource;
/// <summary>
/// Initializes a new instance of the <see cref="Job"/> class.
/// </summary>
/// <param name="action">The method to call.</param>
/// <param name="parameter">The parameter of method to call.</param>
/// <param name="priority">The job priority.</param>
/// <param name="throwOnUiThread">Do not wrap exception in TaskCompletionSource</param>
public Job(Action<T> action, T parameter, DispatcherPriority priority, bool throwOnUiThread)
{
_action = action;
_parameter = parameter;
Priority = priority;
_taskCompletionSource = throwOnUiThread ? null : new TaskCompletionSource<bool>();
}
/// <inheritdoc/>
public DispatcherPriority Priority { get; }
/// <inheritdoc/>
void IJob.Run()
{
if (_taskCompletionSource == null)
{
_action(_parameter);
return;
}
try
{
_action(_parameter);
_taskCompletionSource.SetResult(default);
}
catch (Exception e)
{
_taskCompletionSource.SetException(e);
}
}
}
/// <summary>
/// A job to run thath return value.
/// </summary> /// </summary>
private sealed class Job<TResult> : IJob /// <typeparam name="TResult">Type of job result</typeparam>
private sealed class JobWithResult<TResult> : IJob
{ {
private readonly Func<TResult> _function; private readonly Func<TResult> _function;
private readonly TaskCompletionSource<TResult> _taskCompletionSource; private readonly TaskCompletionSource<TResult> _taskCompletionSource;
@ -191,7 +252,7 @@ namespace Avalonia.Threading
/// </summary> /// </summary>
/// <param name="function">The method to call.</param> /// <param name="function">The method to call.</param>
/// <param name="priority">The job priority.</param> /// <param name="priority">The job priority.</param>
public Job(Func<TResult> function, DispatcherPriority priority) public JobWithResult(Func<TResult> function, DispatcherPriority priority)
{ {
_function = function; _function = function;
Priority = priority; Priority = priority;
@ -200,7 +261,7 @@ namespace Avalonia.Threading
/// <inheritdoc/> /// <inheritdoc/>
public DispatcherPriority Priority { get; } public DispatcherPriority Priority { get; }
/// <summary> /// <summary>
/// The task. /// The task.
/// </summary> /// </summary>

19
src/Avalonia.Controls/TextBox.cs

@ -1006,13 +1006,28 @@ namespace Avalonia.Controls
if (text != null && clickInfo.Properties.IsLeftButtonPressed && !(clickInfo.Pointer?.Captured is Border)) if (text != null && clickInfo.Properties.IsLeftButtonPressed && !(clickInfo.Pointer?.Captured is Border))
{ {
var point = e.GetPosition(_presenter); var point = e.GetPosition(_presenter);
var index = CaretIndex = _presenter.GetCaretIndex(point); var index = _presenter.GetCaretIndex(point);
var clickToSelect = index != CaretIndex && e.KeyModifiers.HasFlag(KeyModifiers.Shift);
if (!clickToSelect)
{
CaretIndex = index;
}
#pragma warning disable CS0618 // Type or member is obsolete #pragma warning disable CS0618 // Type or member is obsolete
switch (e.ClickCount) switch (e.ClickCount)
#pragma warning restore CS0618 // Type or member is obsolete #pragma warning restore CS0618 // Type or member is obsolete
{ {
case 1: case 1:
SelectionStart = SelectionEnd = index; if (clickToSelect)
{
SelectionStart = Math.Min(index, CaretIndex);
SelectionEnd = Math.Max(index, CaretIndex);
}
else
{
SelectionStart = SelectionEnd = index;
}
break; break;
case 2: case 2:
if (!StringUtils.IsStartOfWord(text, index)) if (!StringUtils.IsStartOfWord(text, index))

4
src/Avalonia.DesignerSupport/Remote/PreviewerWindowingPlatform.cs

@ -64,5 +64,9 @@ namespace Avalonia.DesignerSupport.Remote
public Size DoubleClickSize { get; } = new Size(2, 2); public Size DoubleClickSize { get; } = new Size(2, 2);
public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500); public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500);
public Size TouchDoubleClickSize => new Size(16, 16);
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
} }
} }

17
src/Avalonia.Diagnostics/Diagnostics/Convetions.cs

@ -0,0 +1,17 @@
using System;
using System.Reflection;
using Avalonia.Controls;
using Avalonia.VisualTree;
namespace Avalonia.Diagnostics
{
static class Convetions
{
public static string DefaultScreenshotsRoot =>
System.IO.Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures, Environment.SpecialFolderOption.Create),
"Screenshots");
public static IScreenshotHandler DefaultScreenshotHandler { get; } =
new Screenshots.FilePickerHandler();
}
}

10
src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs

@ -1,4 +1,5 @@
using Avalonia.Input; using System;
using Avalonia.Input;
namespace Avalonia.Diagnostics namespace Avalonia.Diagnostics
{ {
@ -33,5 +34,12 @@ namespace Avalonia.Diagnostics
/// Gets or sets a value indicating whether DevTools should be displayed implemented interfaces on Control details. The default value is true. /// Gets or sets a value indicating whether DevTools should be displayed implemented interfaces on Control details. The default value is true.
/// </summary> /// </summary>
public bool ShowImplementedInterfaces { get; set; } = true; public bool ShowImplementedInterfaces { get; set; } = true;
/// <summary>
/// Allow to customizze SreenshotHandler
/// </summary>
/// <remarks>Default handler is <see cref="Screenshots.FilePickerHandler"/></remarks>
public IScreenshotHandler ScreenshotHandler { get; set; }
= Convetions.DefaultScreenshotHandler;
} }
} }

17
src/Avalonia.Diagnostics/Diagnostics/IScreenshotHandler.cs

@ -0,0 +1,17 @@
using System.Threading.Tasks;
using Avalonia.Controls;
namespace Avalonia.Diagnostics
{
/// <summary>
/// Allowed to define custom handler for Shreeshot
/// </summary>
public interface IScreenshotHandler
{
/// <summary>
/// Handle the Screenshot
/// </summary>
/// <returns></returns>
Task Take(IControl control);
}
}

29
src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs

@ -0,0 +1,29 @@
using System.Threading.Tasks;
using Avalonia.Controls;
namespace Avalonia.Diagnostics.Screenshots
{
/// <summary>
/// Base class for render Screenshto to stream
/// </summary>
public abstract class BaseRenderToStreamHandler : IScreenshotHandler
{
/// <summary>
/// Get stream
/// </summary>
/// <param name="control"></param>
/// <returns>stream to render the control</returns>
protected abstract Task<System.IO.Stream?> GetStream(IControl control);
public async Task Take(IControl control)
{
using var output = await GetStream(control);
if (output is { })
{
control.RenderTo(output);
await output.FlushAsync();
}
}
}
}

85
src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs

@ -0,0 +1,85 @@
using System;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
using Avalonia.Controls;
using Lifetimes = Avalonia.Controls.ApplicationLifetimes;
namespace Avalonia.Diagnostics.Screenshots
{
/// <summary>
/// Show a FileSavePicker to select where save screenshot
/// </summary>
public sealed class FilePickerHandler : BaseRenderToStreamHandler
{
/// <summary>
/// Instance FilePickerHandler
/// </summary>
public FilePickerHandler()
{
}
/// <summary>
/// Instance FilePickerHandler with specificated parameter
/// </summary>
/// <param name="title">SaveFilePicker Title</param>
/// <param name="screenshotRoot"></param>
public FilePickerHandler(string? title
, string? screenshotRoot = default
)
{
if (title is { })
Title = title;
if (screenshotRoot is { })
ScreenshotsRoot = screenshotRoot;
}
/// <summary>
/// Get the root folder where screeshots well be stored.
/// The default root folder is [Environment.SpecialFolder.MyPictures]/Screenshots.
/// </summary>
public string ScreenshotsRoot { get; }
= Convetions.DefaultScreenshotsRoot;
/// <summary>
/// SaveFilePicker Title
/// </summary>
public string Title { get; } = "Save Screenshot to ...";
Window GetWindow(IControl control)
{
var window = control.VisualRoot as Window;
var app = Application.Current;
if (app?.ApplicationLifetime is Lifetimes.IClassicDesktopStyleApplicationLifetime desktop)
{
window = desktop.Windows.FirstOrDefault(w => w is Views.MainWindow);
}
return window!;
}
protected async override Task<Stream?> GetStream(IControl control)
{
Stream? output = default;
var result = await new SaveFileDialog()
{
Title = Title,
Filters = new() { new FileDialogFilter() { Name = "PNG", Extensions = new() { "png" } } },
Directory = ScreenshotsRoot,
}.ShowAsync(GetWindow(control));
if (!string.IsNullOrWhiteSpace(result))
{
var foldler = Path.GetDirectoryName(result);
// Directory information for path, or null if path denotes a root directory or is
// null. Returns System.String.Empty if path does not contain directory information.
if (!string.IsNullOrWhiteSpace(foldler))
{
if (!Directory.Exists(foldler))
{
Directory.CreateDirectory(foldler);
}
output = new FileStream(result, FileMode.Create);
}
}
return output;
}
}
}

34
src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs

@ -1,8 +1,11 @@
using System; using System;
using System.ComponentModel; using System.ComponentModel;
using System.Reactive.Linq;
using System.Threading.Tasks;
using Avalonia.Controls; using Avalonia.Controls;
using Avalonia.Diagnostics.Models; using Avalonia.Diagnostics.Models;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Metadata;
using Avalonia.Threading; using Avalonia.Threading;
using System.Reactive.Linq; using System.Reactive.Linq;
using System.Linq; using System.Linq;
@ -26,8 +29,10 @@ namespace Avalonia.Diagnostics.ViewModels
private bool _freezePopups; private bool _freezePopups;
private string? _pointerOverElementName; private string? _pointerOverElementName;
private IInputRoot? _pointerOverRoot; private IInputRoot? _pointerOverRoot;
private IScreenshotHandler? _screenshotHandler;
private bool _showPropertyType; private bool _showPropertyType;
private bool _showImplementedInterfaces; private bool _showImplementedInterfaces;
public MainViewModel(AvaloniaObject root) public MainViewModel(AvaloniaObject root)
{ {
_root = root; _root = root;
@ -286,9 +291,38 @@ namespace Avalonia.Diagnostics.ViewModels
} }
public int? StartupScreenIndex { get; private set; } = default; public int? StartupScreenIndex { get; private set; } = default;
[DependsOn(nameof(TreePageViewModel.SelectedNode))]
[DependsOn(nameof(Content))]
bool CanShot(object? parameter)
{
return Content is TreePageViewModel tree
&& tree.SelectedNode != null
&& tree.SelectedNode.Visual is VisualTree.IVisual visual
&& visual.VisualRoot != null;
}
async void Shot(object? parameter)
{
if ((Content as TreePageViewModel)?.SelectedNode?.Visual is IControl control
&& _screenshotHandler is { }
)
{
try
{
await _screenshotHandler.Take(control);
}
catch (Exception ex)
{
System.Diagnostics.Debug.WriteLine(ex.Message);
//TODO: Notify error
}
}
}
public void SetOptions(DevToolsOptions options) public void SetOptions(DevToolsOptions options)
{ {
_screenshotHandler = options.ScreenshotHandler;
StartupScreenIndex = options.StartupScreenIndex; StartupScreenIndex = options.StartupScreenIndex;
ShowImplementedInterfaces = options.ShowImplementedInterfaces; ShowImplementedInterfaces = options.ShowImplementedInterfaces;
} }

30
src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml

@ -6,6 +6,36 @@
<Menu> <Menu>
<MenuItem Header="_File"> <MenuItem Header="_File">
<MenuItem Header="E_xit" Command="{Binding $parent[Window].Close}" /> <MenuItem Header="E_xit" Command="{Binding $parent[Window].Close}" />
<MenuItem Header="Screenshot" Command="{Binding Shot}" HotKey="F8">
<MenuItem.Icon>
<Image>
<DrawingImage>
<DrawingGroup>
<GeometryDrawing Geometry="F1 M 13.4533,17.56C 13.4533,19.8827 15.344,21.772 17.6653,21.772L 17.6653,21.772C 19.988,21.772 21.8773,19.8827 21.8773,17.56L 21.8773,17.56C 21.8773,15.2373 19.988,13.348 17.6653,13.348L 17.6653,13.348C 15.344,13.348 13.4533,15.2373 13.4533,17.56 Z ">
<GeometryDrawing.Brush>
<RadialGradientBrush Center="0.245696,0.288009" GradientOrigin="0.245696,0.288009" Radius="0.499952">
<RadialGradientBrush.GradientStops>
<GradientStop Color="#FF878A8C" Offset="0" />
<GradientStop Color="#FF544A4C" Offset="0.991379" />
</RadialGradientBrush.GradientStops>
</RadialGradientBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
<GeometryDrawing Geometry="F1 M 13.332,6.22803L 10.2227,9.72668L 8.49866,9.72668L 8.49866,7.56136L 5.33333,7.56136L 5.33333,9.72668L 3.33333,9.72668L 3.33333,24.3947L 13.1213,24.3947C 14.424,25.264 15.9853,25.772 17.6653,25.772L 17.6653,25.772C 19.344,25.772 20.9067,25.264 22.2094,24.3947L 28.6667,24.3947L 28.6667,9.72668L 24.944,9.72668L 21.8333,6.22803M 12.12,17.56C 12.12,14.5013 14.608,12.0147 17.6653,12.0147L 17.6653,12.0147C 20.7227,12.0147 23.2107,14.5013 23.2107,17.56L 23.2107,17.56C 23.2107,20.6174 20.7227,23.104 17.6653,23.104L 17.6653,23.104C 14.608,23.104 12.12,20.6174 12.12,17.56 Z ">
<GeometryDrawing.Brush>
<RadialGradientBrush Center="0.196943,0.216757" GradientOrigin="0.196943,0.216757" Radius="0.44654">
<RadialGradientBrush.GradientStops>
<GradientStop Color="#FF87898C" Offset="0" />
<GradientStop Color="#FF544A4C" Offset="1" />
</RadialGradientBrush.GradientStops>
</RadialGradientBrush>
</GeometryDrawing.Brush>
</GeometryDrawing>
</DrawingGroup>
</DrawingImage>
</Image>
</MenuItem.Icon>
</MenuItem>
</MenuItem> </MenuItem>
<MenuItem Header="_View"> <MenuItem Header="_View">
<MenuItem Header="_Console" Command="{Binding $parent[UserControl].ToggleConsole}"> <MenuItem Header="_Console" Command="{Binding $parent[UserControl].ToggleConsole}">

4
src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml

@ -18,6 +18,8 @@
<StyleInclude Source="avares://Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.axaml" /> <StyleInclude Source="avares://Avalonia.Diagnostics/Diagnostics/Controls/ThicknessEditor.axaml" />
<StyleInclude Source="avares://Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml" /> <StyleInclude Source="avares://Avalonia.Diagnostics/Diagnostics/Controls/FilterTextBox.axaml" />
</Window.Styles> </Window.Styles>
<Window.KeyBindings>
<KeyBinding Gesture="F8" Command="{Binding Shot}"/>
</Window.KeyBindings>
<views:MainView/> <views:MainView/>
</Window> </Window>

71
src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs

@ -0,0 +1,71 @@
using System;
using System.IO;
using Avalonia.Controls;
using Avalonia.Data;
using Avalonia.Media.Imaging;
using Avalonia.VisualTree;
namespace Avalonia.Diagnostics
{
internal static class VisualExtensions
{
/// <summary>
/// Render control to the destination stream.
/// </summary>
/// <param name="source">Control to be rendered.</param>
/// <param name="destination">Destination stream.</param>
/// <param name="dpi">Dpi quality.</param>
public static void RenderTo(this IControl source, Stream destination, double dpi = 96)
{
if (source.TransformedBounds == null)
{
return;
}
var rect = source.TransformedBounds.Value.Clip;
var top = rect.TopLeft;
var pixelSize = new PixelSize((int)rect.Width, (int)rect.Height);
var dpiVector = new Vector(dpi, dpi);
// get Visual root
var root = (source.VisualRoot
?? source.GetVisualRoot())
as IControl ?? source;
IDisposable? clipSetter = default;
IDisposable? clipToBoundsSetter = default;
IDisposable? renderTransformOriginSetter = default;
IDisposable? renderTransformSetter = default;
try
{
// Set clip region
var clipRegion = new Media.RectangleGeometry(rect);
clipToBoundsSetter = root.SetValue(Visual.ClipToBoundsProperty, true, BindingPriority.Animation);
clipSetter = root.SetValue(Visual.ClipProperty, clipRegion, BindingPriority.Animation);
// Translate origin
renderTransformOriginSetter = root.SetValue(Visual.RenderTransformOriginProperty,
new RelativePoint(top, RelativeUnit.Absolute),
BindingPriority.Animation);
renderTransformSetter = root.SetValue(Visual.RenderTransformProperty,
new Media.TranslateTransform(-top.X, -top.Y),
BindingPriority.Animation);
using (var bitmap = new RenderTargetBitmap(pixelSize, dpiVector))
{
bitmap.Render(root);
bitmap.Save(destination);
}
}
finally
{
// Restore values before trasformation
renderTransformSetter?.Dispose();
renderTransformOriginSetter?.Dispose();
clipSetter?.Dispose();
clipToBoundsSetter?.Dispose();
source?.InvalidateVisual();
}
}
}
}

4
src/Avalonia.Headless/HeadlessPlatformStubs.cs

@ -67,6 +67,10 @@ namespace Avalonia.Headless
{ {
public Size DoubleClickSize { get; } = new Size(2, 2); public Size DoubleClickSize { get; } = new Size(2, 2);
public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500); public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500);
public Size TouchDoubleClickSize => new Size(16,16);
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
} }
class HeadlessSystemDialogsStub : ISystemDialogImpl class HeadlessSystemDialogsStub : ISystemDialogImpl

11
src/Avalonia.Input/TouchDevice.cs

@ -58,20 +58,17 @@ namespace Avalonia.Input
} }
else else
{ {
var settings = AvaloniaLocator.Current.GetService<IPlatformSettings>(); var settings = AvaloniaLocator.Current.GetRequiredService<IPlatformSettings>();
if (settings == null)
{
throw new Exception("IPlatformSettings can not be null.");
}
if (!_lastClickRect.Contains(args.Position) if (!_lastClickRect.Contains(args.Position)
|| ev.Timestamp - _lastClickTime > settings.DoubleClickTime.TotalMilliseconds) || ev.Timestamp - _lastClickTime > settings.TouchDoubleClickTime.TotalMilliseconds)
{ {
_clickCount = 0; _clickCount = 0;
} }
++_clickCount; ++_clickCount;
_lastClickTime = ev.Timestamp; _lastClickTime = ev.Timestamp;
_lastClickRect = new Rect(args.Position, new Size()) _lastClickRect = new Rect(args.Position, new Size())
.Inflate(new Thickness(16, 16)); .Inflate(new Thickness(settings.TouchDoubleClickSize.Width / 2, settings.TouchDoubleClickSize.Height / 2));
} }
target.RaiseEvent(new PointerPressedEventArgs(target, pointer, target.RaiseEvent(new PointerPressedEventArgs(target, pointer,

6
src/Avalonia.MicroCom/MicroComRuntime.cs

@ -36,7 +36,13 @@ namespace Avalonia.MicroCom
public static T CreateProxyFor<T>(void* pObject, bool ownsHandle) => (T)CreateProxyFor(typeof(T), new IntPtr(pObject), ownsHandle); public static T CreateProxyFor<T>(void* pObject, bool ownsHandle) => (T)CreateProxyFor(typeof(T), new IntPtr(pObject), ownsHandle);
public static T CreateProxyFor<T>(IntPtr pObject, bool ownsHandle) => (T)CreateProxyFor(typeof(T), pObject, ownsHandle); public static T CreateProxyFor<T>(IntPtr pObject, bool ownsHandle) => (T)CreateProxyFor(typeof(T), pObject, ownsHandle);
public static T CreateProxyOrNullFor<T>(void* pObject, bool ownsHandle) where T : class =>
pObject == null ? null : (T)CreateProxyFor(typeof(T), new IntPtr(pObject), ownsHandle);
public static T CreateProxyOrNullFor<T>(IntPtr pObject, bool ownsHandle) where T : class =>
pObject == IntPtr.Zero ? null : (T)CreateProxyFor(typeof(T), pObject, ownsHandle);
public static object CreateProxyFor(Type type, IntPtr pObject, bool ownsHandle) => _factories[type](pObject, ownsHandle); public static object CreateProxyFor(Type type, IntPtr pObject, bool ownsHandle) => _factories[type](pObject, ownsHandle);
public static IntPtr GetNativeIntPtr<T>(this T obj, bool owned = false) where T : IUnknown public static IntPtr GetNativeIntPtr<T>(this T obj, bool owned = false) where T : IUnknown

5
src/Avalonia.MicroCom/MicroComVtblBase.cs

@ -21,6 +21,11 @@ namespace Avalonia.MicroCom
AddMethod((AddRefDelegate)Release); AddMethod((AddRefDelegate)Release);
} }
protected void AddMethod(void* f)
{
_methods.Add(new IntPtr(f));
}
protected void AddMethod(Delegate d) protected void AddMethod(Delegate d)
{ {
GCHandle.Alloc(d); GCHandle.Alloc(d);

5
src/Avalonia.Native/Avalonia.Native.csproj

@ -6,6 +6,7 @@
<IsPackable Condition="'$([MSBuild]::IsOSPlatform(OSX))' == 'True'">true</IsPackable> <IsPackable Condition="'$([MSBuild]::IsOSPlatform(OSX))' == 'True'">true</IsPackable>
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks> <TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<MicroComGeneratorRuntimeNamespace>Avalonia.MicroCom</MicroComGeneratorRuntimeNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup Condition="'$(PackAvaloniaNative)' == 'true'"> <ItemGroup Condition="'$(PackAvaloniaNative)' == 'true'">
@ -20,7 +21,7 @@
<ItemGroup> <ItemGroup>
<ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" /> <ProjectReference Include="..\..\packages\Avalonia\Avalonia.csproj" />
<ProjectReference Include="..\Avalonia.Dialogs\Avalonia.Dialogs.csproj" /> <ProjectReference Include="..\Avalonia.Dialogs\Avalonia.Dialogs.csproj" />
<AvnComIdl Include="avn.idl" OutputFile="Interop.Generated.cs" /> <PackageReference Include="MicroCom.CodeGenerator.MSBuild" Version="0.10.4" PrivateAssets="all" />
<MicroComIdl Include="avn.idl" CSharpInteropPath="Interop.Generated.cs" />
</ItemGroup> </ItemGroup>
<Import Project="../../build/MicroCom.targets" />
</Project> </Project>

28
src/Avalonia.Native/AvaloniaNativePlatform.cs

@ -26,9 +26,15 @@ namespace Avalonia.Native
public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500); //TODO public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(500); //TODO
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickSize"/>
public Size TouchDoubleClickSize => new Size(16, 16);
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickTime"/>
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
public static AvaloniaNativePlatform Initialize(IntPtr factory, AvaloniaNativePlatformOptions options) public static AvaloniaNativePlatform Initialize(IntPtr factory, AvaloniaNativePlatformOptions options)
{ {
var result = new AvaloniaNativePlatform(MicroComRuntime.CreateProxyFor<IAvaloniaNativeFactory>(factory, true)); var result = new AvaloniaNativePlatform(MicroComRuntime.CreateProxyFor<IAvaloniaNativeFactory>(factory, true));
result.DoInitialize(options); result.DoInitialize(options);
return result; return result;
@ -55,14 +61,14 @@ namespace Avalonia.Native
return Initialize(CreateAvaloniaNative(), options); return Initialize(CreateAvaloniaNative(), options);
} }
public void SetupApplicationMenuExporter () public void SetupApplicationMenuExporter()
{ {
var exporter = new AvaloniaNativeMenuExporter(_factory); var exporter = new AvaloniaNativeMenuExporter(_factory);
} }
public void SetupApplicationName () public void SetupApplicationName()
{ {
if(!string.IsNullOrWhiteSpace(Application.Current.Name)) if (!string.IsNullOrWhiteSpace(Application.Current.Name))
{ {
_factory.MacOptions.SetApplicationTitle(Application.Current.Name); _factory.MacOptions.SetApplicationTitle(Application.Current.Name);
} }
@ -80,13 +86,13 @@ namespace Avalonia.Native
GCHandle.FromIntPtr(handle).Free(); GCHandle.FromIntPtr(handle).Free();
} }
} }
void DoInitialize(AvaloniaNativePlatformOptions options) void DoInitialize(AvaloniaNativePlatformOptions options)
{ {
_options = options; _options = options;
var applicationPlatform = new AvaloniaNativeApplicationPlatform(); var applicationPlatform = new AvaloniaNativeApplicationPlatform();
_factory.Initialize(new GCHandleDeallocator(), applicationPlatform); _factory.Initialize(new GCHandleDeallocator(), applicationPlatform);
if (_factory.MacOptions != null) if (_factory.MacOptions != null)
{ {
@ -118,7 +124,7 @@ namespace Avalonia.Native
hotkeys.MoveCursorToTheStartOfLineWithSelection.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers | hotkeys.SelectionModifiers)); hotkeys.MoveCursorToTheStartOfLineWithSelection.Add(new KeyGesture(Key.Left, hotkeys.CommandModifiers | hotkeys.SelectionModifiers));
hotkeys.MoveCursorToTheEndOfLine.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers)); hotkeys.MoveCursorToTheEndOfLine.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers));
hotkeys.MoveCursorToTheEndOfLineWithSelection.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers | hotkeys.SelectionModifiers)); hotkeys.MoveCursorToTheEndOfLineWithSelection.Add(new KeyGesture(Key.Right, hotkeys.CommandModifiers | hotkeys.SelectionModifiers));
if (_options.UseGpu) if (_options.UseGpu)
{ {
try try
@ -133,7 +139,7 @@ namespace Avalonia.Native
} }
} }
public ITrayIconImpl CreateTrayIcon () public ITrayIconImpl CreateTrayIcon()
{ {
return new TrayIconImpl(_factory); return new TrayIconImpl(_factory);
} }
@ -159,8 +165,8 @@ namespace Avalonia.Native
ShowInDock = true; ShowInDock = true;
} }
public bool ShowInDock public bool ShowInDock
{ {
get => _showInDock; get => _showInDock;
set set
{ {

6
src/Avalonia.Visuals/ApiCompatBaseline.txt

@ -79,4 +79,8 @@ InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWr
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.String)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmap(System.String)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToHeight(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToHeight(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToWidth(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract. InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Platform.IWriteableBitmapImpl Avalonia.Platform.IPlatformRenderInterface.LoadWriteableBitmapToWidth(System.IO.Stream, System.Int32, Avalonia.Visuals.Media.Imaging.BitmapInterpolationMode)' is present in the implementation but not in the contract.
Total Issues: 79 InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Size Avalonia.Platform.IPlatformSettings.TouchDoubleClickSize' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.TimeSpan Avalonia.Platform.IPlatformSettings.TouchDoubleClickTime' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public Avalonia.Size Avalonia.Platform.IPlatformSettings.TouchDoubleClickSize.get()' is present in the implementation but not in the contract.
InterfacesShouldHaveSameMembers : Interface member 'public System.TimeSpan Avalonia.Platform.IPlatformSettings.TouchDoubleClickTime.get()' is present in the implementation but not in the contract.
Total Issues: 84

10
src/Avalonia.Visuals/Platform/IPlatformSettings.cs

@ -7,5 +7,15 @@ namespace Avalonia.Platform
Size DoubleClickSize { get; } Size DoubleClickSize { get; }
TimeSpan DoubleClickTime { get; } TimeSpan DoubleClickTime { get; }
/// <summary>
/// Determines the size of the area within that you should click twice in order for a double click to be counted.
/// </summary>
Size TouchDoubleClickSize { get; }
/// <summary>
/// Determines the time span that what will be used to determine the double-click.
/// </summary>
TimeSpan TouchDoubleClickTime { get; }
} }
} }

12
src/Avalonia.X11/Stubs.cs

@ -1,10 +1,4 @@
using System; using System;
using System.IO;
using System.Threading.Tasks;
using Avalonia.Controls;
using Avalonia.Controls.Platform;
using Avalonia.Input;
using Avalonia.Input.Platform;
using Avalonia.Platform; using Avalonia.Platform;
namespace Avalonia.X11 namespace Avalonia.X11
@ -13,5 +7,11 @@ namespace Avalonia.X11
{ {
public Size DoubleClickSize { get; } = new Size(2, 2); public Size DoubleClickSize { get; } = new Size(2, 2);
public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500); public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500);
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickSize"/>
public Size TouchDoubleClickSize => new Size(16, 16);
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickTime"/>
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
} }
} }

1
src/Avalonia.X11/X11Platform.cs

@ -68,6 +68,7 @@ namespace Avalonia.X11
//TODO: log //TODO: log
if (options.UseDBusMenu) if (options.UseDBusMenu)
DBusHelper.TryInitialize(); DBusHelper.TryInitialize();
AvaloniaLocator.CurrentMutable.BindToSelf(this) AvaloniaLocator.CurrentMutable.BindToSelf(this)
.Bind<IWindowingPlatform>().ToConstant(this) .Bind<IWindowingPlatform>().ToConstant(this)
.Bind<IPlatformThreadingInterface>().ToConstant(new X11PlatformThreading(this)) .Bind<IPlatformThreadingInterface>().ToConstant(new X11PlatformThreading(this))

6
src/Directory.Build.props

@ -2,4 +2,10 @@
<Import Project="..\Directory.Build.props" /> <Import Project="..\Directory.Build.props" />
<Import Project="..\build\SharedVersion.props" /> <Import Project="..\build\SharedVersion.props" />
<Import Project="..\build\SourceLink.props" Condition="'$(DisableSourceLink)' == ''" /> <Import Project="..\build\SourceLink.props" Condition="'$(DisableSourceLink)' == ''" />
<ItemGroup>
<Compile Include="$(MSBuildThisFileDirectory)\Shared\ModuleInitializer.cs" >
<Link>Shared\_ModuleInitializer.cs</Link>
<Visible>false</Visible>
</Compile>
</ItemGroup>
</Project> </Project>

4
src/Linux/Avalonia.LinuxFramebuffer/Stubs.cs

@ -18,5 +18,9 @@ namespace Avalonia.LinuxFramebuffer
{ {
public Size DoubleClickSize { get; } = new Size(4, 4); public Size DoubleClickSize { get; } = new Size(4, 4);
public TimeSpan DoubleClickTime { get; } = new TimeSpan(0, 0, 0, 0, 500); public TimeSpan DoubleClickTime { get; } = new TimeSpan(0, 0, 0, 0, 500);
public Size TouchDoubleClickSize => new Size(16,16);
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
} }
} }

10
src/Shared/ModuleInitializer.cs

@ -0,0 +1,10 @@
namespace System.Runtime.CompilerServices
{
#if !NET5_0_OR_GREATER
internal class ModuleInitializerAttribute : Attribute
{
}
#endif
}

3
src/Web/Avalonia.Web.Blazor/WindowingPlatform.cs

@ -51,6 +51,9 @@ namespace Avalonia.Web.Blazor
public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500); public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500);
public Size TouchDoubleClickSize => new Size(16, 16);
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
public void RunLoop(CancellationToken cancellationToken) public void RunLoop(CancellationToken cancellationToken)
{ {
throw new NotSupportedException(); throw new NotSupportedException();

7
src/Windows/Avalonia.Win32/Avalonia.Win32.csproj

@ -3,14 +3,15 @@
<TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks> <TargetFrameworks>net6.0;netstandard2.0</TargetFrameworks>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks> <AllowUnsafeBlocks>true</AllowUnsafeBlocks>
<PackageId>Avalonia.Win32</PackageId> <PackageId>Avalonia.Win32</PackageId>
<MicroComGeneratorRuntimeNamespace>Avalonia.MicroCom</MicroComGeneratorRuntimeNamespace>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="System.Numerics.Vectors" Version="4.5.0" /> <PackageReference Include="System.Numerics.Vectors" Version="4.5.0" />
<ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" /> <ProjectReference Include="..\..\..\packages\Avalonia\Avalonia.csproj" />
<PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" /> <PackageReference Include="Avalonia.Angle.Windows.Natives" Version="2.1.0.2020091801" />
<AvnComIdl Include="WinRT\winrt.idl" OutputFile="WinRT\WinRT.Generated.cs" /> <PackageReference Include="MicroCom.CodeGenerator.MSBuild" Version="0.10.4" PrivateAssets="all" />
<AvnComIdl Include="Win32Com\win32.idl" OutputFile="Win32Com\Win32.Generated.cs" /> <MicroComIdl Include="WinRT\winrt.idl" CSharpInteropPath="WinRT\WinRT.Generated.cs" />
<MicroComIdl Include="Win32Com\win32.idl" CSharpInteropPath="Win32Com\Win32.Generated.cs" />
</ItemGroup> </ItemGroup>
<Import Project="../../../build/MicroCom.targets" />
<Import Project="$(MSBuildThisFileDirectory)\..\..\..\build\System.Drawing.Common.props" /> <Import Project="$(MSBuildThisFileDirectory)\..\..\..\build\System.Drawing.Common.props" />
</Project> </Project>

5
src/Windows/Avalonia.Win32/Win32Platform.cs

@ -134,6 +134,11 @@ namespace Avalonia.Win32
public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(UnmanagedMethods.GetDoubleClickTime()); public TimeSpan DoubleClickTime => TimeSpan.FromMilliseconds(UnmanagedMethods.GetDoubleClickTime());
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickSize"/>
public Size TouchDoubleClickSize => new Size(16,16);
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickTime"/>
public TimeSpan TouchDoubleClickTime => DoubleClickTime;
public static void Initialize() public static void Initialize()
{ {
Initialize(new Win32PlatformOptions()); Initialize(new Win32PlatformOptions());

15
src/iOS/Avalonia.iOS/Platform.cs

@ -1,11 +1,9 @@
using System; using System;
using Avalonia.Controls;
using Avalonia.Input; using Avalonia.Input;
using Avalonia.Input.Platform; using Avalonia.Input.Platform;
using Avalonia.OpenGL; using Avalonia.OpenGL;
using Avalonia.Platform; using Avalonia.Platform;
using Avalonia.Rendering; using Avalonia.Rendering;
using Avalonia.Shared.PlatformSupport;
namespace Avalonia.iOS namespace Avalonia.iOS
{ {
@ -15,10 +13,17 @@ namespace Avalonia.iOS
public static DisplayLinkTimer Timer; public static DisplayLinkTimer Timer;
class PlatformSettings : IPlatformSettings class PlatformSettings : IPlatformSettings
{ {
public Size DoubleClickSize { get; } = new Size(10, 10); /// <inheritdoc cref="IPlatformSettings.TouchDoubleClickSize"/>
public TimeSpan DoubleClickTime { get; } = TimeSpan.FromMilliseconds(500); public Size TouchDoubleClickSize => new Size(10, 10);
/// <inheritdoc cref="IPlatformSettings.TouchDoubleClickTime"/>
public TimeSpan TouchDoubleClickTime => TimeSpan.FromMilliseconds(500);
public Size DoubleClickSize => new Size(4, 4);
public TimeSpan DoubleClickTime => TouchDoubleClickTime;
} }
public static void Register() public static void Register()
{ {
GlFeature ??= new EaglFeature(); GlFeature ??= new EaglFeature();

241
src/tools/MicroComGenerator/Ast.cs

@ -1,241 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace MicroComGenerator.Ast
{
public class AstAttributeNode
{
public string Name { get; set; }
public string Value { get; set; }
public AstAttributeNode(string name, string value)
{
Name = name;
Value = value;
}
public override string ToString() => $"{Name} = {Value}";
public AstAttributeNode Clone() => new AstAttributeNode(Name, Value);
}
public class AstAttributes : List<AstAttributeNode>
{
public bool HasAttribute(string a) => this.Any(x => x.Name == a);
public AstAttributes Clone()
{
var rv= new AstAttributes();
rv.AddRange(this.Select(x => x.Clone()));
return rv;
}
}
public interface IAstNodeWithAttributes
{
public AstAttributes Attributes { get; set; }
}
public class AstEnumNode : List<AstEnumMemberNode>, IAstNodeWithAttributes
{
public AstAttributes Attributes { get; set; } = new AstAttributes();
public string Name { get; set; }
public override string ToString() => "Enum " + Name;
public AstEnumNode Clone()
{
var rv = new AstEnumNode { Name = Name, Attributes = Attributes.Clone() };
rv.AddRange(this.Select(x => x.Clone()));
return rv;
}
}
public class AstEnumMemberNode
{
public string Name { get; set; }
public string Value { get; set; }
public AstEnumMemberNode(string name, string value)
{
Name = name;
Value = value;
}
public override string ToString() => $"Enum member {Name} = {Value}";
public AstEnumMemberNode Clone() => new AstEnumMemberNode(Name, Value);
}
public class AstStructNode : List<AstStructMemberNode>, IAstNodeWithAttributes
{
public AstAttributes Attributes { get; set; } = new AstAttributes();
public string Name { get; set; }
public override string ToString() => "Struct " + Name;
public AstStructNode Clone()
{
var rv = new AstStructNode { Name = Name, Attributes = Attributes.Clone() };
rv.AddRange(this.Select(x => x.Clone()));
return rv;
}
}
public class AstTypeNode
{
public string Name { get; set; }
public int PointerLevel { get; set; }
public bool IsLink { get; set; }
public string Format() => Name + new string('*', PointerLevel)
+ (IsLink ? "&" : "");
public override string ToString() => Format();
public AstTypeNode Clone() => new AstTypeNode() {
Name = Name,
PointerLevel = PointerLevel,
IsLink = IsLink
};
}
public class AstStructMemberNode : IAstNodeWithAttributes
{
public string Name { get; set; }
public AstTypeNode Type { get; set; }
public override string ToString() => $"Struct member {Type.Format()} {Name}";
public AstStructMemberNode Clone() => new AstStructMemberNode() { Name = Name, Type = Type.Clone() };
public AstAttributes Attributes { get; set; } = new AstAttributes();
}
public class AstInterfaceNode : List<AstInterfaceMemberNode>, IAstNodeWithAttributes
{
public AstAttributes Attributes { get; set; } = new AstAttributes();
public string Name { get; set; }
public string Inherits { get; set; }
public override string ToString()
{
if (Inherits == null)
return Name;
return $"Interface {Name} : {Inherits}";
}
public AstInterfaceNode Clone()
{
var rv = new AstInterfaceNode { Name = Name, Inherits = Inherits, Attributes = Attributes.Clone() };
rv.AddRange(this.Select(x => x.Clone()));
return rv;
}
}
public class AstInterfaceMemberNode : List<AstInterfaceMemberArgumentNode>, IAstNodeWithAttributes
{
public string Name { get; set; }
public AstTypeNode ReturnType { get; set; }
public AstAttributes Attributes { get; set; } = new AstAttributes();
public AstInterfaceMemberNode Clone()
{
var rv = new AstInterfaceMemberNode()
{
Name = Name, Attributes = Attributes.Clone(), ReturnType = ReturnType
};
rv.AddRange(this.Select(x => x.Clone()));
return rv;
}
public override string ToString() =>
$"Interface member {ReturnType.Format()} {Name} ({string.Join(", ", this.Select(x => x.Format()))})";
}
public class AstInterfaceMemberArgumentNode : IAstNodeWithAttributes
{
public string Name { get; set; }
public AstTypeNode Type { get; set; }
public AstAttributes Attributes { get; set; } = new AstAttributes();
public string Format() => $"{Type.Format()} {Name}";
public override string ToString() => "Argument " + Format();
public AstInterfaceMemberArgumentNode Clone() => new AstInterfaceMemberArgumentNode
{
Name = Name, Type = Type.Clone(), Attributes = Attributes.Clone()
};
}
public static class AstExtensions
{
public static bool HasAttribute(this IAstNodeWithAttributes node, string s) => node.Attributes.HasAttribute(s);
public static string GetAttribute(this IAstNodeWithAttributes node, string s)
{
var value = node.Attributes.FirstOrDefault(a => a.Name == s)?.Value;
if (value == null)
throw new CodeGenException("Expected attribute " + s + " for node " + node);
return value;
}
public static string GetAttributeOrDefault(this IAstNodeWithAttributes node, string s)
=> node.Attributes.FirstOrDefault(a => a.Name == s)?.Value;
}
class AstVisitor
{
protected virtual void VisitType(AstTypeNode type)
{
}
protected virtual void VisitArgument(AstInterfaceMemberArgumentNode argument)
{
VisitType(argument.Type);
}
protected virtual void VisitInterfaceMember(AstInterfaceMemberNode member)
{
foreach(var a in member)
VisitArgument(a);
VisitType(member.ReturnType);
}
protected virtual void VisitInterface(AstInterfaceNode iface)
{
foreach(var m in iface)
VisitInterfaceMember(m);
}
protected virtual void VisitStructMember(AstStructMemberNode member)
{
VisitType(member.Type);
}
protected virtual void VisitStruct(AstStructNode node)
{
foreach(var m in node)
VisitStructMember(m);
}
public virtual void VisitAst(AstIdlNode ast)
{
foreach(var iface in ast.Interfaces)
VisitInterface(iface);
foreach (var s in ast.Structs)
VisitStruct(s);
}
}
public class AstIdlNode : IAstNodeWithAttributes
{
public AstAttributes Attributes { get; set; } = new AstAttributes();
public List<AstEnumNode> Enums { get; set; } = new List<AstEnumNode>();
public List<AstStructNode> Structs { get; set; } = new List<AstStructNode>();
public List<AstInterfaceNode> Interfaces { get; set; } = new List<AstInterfaceNode>();
public AstIdlNode Clone() => new AstIdlNode()
{
Attributes = Attributes.Clone(),
Enums = Enums.Select(x => x.Clone()).ToList(),
Structs = Structs.Select(x => x.Clone()).ToList(),
Interfaces = Interfaces.Select(x => x.Clone()).ToList()
};
}
}

232
src/tools/MicroComGenerator/AstParser.cs

@ -1,232 +0,0 @@
using System.Collections.Generic;
using MicroComGenerator.Ast;
namespace MicroComGenerator
{
public class AstParser
{
public static AstIdlNode Parse(string source)
{
var parser = new TokenParser(source);
var idl = new AstIdlNode { Attributes = ParseGlobalAttributes(ref parser) };
while (!parser.Eof)
{
var attrs = ParseLocalAttributes(ref parser);
if (parser.TryConsume(";"))
continue;
if (parser.TryParseKeyword("enum"))
idl.Enums.Add(ParseEnum(attrs, ref parser));
else if (parser.TryParseKeyword("struct"))
idl.Structs.Add(ParseStruct(attrs, ref parser));
else if (parser.TryParseKeyword("interface"))
idl.Interfaces.Add(ParseInterface(attrs, ref parser));
else
throw new ParseException("Unexpected character", ref parser);
}
return idl;
}
static AstAttributes ParseGlobalAttributes(ref TokenParser parser)
{
var rv = new AstAttributes();
while (!parser.Eof)
{
parser.SkipWhitespace();
if (parser.TryConsume('@'))
{
var ident = parser.ParseIdentifier("-");
var value = parser.ReadToEol().Trim();
if (value == "@@")
{
parser.Advance(1);
value = "";
while (true)
{
var l = parser.ReadToEol();
if (l == "@@")
break;
else
value = value.Length == 0 ? l : (value + "\n" + l);
parser.Advance(1);
}
}
rv.Add(new AstAttributeNode(ident, value));
}
else
return rv;
}
return rv;
}
static AstAttributes ParseLocalAttributes(ref TokenParser parser)
{
var rv = new AstAttributes();
while (parser.TryConsume("["))
{
while (!parser.TryConsume("]") && !parser.Eof)
{
if (parser.TryConsume(','))
continue;
// Get identifier
var ident = parser.ParseIdentifier("-");
// No value, end of attribute list
if (parser.TryConsume(']'))
{
rv.Add(new AstAttributeNode(ident, null));
break;
}
// No value, next attribute
else if (parser.TryConsume(','))
rv.Add(new AstAttributeNode(ident, null));
// Has value
else if (parser.TryConsume('('))
{
var value = parser.ReadTo(')');
parser.Consume(')');
rv.Add(new AstAttributeNode(ident, value));
}
else
throw new ParseException("Unexpected character", ref parser);
}
if (parser.Eof)
throw new ParseException("Unexpected EOF", ref parser);
}
return rv;
}
static void EnsureOpenBracket(ref TokenParser parser)
{
if (!parser.TryConsume('{'))
throw new ParseException("{ expected", ref parser);
}
static AstEnumNode ParseEnum(AstAttributes attrs, ref TokenParser parser)
{
var name = parser.ParseIdentifier();
EnsureOpenBracket(ref parser);
var rv = new AstEnumNode { Name = name, Attributes = attrs };
while (!parser.TryConsume('}') && !parser.Eof)
{
if (parser.TryConsume(','))
continue;
var ident = parser.ParseIdentifier();
// Automatic value
if (parser.TryConsume(',') || parser.Peek == '}')
{
rv.Add(new AstEnumMemberNode(ident, null));
continue;
}
if (!parser.TryConsume('='))
throw new ParseException("Unexpected character", ref parser);
var value = parser.ReadToAny(",}").Trim();
rv.Add(new AstEnumMemberNode(ident, value));
if (parser.Eof)
throw new ParseException("Unexpected EOF", ref parser);
}
return rv;
}
static AstTypeNode ParseType(ref TokenParser parser)
{
var ident = parser.ParseIdentifier();
var t = new AstTypeNode { Name = ident };
while (parser.TryConsume('*'))
t.PointerLevel++;
if (parser.TryConsume("&"))
t.IsLink = true;
return t;
}
static AstStructNode ParseStruct(AstAttributes attrs, ref TokenParser parser)
{
var name = parser.ParseIdentifier();
EnsureOpenBracket(ref parser);
var rv = new AstStructNode { Name = name, Attributes = attrs };
while (!parser.TryConsume('}') && !parser.Eof)
{
var memberAttrs = ParseLocalAttributes(ref parser);
var t = ParseType(ref parser);
bool parsedAtLeastOneMember = false;
while (!parser.TryConsume(';'))
{
// Skip any ,
while (parser.TryConsume(',')) { }
var ident = parser.ParseIdentifier();
parsedAtLeastOneMember = true;
rv.Add(new AstStructMemberNode { Name = ident, Type = t, Attributes = memberAttrs});
}
if (!parsedAtLeastOneMember)
throw new ParseException("Expected at least one enum member with declared type " + t, ref parser);
}
return rv;
}
static AstInterfaceNode ParseInterface(AstAttributes interfaceAttrs, ref TokenParser parser)
{
var interfaceName = parser.ParseIdentifier();
string inheritsFrom = null;
if (parser.TryConsume(":"))
inheritsFrom = parser.ParseIdentifier();
EnsureOpenBracket(ref parser);
var rv = new AstInterfaceNode
{
Name = interfaceName, Attributes = interfaceAttrs, Inherits = inheritsFrom
};
while (!parser.TryConsume('}') && !parser.Eof)
{
var memberAttrs = ParseLocalAttributes(ref parser);
var returnType = ParseType(ref parser);
var name = parser.ParseIdentifier();
var member = new AstInterfaceMemberNode
{
Name = name, ReturnType = returnType, Attributes = memberAttrs
};
rv.Add(member);
parser.Consume('(');
while (true)
{
if (parser.TryConsume(')'))
break;
var argumentAttrs = ParseLocalAttributes(ref parser);
var type = ParseType(ref parser);
var argName = parser.ParseIdentifier();
member.Add(new AstInterfaceMemberArgumentNode
{
Name = argName, Type = type, Attributes = argumentAttrs
});
if (parser.TryConsume(')'))
break;
if (parser.TryConsume(','))
continue;
throw new ParseException("Unexpected character", ref parser);
}
parser.Consume(';');
}
return rv;
}
}
}

484
src/tools/MicroComGenerator/CSharpGen.InterfaceGen.cs

@ -1,484 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using MicroComGenerator.Ast;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
// ReSharper disable CoVariantArrayConversion
// HERE BE DRAGONS
namespace MicroComGenerator
{
public partial class CSharpGen
{
abstract class Arg
{
public string Name;
public string NativeType;
public AstAttributes Attributes { get; set; }
public virtual StatementSyntax CreateFixed(StatementSyntax inner) => inner;
public virtual void PreMarshal(List<StatementSyntax> body)
{
}
public virtual void PreMarshalForReturn(List<StatementSyntax> body) =>
throw new InvalidOperationException("Don't know how to use " + NativeType + " as HRESULT-return");
public virtual ExpressionSyntax Value(bool isHresultReturn) => ParseExpression(Name);
public abstract string ManagedType { get; }
public virtual string ReturnManagedType => ManagedType;
public virtual StatementSyntax[] ReturnMarshalResult() => new[] { ParseStatement("return " + Name + ";") };
public virtual void BackPreMarshal(List<StatementSyntax> body)
{
}
public virtual ExpressionSyntax BackMarshalValue() => ParseExpression(Name);
public virtual ExpressionSyntax BackMarshalReturn(string resultVar) => ParseExpression(resultVar);
}
class InterfaceReturnArg : Arg
{
public string InterfaceType;
public override ExpressionSyntax Value(bool isHresultReturn) => ParseExpression("&" + PName);
public override string ManagedType => InterfaceType;
private string PName => "__marshal_" + Name;
public override void PreMarshalForReturn(List<StatementSyntax> body)
{
body.Add(ParseStatement("void* " + PName + " = null;"));
}
public override StatementSyntax[] ReturnMarshalResult() => new[]
{
ParseStatement("return Avalonia.MicroCom.MicroComRuntime.CreateProxyFor<" + InterfaceType + ">(" +
PName + ", true);")
};
public override ExpressionSyntax BackMarshalValue()
{
return ParseExpression("INVALID");
}
public override ExpressionSyntax BackMarshalReturn(string resultVar)
{
return ParseExpression($"Avalonia.MicroCom.MicroComRuntime.GetNativePointer({resultVar}, true)");
}
}
class InterfaceArg : Arg
{
public string InterfaceType;
public override ExpressionSyntax Value(bool isHresultReturn) =>
ParseExpression("Avalonia.MicroCom.MicroComRuntime.GetNativePointer(" + Name + ")");
public override string ManagedType => InterfaceType;
public override StatementSyntax[] ReturnMarshalResult() => new[]
{
ParseStatement("return Avalonia.MicroCom.MicroComRuntime.CreateProxyFor<" + InterfaceType + ">(" +
Name + ", true);")
};
public override ExpressionSyntax BackMarshalValue()
{
return ParseExpression("Avalonia.MicroCom.MicroComRuntime.CreateProxyFor<" + InterfaceType + ">(" +
Name + ", false)");
}
public override ExpressionSyntax BackMarshalReturn(string resultVar)
{
return ParseExpression($"Avalonia.MicroCom.MicroComRuntime.GetNativePointer({resultVar}, true)");
}
}
class BypassArg : Arg
{
public string Type { get; set; }
public int PointerLevel;
public override string ManagedType => Type + new string('*', PointerLevel);
public override string ReturnManagedType => Type + new string('*', PointerLevel - 1);
public override ExpressionSyntax Value(bool isHresultReturn)
{
if (isHresultReturn)
return ParseExpression("&" + Name);
return base.Value(false);
}
public override void PreMarshalForReturn(List<StatementSyntax> body)
{
if (PointerLevel == 0)
base.PreMarshalForReturn(body);
else
body.Add(ParseStatement(Type + new string('*', PointerLevel - 1) + " " + Name + "=default;"));
}
}
class StringArg : Arg
{
private string BName => "__bytemarshal_" + Name;
private string FName => "__fixedmarshal_" + Name;
public override void PreMarshal(List<StatementSyntax> body)
{
body.Add(ParseStatement($"var {BName} = new byte[System.Text.Encoding.UTF8.GetByteCount({Name})+1];"));
body.Add(ParseStatement($"System.Text.Encoding.UTF8.GetBytes({Name}, 0, {Name}.Length, {BName}, 0);"));
}
public override StatementSyntax CreateFixed(StatementSyntax inner)
{
return FixedStatement(DeclareVar("byte*", FName, ParseExpression(BName)), inner);
}
public override ExpressionSyntax Value(bool isHresultReturn) => ParseExpression(FName);
public override string ManagedType => "string";
public override ExpressionSyntax BackMarshalValue()
{
return ParseExpression(
$"({Name} == null ? null : System.Runtime.InteropServices.Marshal.PtrToStringAnsi(new IntPtr(" + Name + ")))");
}
}
string ConvertNativeType(string type)
{
if (type == "size_t")
return "System.IntPtr";
if (type == "HRESULT")
return "int";
return type;
}
Arg ConvertArg(AstInterfaceMemberArgumentNode node)
{
var arg = ConvertArg(node.Name, node.Type);
arg.Attributes = node.Attributes.Clone();
return arg;
}
Arg ConvertArg(string name, AstTypeNode type)
{
type = new AstTypeNode { Name = ConvertNativeType(type.Name), PointerLevel = type.PointerLevel };
if (type.PointerLevel == 2)
{
if (IsInterface(type))
return new InterfaceReturnArg { Name = name, InterfaceType = type.Name, NativeType = "void**" };
}
else if (type.PointerLevel == 1)
{
if (IsInterface(type))
return new InterfaceArg { Name = name, InterfaceType = type.Name, NativeType = "void*" };
if (type.Name == "char")
return new StringArg { Name = name, NativeType = "byte*" };
}
return new BypassArg
{
Name = name, Type = type.Name, PointerLevel = type.PointerLevel, NativeType = type.ToString()
};
}
void GenerateInterfaceMember(AstInterfaceMemberNode member, ref InterfaceDeclarationSyntax iface,
ref ClassDeclarationSyntax proxy, ref ClassDeclarationSyntax vtbl,
List<StatementSyntax> vtblCtor, int num)
{
// Prepare method information
if (member.Name == "GetRenderingDevice")
Console.WriteLine();
var args = member.Select(ConvertArg).ToList();
var returnArg = ConvertArg("__result", member.ReturnType);
bool isHresult = member.ReturnType.Name == "HRESULT";
bool isHresultLastArgumentReturn = isHresult
&& args.Count > 0
&& (args.Last().Name == "ppv"
|| args.Last().Name == "retOut"
|| args.Last().Name == "ret"
|| args.Last().Attributes.HasAttribute("out")
|| args.Last().Attributes.HasAttribute("retval")
)
&& ((member.Last().Type.PointerLevel > 0
&& !IsInterface(member.Last().Type))
|| member.Last().Type.PointerLevel == 2);
bool isVoidReturn = member.ReturnType.Name == "void" && member.ReturnType.PointerLevel == 0;
// Generate method signature
MethodDeclarationSyntax GenerateManagedSig(string returnType, string name,
IEnumerable<(string n, string t)> args)
=> MethodDeclaration(ParseTypeName(returnType), name).WithParameterList(
ParameterList(
SeparatedList(args.Select(x => Parameter(Identifier(x.n)).WithType(ParseTypeName(x.t))))));
var managedSig =
isHresult ?
GenerateManagedSig(isHresultLastArgumentReturn ? args.Last().ReturnManagedType : "void",
member.Name,
(isHresultLastArgumentReturn ? args.SkipLast(1) : args).Select(a => (a.Name, a.ManagedType))) :
GenerateManagedSig(returnArg.ManagedType, member.Name, args.Select(a => (a.Name, a.ManagedType)));
iface = iface.AddMembers(managedSig.WithSemicolonToken(Semicolon()));
// Prepare args for marshaling
var preMarshal = new List<StatementSyntax>();
if (!isVoidReturn)
preMarshal.Add(ParseStatement(returnArg.NativeType + " __result;"));
for (var idx = 0; idx < args.Count; idx++)
{
if (isHresultLastArgumentReturn && idx == args.Count - 1)
args[idx].PreMarshalForReturn(preMarshal);
else
args[idx].PreMarshal(preMarshal);
}
// Generate call expression
ExpressionSyntax callExpr = InvocationExpression(_localInterop.GetCaller(returnArg.NativeType,
args.Select(x => x.NativeType).ToList()))
.AddArgumentListArguments(Argument(ParseExpression("PPV")))
.AddArgumentListArguments(args
.Select((a, i) => Argument(a.Value(isHresultLastArgumentReturn && i == args.Count - 1))).ToArray())
.AddArgumentListArguments(Argument(ParseExpression("(*PPV)[base.VTableSize + " + num + "]")));
if (!isVoidReturn)
callExpr = CastExpression(ParseTypeName(returnArg.NativeType), callExpr);
// Save call result if needed
if (!isVoidReturn)
callExpr = AssignmentExpression(SyntaxKind.SimpleAssignmentExpression, ParseExpression("__result"),
callExpr);
// Wrap call into fixed() blocks
StatementSyntax callStatement = ExpressionStatement(callExpr);
foreach (var arg in args)
callStatement = arg.CreateFixed(callStatement);
// Build proxy body
var proxyBody = Block()
.AddStatements(preMarshal.ToArray())
.AddStatements(callStatement);
// Process return value
if (!isVoidReturn)
{
if (isHresult)
{
proxyBody = proxyBody.AddStatements(
ParseStatement(
$"if(__result != 0) throw new System.Runtime.InteropServices.COMException(\"{member.Name} failed\", __result);"));
if (isHresultLastArgumentReturn)
proxyBody = proxyBody.AddStatements(args.Last().ReturnMarshalResult());
}
else
proxyBody = proxyBody.AddStatements(returnArg.ReturnMarshalResult());
}
// Add the proxy method
proxy = proxy.AddMembers(managedSig.AddModifiers(SyntaxKind.PublicKeyword)
.WithBody(proxyBody));
// Generate VTable method
var shadowDelegate = DelegateDeclaration(ParseTypeName(returnArg.NativeType), member.Name + "Delegate")
.AddParameterListParameters(Parameter(Identifier("@this")).WithType(ParseTypeName("IntPtr")))
.AddParameterListParameters(args.Select(x =>
Parameter(Identifier(x.Name)).WithType(ParseTypeName(x.NativeType))).ToArray())
.AddAttribute("System.Runtime.InteropServices.UnmanagedFunctionPointer",
"System.Runtime.InteropServices.CallingConvention.StdCall");
var shadowMethod = MethodDeclaration(shadowDelegate.ReturnType, member.Name)
.WithParameterList(shadowDelegate.ParameterList)
.AddModifiers(Token(SyntaxKind.StaticKeyword));
var backPreMarshal = new List<StatementSyntax>();
foreach (var arg in args)
arg.BackPreMarshal(backPreMarshal);
backPreMarshal.Add(
ParseStatement($"__target = ({iface.Identifier.Text})Avalonia.MicroCom.MicroComRuntime.GetObjectFromCcw(@this);"));
var isBackVoidReturn = isVoidReturn || (isHresult && !isHresultLastArgumentReturn);
StatementSyntax backCallStatement;
var backCallExpr =
IsPropertyRewriteCandidate(managedSig) ?
ParseExpression("__target." + member.Name.Substring(3)) :
InvocationExpression(ParseExpression("__target." + member.Name))
.WithArgumentList(ArgumentList(SeparatedList(
(isHresultLastArgumentReturn ? args.SkipLast(1) : args)
.Select(a =>
Argument(a.BackMarshalValue())))));
if (isBackVoidReturn)
backCallStatement = ExpressionStatement(backCallExpr);
else
{
backCallStatement = LocalDeclarationStatement(DeclareVar("var", "__result", backCallExpr));
if (isHresultLastArgumentReturn)
{
backCallStatement = Block(backCallStatement,
ExpressionStatement(AssignmentExpression(SyntaxKind.SimpleAssignmentExpression,
ParseExpression("*" + args.Last().Name),
args.Last().BackMarshalReturn("__result")
)));
}
else
backCallStatement = Block(backCallStatement,
ReturnStatement(returnArg.BackMarshalReturn("__result")));
}
BlockSyntax backBodyBlock = Block().AddStatements(backPreMarshal.ToArray()).AddStatements(backCallStatement);
var exceptions = new List<CatchClauseSyntax>()
{
CatchClause(
CatchDeclaration(ParseTypeName("System.Exception"), Identifier("__exception__")), null,
Block(
ParseStatement(
"Avalonia.MicroCom.MicroComRuntime.UnhandledException(__target, __exception__);"),
isHresult ? ParseStatement("return unchecked((int)0x80004005u);")
: isVoidReturn ? EmptyStatement() : ParseStatement("return default;")
))
};
if (isHresult)
exceptions.Insert(0, CatchClause(
CatchDeclaration(ParseTypeName("System.Runtime.InteropServices.COMException"),
Identifier("__com_exception__")),
null, Block(ParseStatement("return __com_exception__.ErrorCode;"))));
backBodyBlock = Block(
TryStatement(
List(exceptions))
.WithBlock(Block(backBodyBlock))
);
if (isHresult)
backBodyBlock = backBodyBlock.AddStatements(ParseStatement("return 0;"));
backBodyBlock = Block()
.AddStatements(ParseStatement($"{iface.Identifier.Text} __target = null;"))
.AddStatements(backBodyBlock.Statements.ToArray());
shadowMethod = shadowMethod.WithBody(backBodyBlock);
vtbl = vtbl.AddMembers(shadowDelegate).AddMembers(shadowMethod);
vtblCtor.Add(ParseStatement("base.AddMethod((" + shadowDelegate.Identifier.Text + ")" +
shadowMethod.Identifier.Text + ");"));
}
class LocalInteropHelper
{
public ClassDeclarationSyntax Class { get; private set; } = ClassDeclaration("LocalInterop");
private HashSet<string> _existing = new HashSet<string>();
public ExpressionSyntax GetCaller(string returnType, List<string> args)
{
string ConvertType(string t) => t.EndsWith("*") ? "void*" : t;
returnType = ConvertType(returnType);
args = args.Select(ConvertType).ToList();
var name = "CalliStdCall" + returnType.Replace("*", "_ptr");
var signature = returnType + "::" + name + "::" + string.Join("::", args);
if (_existing.Add(signature))
{
Class = Class.AddMembers(MethodDeclaration(ParseTypeName(returnType), name)
.AddModifiers(SyntaxKind.StaticKeyword, SyntaxKind.UnsafeKeyword, SyntaxKind.PublicKeyword)
.AddParameterListParameters(Parameter(Identifier("thisObj")).WithType(ParseTypeName("void*")))
.AddParameterListParameters(args.Select((x, i) =>
Parameter(Identifier("arg" + i)).WithType(ParseTypeName(x))).ToArray())
.AddParameterListParameters(Parameter(Identifier("methodPtr")).WithType(ParseTypeName("void*")))
.WithBody(Block(ExpressionStatement(ThrowExpression(ParseExpression("null"))))));
}
return ParseExpression("LocalInterop." + name);
}
}
void GenerateInterface(ref NamespaceDeclarationSyntax ns, ref NamespaceDeclarationSyntax implNs,
AstInterfaceNode iface)
{
var guidString = iface.GetAttribute("uuid");
var inheritsUnknown = iface.Inherits == null || iface.Inherits == "IUnknown";
var ifaceDec = InterfaceDeclaration(iface.Name)
.WithBaseType(inheritsUnknown ? "Avalonia.MicroCom.IUnknown" : iface.Inherits)
.AddModifiers(Token(_visibility), Token(SyntaxKind.UnsafeKeyword), Token(SyntaxKind.PartialKeyword));
var proxyClassName = "__MicroCom" + iface.Name + "Proxy";
var proxy = ClassDeclaration(proxyClassName)
.AddModifiers(Token(SyntaxKind.UnsafeKeyword), Token(_visibility), Token(SyntaxKind.PartialKeyword))
.WithBaseType(inheritsUnknown ?
"Avalonia.MicroCom.MicroComProxyBase" :
("__MicroCom" + iface.Inherits + "Proxy"))
.AddBaseListTypes(SimpleBaseType(ParseTypeName(iface.Name)));
// Generate vtable
var vtbl = ClassDeclaration("__MicroCom" + iface.Name + "VTable")
.AddModifiers(Token(SyntaxKind.UnsafeKeyword));
vtbl = vtbl.WithBaseType(inheritsUnknown ?
"Avalonia.MicroCom.MicroComVtblBase" :
"__MicroCom" + iface.Inherits + "VTable");
var vtblCtor = new List<StatementSyntax>();
for (var idx = 0; idx < iface.Count; idx++)
GenerateInterfaceMember(iface[idx], ref ifaceDec, ref proxy, ref vtbl, vtblCtor, idx);
vtbl = vtbl.AddMembers(
ConstructorDeclaration(vtbl.Identifier.Text)
.AddModifiers(Token(SyntaxKind.PublicKeyword))
.WithBody(Block(vtblCtor))
)
.AddMembers(MethodDeclaration(ParseTypeName("void"), "__MicroComModuleInit")
.AddModifiers(Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.InternalKeyword))
.WithExpressionBody(ArrowExpressionClause(
ParseExpression("Avalonia.MicroCom.MicroComRuntime.RegisterVTable(typeof(" +
iface.Name + "), new " + vtbl.Identifier.Text + "().CreateVTable())")))
.WithSemicolonToken(Semicolon()));
// Finalize proxy code
proxy = proxy.AddMembers(
MethodDeclaration(ParseTypeName("void"), "__MicroComModuleInit")
.AddModifiers(Token(SyntaxKind.StaticKeyword), Token(SyntaxKind.InternalKeyword))
.WithBody(Block(
ParseStatement("Avalonia.MicroCom.MicroComRuntime.Register(typeof(" +
iface.Name + "), new Guid(\"" + guidString + "\"), (p, owns) => new " +
proxyClassName + "(p, owns));")
)))
.AddMembers(ParseMemberDeclaration("public " + proxyClassName +
"(IntPtr nativePointer, bool ownsHandle) : base(nativePointer, ownsHandle) {}"))
.AddMembers(ParseMemberDeclaration("protected override int VTableSize => base.VTableSize + " +
iface.Count + ";"));
ns = ns.AddMembers(RewriteMethodsToProperties(ifaceDec));
implNs = implNs.AddMembers(RewriteMethodsToProperties(proxy), RewriteMethodsToProperties(vtbl));
}
}
}

111
src/tools/MicroComGenerator/CSharpGen.Utils.cs

@ -1,111 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using MicroComGenerator.Ast;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Formatting;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace MicroComGenerator
{
public partial class CSharpGen
{
CompilationUnitSyntax Unit()
=> CompilationUnit().WithUsings(List(new[]
{
"System", "System.Text", "System.Collections", "System.Collections.Generic", "Avalonia.MicroCom"
}
.Concat(_extraUsings).Select(u => UsingDirective(IdentifierName(u)))));
string Format(CompilationUnitSyntax unit)
{
var cw = new AdhocWorkspace();
return
"#pragma warning disable 108\n" +
Microsoft.CodeAnalysis.Formatting.Formatter.Format(unit.NormalizeWhitespace(), cw, cw.Options
.WithChangedOption(CSharpFormattingOptions.NewLineForMembersInObjectInit, true)
.WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInObjectCollectionArrayInitializers,
true)
.WithChangedOption(CSharpFormattingOptions.NewLineForMembersInAnonymousTypes, true)
.WithChangedOption(CSharpFormattingOptions.NewLinesForBracesInMethods, true)
).ToFullString();
}
SyntaxToken Semicolon() => Token(SyntaxKind.SemicolonToken);
static VariableDeclarationSyntax DeclareVar(string type, string name,
ExpressionSyntax initializer = null)
=> VariableDeclaration(ParseTypeName(type),
SingletonSeparatedList(VariableDeclarator(name)
.WithInitializer(initializer == null ? null : EqualsValueClause(initializer))));
FieldDeclarationSyntax DeclareConstant(string type, string name, LiteralExpressionSyntax value)
=> FieldDeclaration(
VariableDeclaration(ParseTypeName(type),
SingletonSeparatedList(
VariableDeclarator(name).WithInitializer(EqualsValueClause(value))
))
).WithSemicolonToken(Semicolon())
.WithModifiers(TokenList(Token(SyntaxKind.PublicKeyword), Token(SyntaxKind.ConstKeyword)));
FieldDeclarationSyntax DeclareField(string type, string name, params SyntaxKind[] modifiers) =>
DeclareField(type, name, null, modifiers);
FieldDeclarationSyntax DeclareField(string type, string name, EqualsValueClauseSyntax initializer,
params SyntaxKind[] modifiers) =>
FieldDeclaration(
VariableDeclaration(ParseTypeName(type),
SingletonSeparatedList(
VariableDeclarator(name).WithInitializer(initializer))))
.WithSemicolonToken(Semicolon())
.WithModifiers(TokenList(modifiers.Select(x => Token(x))));
bool IsPropertyRewriteCandidate(MethodDeclarationSyntax method)
{
return
method.ReturnType.ToFullString() != "void"
&& method.Identifier.Text.StartsWith("Get")
&& method.ParameterList.Parameters.Count == 0;
}
TypeDeclarationSyntax RewriteMethodsToProperties<T>(T decl) where T : TypeDeclarationSyntax
{
var replace = new Dictionary<MethodDeclarationSyntax, PropertyDeclarationSyntax>();
foreach (var method in decl.Members.OfType<MethodDeclarationSyntax>().ToList())
{
if (IsPropertyRewriteCandidate(method))
{
var getter = AccessorDeclaration(SyntaxKind.GetAccessorDeclaration);
if (method.Body != null)
getter = getter.WithBody(method.Body);
else
getter = getter.WithSemicolonToken(Semicolon());
replace[method] = PropertyDeclaration(method.ReturnType,
method.Identifier.Text.Substring(3))
.WithModifiers(method.Modifiers).AddAccessorListAccessors(getter);
}
}
return decl.ReplaceNodes(replace.Keys, (m, m2) => replace[m]);
}
bool IsInterface(string name)
{
if (name == "IUnknown")
return true;
return _idl.Interfaces.Any(i => i.Name == name);
}
private bool IsInterface(AstTypeNode type) => IsInterface(type.Name);
}
}

155
src/tools/MicroComGenerator/CSharpGen.cs

@ -1,155 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.CompilerServices;
using MicroComGenerator.Ast;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
// ReSharper disable CoVariantArrayConversion
namespace MicroComGenerator
{
public partial class CSharpGen
{
private readonly AstIdlNode _idl;
private List<string> _extraUsings;
private string _namespace;
private SyntaxKind _visibility;
private LocalInteropHelper _localInterop = new LocalInteropHelper();
public CSharpGen(AstIdlNode idl)
{
_idl = idl.Clone();
new AstRewriter(_idl.Attributes.Where(a => a.Name == "clr-map")
.Select(x => x.Value.Trim().Split(' '))
.ToDictionary(x => x[0], x => x[1])
).VisitAst(_idl);
_extraUsings = _idl.Attributes.Where(u => u.Name == "clr-using").Select(u => u.Value).ToList();
_namespace = _idl.GetAttribute("clr-namespace");
var visibilityString = _idl.GetAttribute("clr-access");
if (visibilityString == "internal")
_visibility = SyntaxKind.InternalKeyword;
else if (visibilityString == "public")
_visibility = SyntaxKind.PublicKeyword;
else
throw new CodeGenException("Invalid clr-access attribute");
}
class AstRewriter : AstVisitor
{
private readonly Dictionary<string, string> _typeMap = new Dictionary<string, string>();
public AstRewriter(Dictionary<string, string> typeMap)
{
_typeMap = typeMap;
}
void ConvertIntPtr(AstTypeNode type)
{
if (type.Name == "void" && type.PointerLevel > 0)
{
type.Name = "IntPtr";
type.PointerLevel--;
}
}
protected override void VisitStructMember(AstStructMemberNode member)
{
if (member.HasAttribute("intptr"))
ConvertIntPtr(member.Type);
base.VisitStructMember(member);
}
protected override void VisitType(AstTypeNode type)
{
if (type.IsLink)
{
type.PointerLevel++;
type.IsLink = false;
}
if (_typeMap.TryGetValue(type.Name, out var mapped))
type.Name = mapped;
base.VisitType(type);
}
protected override void VisitArgument(AstInterfaceMemberArgumentNode argument)
{
if (argument.HasAttribute("intptr"))
{
if(argument.Name == "retOut")
Console.WriteLine();
ConvertIntPtr(argument.Type);
}
base.VisitArgument(argument);
}
protected override void VisitInterfaceMember(AstInterfaceMemberNode member)
{
if (member.HasAttribute("intptr"))
ConvertIntPtr(member.ReturnType);
if (member.HasAttribute("propget") && !member.Name.StartsWith("Get"))
member.Name = "Get" + member.Name;
if (member.HasAttribute("propput") && !member.Name.StartsWith("Set"))
member.Name = "Set" + member.Name;
base.VisitInterfaceMember(member);
}
}
public string Generate()
{
var ns = NamespaceDeclaration(ParseName(_namespace));
var implNs = NamespaceDeclaration(ParseName(_namespace + ".Impl"));
ns = GenerateEnums(ns);
ns = GenerateStructs(ns);
foreach (var i in _idl.Interfaces)
GenerateInterface(ref ns, ref implNs, i);
implNs = implNs.AddMembers(_localInterop.Class);
var unit = Unit().AddMembers(ns, implNs);
return Format(unit);
}
NamespaceDeclarationSyntax GenerateEnums(NamespaceDeclarationSyntax ns)
{
return ns.AddMembers(_idl.Enums.Select(e =>
{
var dec = EnumDeclaration(e.Name)
.WithModifiers(TokenList(Token(_visibility)))
.WithMembers(SeparatedList(e.Select(m =>
{
var member = EnumMemberDeclaration(m.Name);
if (m.Value != null)
return member.WithEqualsValue(EqualsValueClause(ParseExpression(m.Value)));
return member;
})));
if (e.HasAttribute("flags"))
dec = dec.AddAttribute("System.Flags");
return dec;
}).ToArray());
}
NamespaceDeclarationSyntax GenerateStructs(NamespaceDeclarationSyntax ns)
{
return ns.AddMembers(_idl.Structs.Select(e =>
StructDeclaration(e.Name)
.WithModifiers(TokenList(Token(_visibility)))
.AddAttribute("System.Runtime.InteropServices.StructLayout", "System.Runtime.InteropServices.LayoutKind.Sequential")
.AddModifiers(Token(SyntaxKind.UnsafeKeyword))
.WithMembers(new SyntaxList<MemberDeclarationSyntax>(SeparatedList(e.Select(m =>
DeclareField(m.Type.ToString(), m.Name, SyntaxKind.PublicKeyword)))))
).ToArray());
}
}
}

119
src/tools/MicroComGenerator/CppGen.cs

@ -1,119 +0,0 @@
using System;
using System.Linq;
using System.Text;
using MicroComGenerator.Ast;
namespace MicroComGenerator
{
public class CppGen
{
static string ConvertType(AstTypeNode type)
{
var name = type.Name;
if (name == "byte")
name = "unsigned char";
else if(name == "uint")
name = "unsigned int";
type = type.Clone();
type.Name = name;
return type.Format();
}
public static string GenerateCpp(AstIdlNode idl)
{
var sb = new StringBuilder();
var preamble = idl.GetAttributeOrDefault("cpp-preamble");
if (preamble != null)
sb.AppendLine(preamble);
foreach (var s in idl.Structs)
sb.AppendLine("struct " + s.Name + ";");
foreach (var s in idl.Interfaces)
sb.AppendLine("struct " + s.Name + ";");
foreach (var en in idl.Enums)
{
sb.Append("enum ");
if (en.Attributes.Any(a => a.Name == "class-enum"))
sb.Append("class ");
sb.AppendLine(en.Name).AppendLine("{");
foreach (var m in en)
{
sb.Append(" ").Append(m.Name);
if (m.Value != null)
sb.Append(" = ").Append(m.Value);
sb.AppendLine(",");
}
sb.AppendLine("};");
}
foreach (var s in idl.Structs)
{
sb.Append("struct ").AppendLine(s.Name).AppendLine("{");
foreach (var m in s)
sb.Append(" ").Append(ConvertType(m.Type)).Append(" ").Append(m.Name).AppendLine(";");
sb.AppendLine("};");
}
foreach (var i in idl.Interfaces)
{
var guidString = i.GetAttribute("uuid");
var guid = Guid.Parse(guidString).ToString().Replace("-", "");
sb.Append("COMINTERFACE(").Append(i.Name).Append(", ")
.Append(guid.Substring(0, 8)).Append(", ")
.Append(guid.Substring(8, 4)).Append(", ")
.Append(guid.Substring(12, 4));
for (var c = 0; c < 8; c++)
{
sb.Append(", ").Append(guid.Substring(16 + c * 2, 2));
}
sb.Append(") : ");
if (i.HasAttribute("cpp-virtual-inherits"))
sb.Append("virtual ");
sb.AppendLine(i.Inherits ?? "IUnknown")
.AppendLine("{");
foreach (var m in i)
{
sb.Append(" ")
.Append("virtual ")
.Append(ConvertType(m.ReturnType))
.Append(" ").Append(m.Name).Append(" (");
if (m.Count == 0)
sb.AppendLine(") = 0;");
else
{
sb.AppendLine();
for (var c = 0; c < m.Count; c++)
{
var arg = m[c];
sb.Append(" ");
if (arg.Attributes.Any(a => a.Name == "const"))
sb.Append("const ");
sb.Append(ConvertType(arg.Type))
.Append(" ")
.Append(arg.Name);
if (c != m.Count - 1)
sb.Append(", ");
sb.AppendLine();
}
sb.AppendLine(" ) = 0;");
}
}
sb.AppendLine("};");
}
return sb.ToString();
}
}
}

97
src/tools/MicroComGenerator/Extensions.cs

@ -1,97 +0,0 @@
using System;
using System.Linq;
using Microsoft.CodeAnalysis;
using Microsoft.CodeAnalysis.CSharp;
using Microsoft.CodeAnalysis.CSharp.Syntax;
using static Microsoft.CodeAnalysis.CSharp.SyntaxFactory;
namespace MicroComGenerator
{
public static class Extensions
{
public static ClassDeclarationSyntax AddModifiers(this ClassDeclarationSyntax cl, params SyntaxKind[] modifiers)
{
if (modifiers == null)
return cl;
return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray());
}
public static MethodDeclarationSyntax AddModifiers(this MethodDeclarationSyntax cl, params SyntaxKind[] modifiers)
{
if (modifiers == null)
return cl;
return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray());
}
public static PropertyDeclarationSyntax AddModifiers(this PropertyDeclarationSyntax cl, params SyntaxKind[] modifiers)
{
if (modifiers == null)
return cl;
return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray());
}
public static ConstructorDeclarationSyntax AddModifiers(this ConstructorDeclarationSyntax cl, params SyntaxKind[] modifiers)
{
if (modifiers == null)
return cl;
return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray());
}
public static AccessorDeclarationSyntax AddModifiers(this AccessorDeclarationSyntax cl, params SyntaxKind[] modifiers)
{
if (modifiers == null)
return cl;
return cl.AddModifiers(modifiers.Select(x => SyntaxFactory.Token(x)).ToArray());
}
public static string WithLowerFirst(this string s)
{
if (string.IsNullOrEmpty(s))
return s;
return char.ToLowerInvariant(s[0]) + s.Substring(1);
}
public static ExpressionSyntax MemberAccess(params string[] identifiers)
{
if (identifiers == null || identifiers.Length == 0)
throw new ArgumentException();
var expr = (ExpressionSyntax)IdentifierName(identifiers[0]);
for (var c = 1; c < identifiers.Length; c++)
expr = MemberAccess(expr, identifiers[c]);
return expr;
}
public static ExpressionSyntax MemberAccess(ExpressionSyntax expr, params string[] identifiers)
{
foreach (var i in identifiers)
expr = MemberAccess(expr, i);
return expr;
}
public static MemberAccessExpressionSyntax MemberAccess(ExpressionSyntax expr, string identifier) =>
MemberAccessExpression(SyntaxKind.SimpleMemberAccessExpression, expr, IdentifierName(identifier));
public static ClassDeclarationSyntax WithBaseType(this ClassDeclarationSyntax cl, string bt)
{
return cl.AddBaseListTypes(SimpleBaseType(SyntaxFactory.ParseTypeName(bt)));
}
public static InterfaceDeclarationSyntax WithBaseType(this InterfaceDeclarationSyntax cl, string bt)
{
return cl.AddBaseListTypes(SimpleBaseType(SyntaxFactory.ParseTypeName(bt)));
}
public static T AddAttribute<T>(this T member, string attribute, params string[] args) where T : MemberDeclarationSyntax
{
return (T)member.AddAttributeLists(AttributeList(SingletonSeparatedList(
Attribute(ParseName(attribute), AttributeArgumentList(
SeparatedList(args.Select(a => AttributeArgument(ParseExpression(a)))))))));
}
public static string StripPrefix(this string s, string prefix) => string.IsNullOrEmpty(s)
? s
: s.StartsWith(prefix)
? s.Substring(prefix.Length)
: s;
}
}

10
src/tools/MicroComGenerator/MicroComGenerator.csproj

@ -1,10 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net6.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CommandLineParser" Version="2.8.0" />
<PackageReference Include="Microsoft.CodeAnalysis.CSharp.Workspaces" Version="3.7.0" />
</ItemGroup>
</Project>

27
src/tools/MicroComGenerator/ParseException.cs

@ -1,27 +0,0 @@
using System;
namespace MicroComGenerator
{
class ParseException : Exception
{
public int Line { get; }
public int Position { get; }
public ParseException(string message, int line, int position) : base($"({line}, {position}) {message}")
{
Line = line;
Position = position;
}
public ParseException(string message, ref TokenParser parser) : this(message, parser.Line, parser.Position)
{
}
}
class CodeGenException : Exception
{
public CodeGenException(string message) : base(message)
{
}
}
}

52
src/tools/MicroComGenerator/Program.cs

@ -1,52 +0,0 @@
using System;
using System.IO;
using System.Linq;
using CommandLine;
namespace MicroComGenerator
{
class Program
{
public class Options
{
[Option('i', "input", Required = true, HelpText = "Input IDL file")]
public string Input { get; set; }
[Option("cpp", Required = false, HelpText = "C++ output file")]
public string CppOutput { get; set; }
[Option("cs", Required = false, HelpText = "C# output file")]
public string CSharpOutput { get; set; }
}
static int Main(string[] args)
{
var p = Parser.Default.ParseArguments<Options>(args);
if (p is NotParsed<Options>)
{
return 1;
}
var opts = ((Parsed<Options>)p).Value;
var text = File.ReadAllText(opts.Input);
var ast = AstParser.Parse(text);
if (opts.CppOutput != null)
File.WriteAllText(opts.CppOutput, CppGen.GenerateCpp(ast));
if (opts.CSharpOutput != null)
{
File.WriteAllText(opts.CSharpOutput, new CSharpGen(ast).Generate());
// HACK: Can't work out how to get the VS project system's fast up-to-date checks
// to ignore the generated code, so as a workaround set the write time to that of
// the input.
File.SetLastWriteTime(opts.CSharpOutput, File.GetLastWriteTime(opts.Input));
}
return 0;
}
}
}

417
src/tools/MicroComGenerator/TokenParser.cs

@ -1,417 +0,0 @@
using System;
using System.Globalization;
using System.IO;
namespace MicroComGenerator
{
internal ref struct TokenParser
{
private ReadOnlySpan<char> _s;
public int Position { get; private set; }
public int Line { get; private set; }
public TokenParser(ReadOnlySpan<char> s)
{
_s = s;
Position = 0;
Line = 0;
}
public void SkipWhitespace()
{
while (true)
{
if(_s.Length == 0)
return;
if (char.IsWhiteSpace(_s[0]))
Advance(1);
else if (_s[0] == '/' && _s.Length>1)
{
if (_s[1] == '/')
SkipOneLineComment();
else if (_s[1] == '*')
SkipMultiLineComment();
else
return;
}
else
return;
}
}
void SkipOneLineComment()
{
while (true)
{
if (_s.Length > 0 && _s[0] != '\n' && _s[0] != '\r')
Advance(1);
else
return;
}
}
void SkipMultiLineComment()
{
var l = Line;
var p = Position;
while (true)
{
if (_s.Length == 0)
throw new ParseException("No matched */ found for /*", l, p);
if (_s[0] == '*' && _s.Length > 1 && _s[1] == '/')
{
Advance(2);
return;
}
Advance(1);
}
}
static bool IsAlphaNumeric(char ch) => (ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') ||
(ch >= 'A' && ch <= 'Z');
public void Consume(char c)
{
if (!TryConsume(c))
throw new ParseException("Expected " + c, Line, Position);
}
public bool TryConsume(char c)
{
SkipWhitespace();
if (_s.Length == 0 || _s[0] != c)
return false;
Advance(1);
return true;
}
public bool TryConsume(string s)
{
SkipWhitespace();
if (_s.Length < s.Length)
return false;
for (var c = 0; c < s.Length; c++)
{
if (_s[c] != s[c])
return false;
}
Advance(s.Length);
return true;
}
public bool TryConsumeAny(ReadOnlySpan<char> chars, out char token)
{
SkipWhitespace();
token = default;
if (_s.Length == 0)
return false;
foreach (var c in chars)
{
if (c == _s[0])
{
token = c;
Advance(1);
return true;
}
}
return false;
}
public bool TryParseKeyword(string keyword)
{
SkipWhitespace();
if (keyword.Length > _s.Length)
return false;
for(var c=0; c<keyword.Length;c++)
if (keyword[c] != _s[c])
return false;
if (_s.Length > keyword.Length && IsAlphaNumeric(_s[keyword.Length]))
return false;
Advance(keyword.Length);
return true;
}
public bool TryParseKeywordLowerCase(string keywordInLowerCase)
{
SkipWhitespace();
if (keywordInLowerCase.Length > _s.Length)
return false;
for(var c=0; c<keywordInLowerCase.Length;c++)
if (keywordInLowerCase[c] != char.ToLowerInvariant(_s[c]))
return false;
if (_s.Length > keywordInLowerCase.Length && IsAlphaNumeric(_s[keywordInLowerCase.Length]))
return false;
Advance(keywordInLowerCase.Length);
return true;
}
public void Advance(int c)
{
while (c > 0)
{
if (_s[0] == '\n')
{
Line++;
Position = 0;
}
else
Position++;
_s = _s.Slice(1);
c--;
}
}
public int Length => _s.Length;
public bool Eof
{
get
{
SkipWhitespace();
return Length == 0;
}
}
public char Peek
{
get
{
if (_s.Length == 0)
throw new ParseException("Unexpected EOF", Line, Position);
return _s[0];
}
}
public string ParseIdentifier(ReadOnlySpan<char> extraValidChars)
{
if (!TryParseIdentifier(extraValidChars, out var ident))
throw new ParseException("Identifier expected", Line, Position);
return ident.ToString();
}
public string ParseIdentifier()
{
if (!TryParseIdentifier(out var ident))
throw new ParseException("Identifier expected", Line, Position);
return ident.ToString();
}
public bool TryParseIdentifier(ReadOnlySpan<char> extraValidChars, out ReadOnlySpan<char> res)
{
res = ReadOnlySpan<char>.Empty;
SkipWhitespace();
if (_s.Length == 0)
return false;
var first = _s[0];
if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z') || first == '_'))
return false;
int len = 1;
for (var c = 1; c < _s.Length; c++)
{
var ch = _s[c];
if (IsAlphaNumeric(ch) || ch == '_')
len++;
else
{
var found = false;
foreach(var vc in extraValidChars)
if (vc == ch)
{
found = true;
break;
}
if (found)
len++;
else
break;
}
}
res = _s.Slice(0, len);
Advance(len);
return true;
}
public bool TryParseIdentifier(out ReadOnlySpan<char> res)
{
res = ReadOnlySpan<char>.Empty;
SkipWhitespace();
if (_s.Length == 0)
return false;
var first = _s[0];
if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z') || first == '_'))
return false;
int len = 1;
for (var c = 1; c < _s.Length; c++)
{
var ch = _s[c];
if (IsAlphaNumeric(ch) || ch == '_')
len++;
else
break;
}
res = _s.Slice(0, len);
Advance(len);
return true;
}
public string ReadToEol()
{
var initial = _s;
var len = 0;
while (true)
{
if (_s.Length > 0 && _s[0] != '\n' && _s[0] != '\r')
{
len++;
Advance(1);
}
else
return initial.Slice(0, len).ToString();
}
}
public string ReadTo(char c)
{
var initial = _s;
var len = 0;
var l = Line;
var p = Position;
while (true)
{
if (_s.Length == 0)
throw new ParseException("Expected " + c + " before EOF", l, p);
if (_s[0] != c)
{
len++;
Advance(1);
}
else
return initial.Slice(0, len).ToString();
}
}
public string ReadToAny(ReadOnlySpan<char> chars)
{
var initial = _s;
var len = 0;
var l = Line;
var p = Position;
while (true)
{
if (_s.Length == 0)
throw new ParseException("Expected any of '" + chars.ToString() + "' before EOF", l, p);
var foundTerminator = false;
foreach (var term in chars)
{
if (_s[0] == term)
{
foundTerminator = true;
break;
}
}
if (!foundTerminator)
{
len++;
Advance(1);
}
else
return initial.Slice(0, len).ToString();
}
}
public bool TryParseCall(out ReadOnlySpan<char> res)
{
res = ReadOnlySpan<char>.Empty;
SkipWhitespace();
if (_s.Length == 0)
return false;
var first = _s[0];
if (!((first >= 'a' && first <= 'z') || (first >= 'A' && first <= 'Z')))
return false;
int len = 1;
for (var c = 1; c < _s.Length; c++)
{
var ch = _s[c];
if ((ch >= '0' && ch <= '9') || (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch<= 'Z') || ch == '.')
len++;
else
break;
}
res = _s.Slice(0, len);
// Find '('
for (var c = len; c < _s.Length; c++)
{
if(char.IsWhiteSpace(_s[c]))
continue;
if(_s[c]=='(')
{
Advance(c + 1);
return true;
}
return false;
}
return false;
}
public bool TryParseFloat(out float res)
{
res = 0;
SkipWhitespace();
if (_s.Length == 0)
return false;
var len = 0;
var dotCount = 0;
for (var c = 0; c < _s.Length; c++)
{
var ch = _s[c];
if (ch >= '0' && ch <= '9')
len = c + 1;
else if (ch == '.' && dotCount == 0)
{
len = c + 1;
dotCount++;
}
else
break;
}
var span = _s.Slice(0, len);
#if NETSTANDARD2_0
if (!float.TryParse(span.ToString(), NumberStyles.Number, CultureInfo.InvariantCulture, out res))
return false;
#else
if (!float.TryParse(span, NumberStyles.Number, CultureInfo.InvariantCulture, out res))
return false;
#endif
Advance(len);
return true;
}
public override string ToString() => _s.ToString();
}
}

2
tests/Avalonia.Input.UnitTests/TouchDeviceTests.cs

@ -209,7 +209,7 @@ namespace Avalonia.Input.UnitTests
var unitTestApp = UnitTestApplication.Start( var unitTestApp = UnitTestApplication.Start(
new TestServices(inputManager: new InputManager())); new TestServices(inputManager: new InputManager()));
var iSettingsMock = new Mock<IPlatformSettings>(); var iSettingsMock = new Mock<IPlatformSettings>();
iSettingsMock.Setup(x => x.DoubleClickTime).Returns(doubleClickTime); iSettingsMock.Setup(x => x.TouchDoubleClickTime).Returns(doubleClickTime);
AvaloniaLocator.CurrentMutable.BindToSelf(this) AvaloniaLocator.CurrentMutable.BindToSelf(this)
.Bind<IPlatformSettings>().ToConstant(iSettingsMock.Object); .Bind<IPlatformSettings>().ToConstant(iSettingsMock.Object);
return unitTestApp; return unitTestApp;

6
tests/Avalonia.UnitTests/ImmediateDispatcher.cs

@ -21,6 +21,12 @@ namespace Avalonia.UnitTests
action(); action();
} }
/// <inheritdoc/>
public void Post<T>(Action<T> action, T arg, DispatcherPriority priority = DispatcherPriority.Normal)
{
action(arg);
}
/// <inheritdoc/> /// <inheritdoc/>
public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal)
{ {

Loading…
Cancel
Save