Browse Source
commit345ff4417eAuthor: Dan Walmsley <dan@walms.co.uk> Date: Wed Mar 2 18:31:16 2022 +0000 bump version. commit892960c83dAuthor: Max Katz <maxkatz6@outlook.com> Date: Wed Mar 2 14:18:06 2022 -0400 Merge pull request #7736 from AvaloniaUI/fixes/mac-os-set-window-title-null allow setting the window title to null on osx. commitd8848478eaAuthor: Dan Walmsley <dan@walms.co.uk> Date: Wed Mar 2 16:48:43 2022 +0000 Merge pull request #7723 from AvaloniaUI/fix-expander Fix expander header stretching commit2eb51b8672Author: Dan Walmsley <dan@walms.co.uk> Date: Wed Mar 2 14:21:55 2022 +0000 Merge pull request #7730 from AvaloniaUI/fixes/7582-centerowner-minimized Fall back from CenterOwner to CenterScreen when owner window is minimized. commit84f04f429bAuthor: Max Katz <maxkatz6@outlook.com> Date: Sun Feb 20 14:31:19 2022 -0500 Merge pull request #7660 from timunie/fix/gh-7636 Add missing call to base class in ReactiveUserControl.OnDataContextChanged commit5b4f5da127Author: Max Katz <maxkatz6@outlook.com> Date: Sun Feb 20 14:09:49 2022 -0500 Merge pull request #7658 from trympet/7657-fix-brush-opacity-animation fix brush opacity animation commitd5359603acAuthor: Max Katz <maxkatz6@outlook.com> Date: Fri Feb 18 20:18:16 2022 -0500 Merge pull request #7645 from timunie/fix/CalendarDatePickerBindingMode Change default binding mode of SelectedDateProperty to TwoWay # Conflicts: # src/Avalonia.Controls/Calendar/CalendarDatePicker.cs commit480dfdfe0dAuthor: Nikita Tsukanov <keks9n@gmail.com> Date: Wed Dec 29 16:26:59 2021 +0300 Merge pull request #7259 from AvaloniaUI/features/use-external-microcom-generator Use microcom generator from nuget # Conflicts: # build/MicroCom.targets commit3f11b014a6Author: Max Katz <maxkatz6@outlook.com> Date: Wed Jan 26 15:21:00 2022 -0500 Merge pull request #7440 from emmauss/diagnostic-key Ensure Control Inspection in Diagnostics tool window is triggered on Key Down commit870f62fb8fAuthor: Tako <53405089+Takoooooo@users.noreply.github.com> Date: Sat Jan 29 17:35:18 2022 +0200 Merge pull request #7449 from emmauss/fluent-compact Add DensityStyle property in Fluent Theme provider commit7d771c86e6Author: Nikita Tsukanov <keks9n@gmail.com> Date: Fri Jan 28 12:38:54 2022 +0300 Merge pull request #7455 from Mikolaytis/DeadlockFix [Deadlock] Fix Monitor.Enter in finally commit859793b122Author: Max Katz <maxkatz6@outlook.com> Date: Sun Jan 30 18:46:45 2022 -0500 Merge pull request #7475 from wieslawsoltes/ViewboxPageXamlOnly [ControlCatalog] Set ComboBox items from xaml on ViewboxPage commit256bba51f7Author: Max Katz <maxkatz6@outlook.com> Date: Sun Jan 30 17:30:39 2022 -0500 Merge pull request #7484 from wieslawsoltes/ClippingPageXamlOnly [RenderDemo] Set Border clip from xaml on ClippingPage commit039fa5b7adAuthor: Max Katz <maxkatz6@outlook.com> Date: Tue Feb 15 15:22:22 2022 -0500 Merge pull request #7520 from timunie/fix/ScrollViewerShiftAndPointerWheel Fix [Shift] + [PointerWheel] should scroll horizontally commit2c53e06c26Author: Olivier DALET <olivier.dalet@addupsolutions.com> Date: Fri Feb 4 17:55:12 2022 +0100 Fix #7519 - Reset fb and depth buffer Ids once they are deleted commit8bc795be04Author: Andrii Kurdiumov <kant2002@gmail.com> Date: Sat Feb 5 17:51:15 2022 +0600 Fix AOT incompatible code (#7534) * Fix AOT incompatible code Use code patterns which are AOT-friendly. That improves R2R and Native AOT scenarios commit7b4462163aAuthor: Dan Walmsley <dan@walms.co.uk> Date: Mon Feb 7 22:39:02 2022 +0000 Merge pull request #7537 from fr-Pursuit/master Minimization bugfix on Windows commit80319bf257Author: Nikita Tsukanov <keks9n@gmail.com> Date: Mon Feb 7 22:13:51 2022 +0300 Merge pull request #7548 from ahopper/fix-32bit-linux-session-manger-pinvoke fix 32 bit raspberry pi session manager seg fault commite89b6dcbc1Author: Steve <hez2010@outlook.com> Date: Tue Feb 8 20:30:48 2022 +0800 Fix COM issue in Cursor (#7551) Bump S.D.Common to 6.0.0 for non-netstandard2.0 builds commit4dc4c993deAuthor: Dariusz Komosiński <darek.komosinski@gmail.com> Date: Wed Feb 9 14:41:07 2022 +0100 Merge pull request #7569 from MarchingCube/win32-filepicker-no-exceptions Avoid using COM exceptions for dialog control flow. commit03bc5a4752Author: Max Katz <maxkatz6@outlook.com> Date: Thu Feb 10 22:17:17 2022 -0500 Merge pull request #7576 from pr8x/button-flyout-diagnostics DevTools: Enable inspection for Button.Flyout commitbfff7d9e01Author: Dan Walmsley <dan@walms.co.uk> Date: Mon Feb 21 21:15:56 2022 +0000 Merge pull request #7622 from pr8x/child-window-property2 Exposing `Window.ChildWindows` collection commitda3004d1d6Author: Dan Walmsley <dan@walms.co.uk> Date: Wed Feb 16 22:24:27 2022 +0000 Merge pull request #7628 from wieslawsoltes/UpdateNuGetPackageDescription Update PackageDescription for NuGet commit4b6d1223dbAuthor: Max Katz <maxkatz6@outlook.com> Date: Thu Feb 17 10:22:03 2022 -0500 Merge pull request #7634 from AvaloniaUI/fixes/7633-date-time-picker-popup Fix Date/Time picker popups # Conflicts: # src/Avalonia.Controls/DateTimePickers/DatePicker.cs # src/Avalonia.Controls/DateTimePickers/TimePicker.cs commit0419426061Merge:645ce6adaa9d683bb8Author: Dan Walmsley <dan@walms.co.uk> Date: Wed Feb 16 13:46:20 2022 +0000 Merge branch 'stable/0.10.x' of https://github.com/AvaloniaUI/Avalonia into stable/0.10.x commit645ce6ada3Author: Jumar Macato <16554748+jmacato@users.noreply.github.com> Date: Wed Feb 16 21:42:16 2022 +0800 Merge pull request #7611 from AvaloniaUI/feature/transitioning-content-control add transitioning content control. # Conflicts: # src/Avalonia.Themes.Default/DefaultTheme.xaml # src/Avalonia.Themes.Default/TransitioningContentControl.xaml commita9d683bb8fAuthor: Nikita Tsukanov <keks9n@gmail.com> Date: Sat Feb 12 15:25:42 2022 +0300 Introduced RawPointerPoint for usage with IntermediatePoints (#7581) Introduced RawPointerPoint for usage with IntermediatePoints commit8b3b65496fAuthor: Max Katz <maxkatz6@outlook.com> Date: Sun Jan 23 19:55:40 2022 -0500 Merge pull request #7413 from AvaloniaUI/feature/intermediate-points Added GetIntermediatePoints support for X11, libinput and evdev # Conflicts: # src/Avalonia.Base/Threading/JobRunner.cs commitd22e627112Author: Dan Walmsley <dan@walms.co.uk> Date: Tue Feb 15 17:45:22 2022 +0000 Merge pull request #7605 from AvaloniaUI/feature/skia-layering-extensions Add Skia Helper Methods to allow applying Skia Filter Effects (Blur, DropShadow, Lighting) to DC content # Conflicts: # build/SharedVersion.props
75 changed files with 1151 additions and 2259 deletions
@ -1,34 +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> |
|||
</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 "$(MSBuildThisFileDirectory)../src/tools/MicroComGenerator/bin/$(Configuration)/net6.0/MicroComGenerator.dll" -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> |
|||
@ -1,5 +1,6 @@ |
|||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003"> |
|||
<ItemGroup> |
|||
<PackageReference Include="System.Drawing.Common" Version="4.5.0" /> |
|||
<PackageReference Condition="'$(TargetFramework)'!='netstandard2.0'" Include="System.Drawing.Common" Version="6.0.0" /> |
|||
<PackageReference Condition="'$(TargetFramework)'=='netstandard2.0'" Include="System.Drawing.Common" Version="4.5.0" /> |
|||
</ItemGroup> |
|||
</Project> |
|||
|
|||
@ -1,14 +1,14 @@ |
|||
using System.IO; |
|||
using MicroComGenerator; |
|||
using MicroCom.CodeGenerator; |
|||
using Nuke.Common; |
|||
|
|||
partial class Build : NukeBuild |
|||
{ |
|||
Target GenerateCppHeaders => _ => _.Executes(() => |
|||
{ |
|||
var text = File.ReadAllText(RootDirectory / "src" / "Avalonia.Native" / "avn.idl"); |
|||
var ast = AstParser.Parse(text); |
|||
var file = MicroComCodeGenerator.Parse( |
|||
File.ReadAllText(RootDirectory / "src" / "Avalonia.Native" / "avn.idl")); |
|||
File.WriteAllText(RootDirectory / "native" / "Avalonia.Native" / "inc" / "avalonia-native.h", |
|||
CppGen.GenerateCpp(ast)); |
|||
file.GenerateCppHeader()); |
|||
}); |
|||
} |
|||
@ -1,35 +1,18 @@ |
|||
using System; |
|||
using System.Reactive.Linq; |
|||
using Avalonia; |
|||
using Avalonia.Animation; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Data; |
|||
using Avalonia.Markup.Xaml; |
|||
using Avalonia.Media; |
|||
|
|||
namespace RenderDemo.Pages |
|||
{ |
|||
public class ClippingPage : UserControl |
|||
{ |
|||
private Geometry _clip; |
|||
|
|||
public ClippingPage() |
|||
{ |
|||
InitializeComponent(); |
|||
WireUpCheckbox(); |
|||
} |
|||
|
|||
private void InitializeComponent() |
|||
{ |
|||
AvaloniaXamlLoader.Load(this); |
|||
} |
|||
|
|||
private void WireUpCheckbox() |
|||
{ |
|||
var useMask = this.FindControl<CheckBox>("useMask"); |
|||
var clipped = this.FindControl<Border>("clipped"); |
|||
_clip = clipped.Clip; |
|||
useMask.Click += (s, e) => clipped.Clip = clipped.Clip == null ? _clip : null; |
|||
} |
|||
} |
|||
} |
|||
|
|||
@ -0,0 +1,96 @@ |
|||
using System; |
|||
using System.Threading; |
|||
using Avalonia.Animation; |
|||
using Avalonia.Controls.Templates; |
|||
using Avalonia.Threading; |
|||
|
|||
namespace Avalonia.Controls; |
|||
|
|||
/// <summary>
|
|||
/// Displays <see cref="ContentControl.Content"/> according to a <see cref="FuncDataTemplate"/>.
|
|||
/// Uses <see cref="PageTransition"/> to move between the old and new content values.
|
|||
/// </summary>
|
|||
public class TransitioningContentControl : ContentControl |
|||
{ |
|||
private CancellationTokenSource? _lastTransitionCts; |
|||
private object? _currentContent; |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="PageTransition"/> property.
|
|||
/// </summary>
|
|||
public static readonly StyledProperty<IPageTransition?> PageTransitionProperty = |
|||
AvaloniaProperty.Register<TransitioningContentControl, IPageTransition?>(nameof(PageTransition), |
|||
new CrossFade(TimeSpan.FromSeconds(0.125))); |
|||
|
|||
/// <summary>
|
|||
/// Defines the <see cref="CurrentContent"/> property.
|
|||
/// </summary>
|
|||
public static readonly DirectProperty<TransitioningContentControl, object?> CurrentContentProperty = |
|||
AvaloniaProperty.RegisterDirect<TransitioningContentControl, object?>(nameof(CurrentContent), |
|||
o => o.CurrentContent); |
|||
|
|||
/// <summary>
|
|||
/// Gets or sets the animation played when content appears and disappears.
|
|||
/// </summary>
|
|||
public IPageTransition? PageTransition |
|||
{ |
|||
get => GetValue(PageTransitionProperty); |
|||
set => SetValue(PageTransitionProperty, value); |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Gets the content currently displayed on the screen.
|
|||
/// </summary>
|
|||
public object? CurrentContent |
|||
{ |
|||
get => _currentContent; |
|||
private set => SetAndRaise(CurrentContentProperty, ref _currentContent, value); |
|||
} |
|||
|
|||
protected override void OnAttachedToVisualTree(VisualTreeAttachmentEventArgs e) |
|||
{ |
|||
base.OnAttachedToVisualTree(e); |
|||
|
|||
Dispatcher.UIThread.Post(() => UpdateContentWithTransition(Content)); |
|||
} |
|||
|
|||
protected override void OnDetachedFromVisualTree(VisualTreeAttachmentEventArgs e) |
|||
{ |
|||
base.OnDetachedFromVisualTree(e); |
|||
|
|||
_lastTransitionCts?.Cancel(); |
|||
} |
|||
|
|||
protected override void OnPropertyChanged<T>(AvaloniaPropertyChangedEventArgs<T> change) |
|||
{ |
|||
base.OnPropertyChanged(change); |
|||
|
|||
if (change.Property == ContentProperty) |
|||
{ |
|||
Dispatcher.UIThread.Post(() => UpdateContentWithTransition(Content)); |
|||
} |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Updates the content with transitions.
|
|||
/// </summary>
|
|||
/// <param name="content">New content to set.</param>
|
|||
private async void UpdateContentWithTransition(object? content) |
|||
{ |
|||
if (VisualRoot is null) |
|||
{ |
|||
return; |
|||
} |
|||
|
|||
_lastTransitionCts?.Cancel(); |
|||
_lastTransitionCts = new CancellationTokenSource(); |
|||
|
|||
if (PageTransition != null) |
|||
await PageTransition.Start(this, null, true, _lastTransitionCts.Token); |
|||
|
|||
CurrentContent = content; |
|||
|
|||
if (PageTransition != null) |
|||
await PageTransition.Start(null, this, true, _lastTransitionCts.Token); |
|||
} |
|||
} |
|||
@ -0,0 +1,20 @@ |
|||
<Styles xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
<Style Selector="TransitioningContentControl"> |
|||
<!-- Set Defaults --> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<ContentPresenter Name="PART_ContentPresenter" |
|||
Background="{TemplateBinding Background}" |
|||
BorderBrush="{TemplateBinding BorderBrush}" |
|||
BorderThickness="{TemplateBinding BorderThickness}" |
|||
CornerRadius="{TemplateBinding CornerRadius}" |
|||
ContentTemplate="{TemplateBinding ContentTemplate}" |
|||
Content="{TemplateBinding CurrentContent}" |
|||
Padding="{TemplateBinding Padding}" |
|||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" |
|||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"/> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
</Styles> |
|||
@ -0,0 +1,20 @@ |
|||
<Styles xmlns="https://github.com/avaloniaui" |
|||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> |
|||
<Style Selector="TransitioningContentControl"> |
|||
<!-- Set Defaults --> |
|||
<Setter Property="Template"> |
|||
<ControlTemplate> |
|||
<ContentPresenter Name="PART_ContentPresenter" |
|||
Background="{TemplateBinding Background}" |
|||
BorderBrush="{TemplateBinding BorderBrush}" |
|||
BorderThickness="{TemplateBinding BorderThickness}" |
|||
CornerRadius="{TemplateBinding CornerRadius}" |
|||
ContentTemplate="{TemplateBinding ContentTemplate}" |
|||
Content="{TemplateBinding CurrentContent}" |
|||
Padding="{TemplateBinding Padding}" |
|||
VerticalContentAlignment="{TemplateBinding VerticalContentAlignment}" |
|||
HorizontalContentAlignment="{TemplateBinding HorizontalContentAlignment}"/> |
|||
</ControlTemplate> |
|||
</Setter> |
|||
</Style> |
|||
</Styles> |
|||
@ -0,0 +1,43 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Input.Raw; |
|||
using Avalonia.Threading; |
|||
|
|||
namespace Avalonia.LinuxFramebuffer.Input; |
|||
|
|||
internal class RawEventGroupingThreadingHelper : IDisposable |
|||
{ |
|||
private readonly RawEventGrouper _grouper; |
|||
private readonly Queue<RawInputEventArgs> _rawQueue = new(); |
|||
private readonly Action _queueHandler; |
|||
|
|||
public RawEventGroupingThreadingHelper(Action<RawInputEventArgs> eventCallback) |
|||
{ |
|||
_grouper = new RawEventGrouper(eventCallback); |
|||
_queueHandler = QueueHandler; |
|||
} |
|||
|
|||
private void QueueHandler() |
|||
{ |
|||
lock (_rawQueue) |
|||
{ |
|||
while (_rawQueue.Count > 0) |
|||
_grouper.HandleEvent(_rawQueue.Dequeue()); |
|||
} |
|||
} |
|||
|
|||
public void OnEvent(RawInputEventArgs args) |
|||
{ |
|||
lock (_rawQueue) |
|||
{ |
|||
_rawQueue.Enqueue(args); |
|||
if (_rawQueue.Count == 1) |
|||
{ |
|||
Dispatcher.UIThread.Post(_queueHandler, DispatcherPriority.Input); |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void Dispose() => |
|||
Dispatcher.UIThread.Post(() => _grouper.Dispose(), DispatcherPriority.Input + 1); |
|||
} |
|||
@ -0,0 +1,10 @@ |
|||
namespace System.Runtime.CompilerServices |
|||
{ |
|||
#if !NET5_0_OR_GREATER
|
|||
internal class ModuleInitializerAttribute : Attribute |
|||
{ |
|||
|
|||
} |
|||
#endif
|
|||
} |
|||
|
|||
@ -0,0 +1,133 @@ |
|||
#nullable enable |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using Avalonia.Collections.Pooled; |
|||
using Avalonia.Input; |
|||
using Avalonia.Input.Raw; |
|||
using Avalonia.Threading; |
|||
using JetBrains.Annotations; |
|||
|
|||
namespace Avalonia; |
|||
|
|||
/* |
|||
This helper maintains an input queue for backends that handle input asynchronously. |
|||
While doing that it groups Move and TouchUpdate events so we could provide GetIntermediatePoints API |
|||
*/ |
|||
|
|||
internal class RawEventGrouper : IDisposable |
|||
{ |
|||
private readonly Action<RawInputEventArgs> _eventCallback; |
|||
private readonly Queue<RawInputEventArgs> _inputQueue = new(); |
|||
private readonly Action _dispatchFromQueue; |
|||
readonly Dictionary<long, RawTouchEventArgs> _lastTouchPoints = new(); |
|||
RawInputEventArgs? _lastEvent; |
|||
|
|||
public RawEventGrouper(Action<RawInputEventArgs> eventCallback) |
|||
{ |
|||
_eventCallback = eventCallback; |
|||
_dispatchFromQueue = DispatchFromQueue; |
|||
} |
|||
|
|||
private void AddToQueue(RawInputEventArgs args) |
|||
{ |
|||
_lastEvent = args; |
|||
_inputQueue.Enqueue(args); |
|||
if (_inputQueue.Count == 1) |
|||
Dispatcher.UIThread.Post(_dispatchFromQueue, DispatcherPriority.Input); |
|||
} |
|||
|
|||
private void DispatchFromQueue() |
|||
{ |
|||
while (true) |
|||
{ |
|||
if(_inputQueue.Count == 0) |
|||
return; |
|||
|
|||
var ev = _inputQueue.Dequeue(); |
|||
|
|||
if (_lastEvent == ev) |
|||
_lastEvent = null; |
|||
|
|||
if (ev is RawTouchEventArgs { Type: RawPointerEventType.TouchUpdate } touchUpdate) |
|||
_lastTouchPoints.Remove(touchUpdate.TouchPointId); |
|||
|
|||
_eventCallback?.Invoke(ev); |
|||
|
|||
if (ev is RawPointerEventArgs { IntermediatePoints.Value: PooledList<RawPointerPoint> list }) |
|||
list.Dispose(); |
|||
|
|||
if (Dispatcher.UIThread.HasJobsWithPriority(DispatcherPriority.Input + 1)) |
|||
{ |
|||
Dispatcher.UIThread.Post(_dispatchFromQueue, DispatcherPriority.Input); |
|||
return; |
|||
} |
|||
} |
|||
} |
|||
|
|||
public void HandleEvent(RawInputEventArgs args) |
|||
{ |
|||
/* |
|||
Try to update already enqueued events if |
|||
1) they are still not handled (_lastEvent and _lastTouchPoints shouldn't contain said event in that case) |
|||
2) previous event belongs to the same "event block", events in the same block: |
|||
- belong from the same device |
|||
- are pointer move events (Move/TouchUpdate) |
|||
- have the same type |
|||
- have same modifiers |
|||
|
|||
Even if nothing is updated and the event is actually enqueued, we need to update the relevant tracking info |
|||
*/ |
|||
if ( |
|||
args is RawPointerEventArgs pointerEvent |
|||
&& _lastEvent != null |
|||
&& _lastEvent.Device == args.Device |
|||
&& _lastEvent is RawPointerEventArgs lastPointerEvent |
|||
&& lastPointerEvent.InputModifiers == pointerEvent.InputModifiers |
|||
&& lastPointerEvent.Type == pointerEvent.Type |
|||
&& lastPointerEvent.Type is RawPointerEventType.Move or RawPointerEventType.TouchUpdate) |
|||
{ |
|||
if (args is RawTouchEventArgs touchEvent) |
|||
{ |
|||
if (_lastTouchPoints.TryGetValue(touchEvent.TouchPointId, out var lastTouchEvent)) |
|||
MergeEvents(lastTouchEvent, touchEvent); |
|||
else |
|||
{ |
|||
_lastTouchPoints[touchEvent.TouchPointId] = touchEvent; |
|||
AddToQueue(touchEvent); |
|||
} |
|||
} |
|||
else |
|||
MergeEvents(lastPointerEvent, pointerEvent); |
|||
|
|||
return; |
|||
} |
|||
else |
|||
{ |
|||
_lastTouchPoints.Clear(); |
|||
if (args is RawTouchEventArgs { Type: RawPointerEventType.TouchUpdate } touchEvent) |
|||
_lastTouchPoints[touchEvent.TouchPointId] = touchEvent; |
|||
} |
|||
AddToQueue(args); |
|||
} |
|||
|
|||
private static IReadOnlyList<RawPointerPoint> GetPooledList() => new PooledList<RawPointerPoint>(); |
|||
private static readonly Func<IReadOnlyList<RawPointerPoint>> s_getPooledListDelegate = GetPooledList; |
|||
|
|||
private static void MergeEvents(RawPointerEventArgs last, RawPointerEventArgs current) |
|||
{ |
|||
|
|||
last.IntermediatePoints ??= new Lazy<IReadOnlyList<RawPointerPoint>?>(s_getPooledListDelegate); |
|||
((PooledList<RawPointerPoint>)last.IntermediatePoints.Value!).Add(new RawPointerPoint { Position = last.Position }); |
|||
last.Position = current.Position; |
|||
last.Timestamp = current.Timestamp; |
|||
last.InputModifiers = current.InputModifiers; |
|||
} |
|||
|
|||
public void Dispose() |
|||
{ |
|||
_inputQueue.Clear(); |
|||
_lastEvent = null; |
|||
_lastTouchPoints.Clear(); |
|||
} |
|||
} |
|||
|
|||
@ -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() |
|||
}; |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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)); |
|||
} |
|||
} |
|||
} |
|||
@ -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); |
|||
|
|||
} |
|||
} |
|||
@ -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()); |
|||
} |
|||
|
|||
|
|||
|
|||
} |
|||
} |
|||
@ -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(); |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
@ -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> |
|||
@ -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) |
|||
{ |
|||
} |
|||
} |
|||
} |
|||
@ -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; |
|||
} |
|||
} |
|||
} |
|||
@ -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(); |
|||
|
|||
} |
|||
} |
|||
@ -0,0 +1,54 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Linq; |
|||
using System.Text; |
|||
using System.Threading.Tasks; |
|||
using Avalonia.Controls; |
|||
using Avalonia.Media; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Animation.UnitTests |
|||
{ |
|||
public class BrushTransitionTests |
|||
{ |
|||
[Fact] |
|||
public void SolidColorBrush_Opacity_IsInteroplated() |
|||
{ |
|||
Test(0, new SolidColorBrush { Opacity = 0 }, new SolidColorBrush { Opacity = 0 }); |
|||
Test(0, new SolidColorBrush { Opacity = 0 }, new SolidColorBrush { Opacity = 1 }); |
|||
Test(0.5, new SolidColorBrush { Opacity = 0 }, new SolidColorBrush { Opacity = 1 }); |
|||
Test(0.5, new SolidColorBrush { Opacity = 0.5 }, new SolidColorBrush { Opacity = 0.5 }); |
|||
Test(1, new SolidColorBrush { Opacity = 1 }, new SolidColorBrush { Opacity = 1 }); |
|||
// TODO: investigate why this case fails.
|
|||
//Test2(1, new SolidColorBrush { Opacity = 0 }, new SolidColorBrush { Opacity = 1 });
|
|||
} |
|||
|
|||
[Fact] |
|||
public void LinearGradientBrush_Opacity_IsInteroplated() |
|||
{ |
|||
Test(0, new LinearGradientBrush { Opacity = 0 }, new LinearGradientBrush { Opacity = 0 }); |
|||
Test(0, new LinearGradientBrush { Opacity = 0 }, new LinearGradientBrush { Opacity = 1 }); |
|||
Test(0.5, new LinearGradientBrush { Opacity = 0 }, new LinearGradientBrush { Opacity = 1 }); |
|||
Test(0.5, new LinearGradientBrush { Opacity = 0.5 }, new LinearGradientBrush { Opacity = 0.5 }); |
|||
Test(1, new LinearGradientBrush { Opacity = 1 }, new LinearGradientBrush { Opacity = 1 }); |
|||
} |
|||
|
|||
private static void Test(double progress, IBrush oldBrush, IBrush newBrush) |
|||
{ |
|||
var clock = new TestClock(); |
|||
var border = new Border() { Background = oldBrush }; |
|||
BrushTransition sut = new BrushTransition |
|||
{ |
|||
Duration = TimeSpan.FromSeconds(1), |
|||
Property = Border.BackgroundProperty |
|||
}; |
|||
|
|||
sut.Apply(border, clock, oldBrush, newBrush); |
|||
clock.Pulse(TimeSpan.Zero); |
|||
clock.Pulse(sut.Duration * progress); |
|||
|
|||
Assert.NotNull(border.Background); |
|||
Assert.Equal(oldBrush.Opacity + (newBrush.Opacity - oldBrush.Opacity) * progress, border.Background.Opacity); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue