From da38b72a35149bc83fe4fb5acb1d828320baccfe Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 20 Jul 2021 15:17:35 +0200 Subject: [PATCH 01/20] fixes(DevTools): Renamed Type as AssignedType --- .../Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs | 8 ++++---- .../Diagnostics/ViewModels/ClrPropertyViewModel.cs | 6 +++--- .../Diagnostics/ViewModels/PropertyViewModel.cs | 2 +- .../Diagnostics/Views/ControlDetailsView.xaml | 2 +- 4 files changed, 9 insertions(+), 9 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs index e4c4ca6115..6bd26e39d6 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs @@ -3,7 +3,7 @@ namespace Avalonia.Diagnostics.ViewModels internal class AvaloniaPropertyViewModel : PropertyViewModel { private readonly AvaloniaObject _target; - private string _type; + private string _assignedType; private object? _value; private string _priority; private string _group; @@ -32,7 +32,7 @@ namespace Avalonia.Diagnostics.ViewModels public override string Priority => _priority; - public override string Type => _type; + public override string AssignedType => _assignedType; public override string Value { @@ -56,7 +56,7 @@ namespace Avalonia.Diagnostics.ViewModels if (Property.IsDirect) { RaiseAndSetIfChanged(ref _value, _target.GetValue(Property), nameof(Value)); - RaiseAndSetIfChanged(ref _type, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(Type)); + RaiseAndSetIfChanged(ref _assignedType, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(AssignedType)); RaiseAndSetIfChanged(ref _priority, "Direct", nameof(Priority)); _group = "Properties"; @@ -66,7 +66,7 @@ namespace Avalonia.Diagnostics.ViewModels var val = _target.GetDiagnostic(Property); RaiseAndSetIfChanged(ref _value, val?.Value, nameof(Value)); - RaiseAndSetIfChanged(ref _type, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(Type)); + RaiseAndSetIfChanged(ref _assignedType, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(AssignedType)); if (val != null) { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs index 65626aeea5..b87d2b0b53 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs @@ -5,7 +5,7 @@ namespace Avalonia.Diagnostics.ViewModels internal class ClrPropertyViewModel : PropertyViewModel { private readonly object _target; - private string _type; + private string _assignedType; private object? _value; #nullable disable @@ -33,7 +33,7 @@ namespace Avalonia.Diagnostics.ViewModels public override string Name { get; } public override string Group => "CLR Properties"; - public override string Type => _type; + public override string AssignedType => _assignedType; public override string Value { @@ -60,7 +60,7 @@ namespace Avalonia.Diagnostics.ViewModels { var val = Property.GetValue(_target); RaiseAndSetIfChanged(ref _value, val, nameof(Value)); - RaiseAndSetIfChanged(ref _type, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(Type)); + RaiseAndSetIfChanged(ref _assignedType, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(AssignedType)); } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs index fdbd8c1aa3..080eab072c 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs @@ -14,7 +14,7 @@ namespace Avalonia.Diagnostics.ViewModels public abstract object Key { get; } public abstract string Name { get; } public abstract string Group { get; } - public abstract string Type { get; } + public abstract string AssignedType { get; } public abstract string Value { get; set; } public abstract string Priority { get; } public abstract bool? IsAttached { get; } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml index 4b37438993..22ad3374ed 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml @@ -33,7 +33,7 @@ - + From 4c3666ba214a9926d6468eb912ec4ea5e424b1d7 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 20 Jul 2021 16:18:36 +0200 Subject: [PATCH 02/20] feat(DevTools): Add PropertyType on ControlDettails --- .../ViewModels/AvaloniaPropertyViewModel.cs | 4 +++ .../ViewModels/ClrPropertyViewModel.cs | 4 +++ .../ViewModels/PropertyViewModel.cs | 26 ++++++++++++++++--- .../Diagnostics/Views/ControlDetailsView.xaml | 1 + 4 files changed, 32 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs index 6bd26e39d6..68e12b545e 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs @@ -7,6 +7,7 @@ namespace Avalonia.Diagnostics.ViewModels private object? _value; private string _priority; private string _group; + private readonly string _propertyType; #nullable disable // Remove "nullable disable" after MemberNotNull will work on our CI. @@ -20,6 +21,7 @@ namespace Avalonia.Diagnostics.ViewModels $"[{property.OwnerType.Name}.{property.Name}]" : property.Name; + _propertyType = GetTypeName(property.PropertyType); Update(); } @@ -50,6 +52,8 @@ namespace Avalonia.Diagnostics.ViewModels public override string Group => _group; + public override string PropertyType => _propertyType; + // [MemberNotNull(nameof(_type), nameof(_group), nameof(_priority))] public override void Update() { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs index b87d2b0b53..cbbf3e2fbb 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs @@ -7,6 +7,7 @@ namespace Avalonia.Diagnostics.ViewModels private readonly object _target; private string _assignedType; private object? _value; + private readonly string _propertyType; #nullable disable // Remove "nullable disable" after MemberNotNull will work on our CI. @@ -25,6 +26,8 @@ namespace Avalonia.Diagnostics.ViewModels Name = property.DeclaringType.Name + '.' + property.Name; } + _propertyType = GetTypeName(property.PropertyType); + Update(); } @@ -34,6 +37,7 @@ namespace Avalonia.Diagnostics.ViewModels public override string Group => "CLR Properties"; public override string AssignedType => _assignedType; + public override string PropertyType => _propertyType; public override string Value { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs index 080eab072c..067693de38 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs @@ -2,6 +2,7 @@ using System.ComponentModel; using System.Globalization; using System.Reflection; +using System.Linq; namespace Avalonia.Diagnostics.ViewModels { @@ -17,8 +18,27 @@ namespace Avalonia.Diagnostics.ViewModels public abstract string AssignedType { get; } public abstract string Value { get; set; } public abstract string Priority { get; } - public abstract bool? IsAttached { get; } - public abstract void Update(); + public abstract bool? IsAttached { get; } + public abstract void Update(); + public abstract string PropertyType { get; } + + + protected static string GetTypeName(Type type) + { + var name = type.Name; + if (Nullable.GetUnderlyingType(type) is Type nullable) + { + name = nullable.Name + "?"; + } + else if (type.IsGenericType) + { + var definition = type.GetGenericTypeDefinition(); + var arguments = type.GetGenericArguments(); + name = definition.Name.Substring(0, definition.Name.IndexOf('`')); + name = $"{name}<{string.Join(",", arguments.Select(GetTypeName))}>"; + } + return name; + } protected static string ConvertToString(object? value) { @@ -30,7 +50,7 @@ namespace Avalonia.Diagnostics.ViewModels var converter = TypeDescriptor.GetConverter(value); //CollectionConverter does not deliver any important information. It just displays "(Collection)". - if (!converter.CanConvertTo(typeof(string)) || + if (!converter.CanConvertTo(typeof(string)) || converter.GetType() == typeof(CollectionConverter)) { return value.ToString() ?? "(null)"; diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml index 22ad3374ed..b92ea85227 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml @@ -34,6 +34,7 @@ + From 67f83633ce3e7615dd97707e38799c2b769451e5 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 20 Jul 2021 16:26:19 +0200 Subject: [PATCH 03/20] feat(DevTools): Allowed to decode Nullable and Generic Type for AssignedType --- .../Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs | 4 ++-- .../Diagnostics/ViewModels/ClrPropertyViewModel.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs index 68e12b545e..29e049a5d8 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs @@ -60,7 +60,7 @@ namespace Avalonia.Diagnostics.ViewModels if (Property.IsDirect) { RaiseAndSetIfChanged(ref _value, _target.GetValue(Property), nameof(Value)); - RaiseAndSetIfChanged(ref _assignedType, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(AssignedType)); + RaiseAndSetIfChanged(ref _assignedType, GetTypeName(_value?.GetType() ?? Property.PropertyType), nameof(AssignedType)); RaiseAndSetIfChanged(ref _priority, "Direct", nameof(Priority)); _group = "Properties"; @@ -70,7 +70,7 @@ namespace Avalonia.Diagnostics.ViewModels var val = _target.GetDiagnostic(Property); RaiseAndSetIfChanged(ref _value, val?.Value, nameof(Value)); - RaiseAndSetIfChanged(ref _assignedType, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(AssignedType)); + RaiseAndSetIfChanged(ref _assignedType, GetTypeName(_value?.GetType() ?? Property.PropertyType), nameof(AssignedType)); if (val != null) { diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs index cbbf3e2fbb..fe48179150 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs @@ -64,7 +64,7 @@ namespace Avalonia.Diagnostics.ViewModels { var val = Property.GetValue(_target); RaiseAndSetIfChanged(ref _value, val, nameof(Value)); - RaiseAndSetIfChanged(ref _assignedType, _value?.GetType().Name ?? Property.PropertyType.Name, nameof(AssignedType)); + RaiseAndSetIfChanged(ref _assignedType, GetTypeName(_value?.GetType() ?? Property.PropertyType), nameof(AssignedType)); } } } From 3efcd9d259c43d756d5c1ee1ad548d15cac171f2 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Tue, 20 Jul 2021 16:35:18 +0200 Subject: [PATCH 04/20] feat(DevTools): Allow caching of GetTypeName --- .../ViewModels/PropertyViewModel.cs | 27 ++++++++++++------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs index 067693de38..1de1c34ea0 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs @@ -3,6 +3,7 @@ using System.ComponentModel; using System.Globalization; using System.Reflection; using System.Linq; +using System.Runtime.CompilerServices; namespace Avalonia.Diagnostics.ViewModels { @@ -11,6 +12,8 @@ namespace Avalonia.Diagnostics.ViewModels private const BindingFlags PublicStatic = BindingFlags.Public | BindingFlags.Static; private static readonly Type[] StringParameter = { typeof(string) }; private static readonly Type[] StringIFormatProviderParameters = { typeof(string), typeof(IFormatProvider) }; + private static readonly ConditionalWeakTable s_getTypeNameCache = + new ConditionalWeakTable(); public abstract object Key { get; } public abstract string Name { get; } @@ -25,17 +28,21 @@ namespace Avalonia.Diagnostics.ViewModels protected static string GetTypeName(Type type) { - var name = type.Name; - if (Nullable.GetUnderlyingType(type) is Type nullable) + if (!s_getTypeNameCache.TryGetValue(type, out var name)) { - name = nullable.Name + "?"; - } - else if (type.IsGenericType) - { - var definition = type.GetGenericTypeDefinition(); - var arguments = type.GetGenericArguments(); - name = definition.Name.Substring(0, definition.Name.IndexOf('`')); - name = $"{name}<{string.Join(",", arguments.Select(GetTypeName))}>"; + name = type.Name; + if (Nullable.GetUnderlyingType(type) is Type nullable) + { + name = nullable.Name + "?"; + } + else if (type.IsGenericType) + { + var definition = type.GetGenericTypeDefinition(); + var arguments = type.GetGenericArguments(); + name = definition.Name.Substring(0, definition.Name.IndexOf('`')); + name = $"{name}<{string.Join(",", arguments.Select(GetTypeName))}>"; + } + s_getTypeNameCache.Add(type, name); } return name; } From d0e810305192ab8d058095f759743bae06103628 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 21 Jul 2021 09:25:10 +0200 Subject: [PATCH 05/20] feat(DevTools): Allow toggle PropertyType column visibility --- .../Diagnostics/ViewModels/MainViewModel.cs | 16 ++++++++++++++-- .../Diagnostics/Views/ControlDetailsView.xaml | 5 ++++- .../Diagnostics/Views/MainView.xaml | 10 ++++++++++ 3 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 3f367165ac..90f127acd2 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -22,6 +22,7 @@ namespace Avalonia.Diagnostics.ViewModels private bool _shouldVisualizeMarginPadding = true; private bool _shouldVisualizeDirtyRects; private bool _showFpsOverlay; + private bool _showPropertyType; #nullable disable // Remove "nullable disable" after MemberNotNull will work on our CI. @@ -46,7 +47,7 @@ namespace Avalonia.Diagnostics.ViewModels get => _shouldVisualizeMarginPadding; set => RaiseAndSetIfChanged(ref _shouldVisualizeMarginPadding, value); } - + public bool ShouldVisualizeDirtyRects { get => _shouldVisualizeDirtyRects; @@ -151,7 +152,7 @@ namespace Avalonia.Diagnostics.ViewModels get { return _pointerOverElement; } private set { RaiseAndSetIfChanged(ref _pointerOverElement, value); } } - + private void UpdateConsoleContext(ConsoleContext context) { context.root = _root; @@ -220,5 +221,16 @@ namespace Avalonia.Diagnostics.ViewModels { StartupScreenIndex = options.StartupScreenIndex; } + + public bool ShowDettailsPropertyType + { + get => _showPropertyType; + private set => RaiseAndSetIfChanged(ref _showPropertyType , value); + } + + public void ToggleShowDettailsPropertyType(object paramter) + { + ShowDettailsPropertyType = !ShowDettailsPropertyType; + } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml index b92ea85227..5dfc75106a 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml @@ -34,7 +34,10 @@ - + diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml index 8c4db33f91..6d01eb27a1 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml @@ -15,6 +15,16 @@ IsEnabled="False"/> + + + + + + + + From 739d9de5b7c70d2d35d28063e19663745919ffb4 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 1 Sep 2021 12:32:40 +0200 Subject: [PATCH 06/20] feat(DevTools): Shows the property type and the type assigned to the property in a single or split column. --- .../Diagnostics/ViewModels/PropertyViewModel.cs | 3 +++ .../Diagnostics/Views/ControlDetailsView.xaml | 9 ++++++++- src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml | 2 +- 3 files changed, 12 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs index 1de1c34ea0..d3cdff6e71 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/PropertyViewModel.cs @@ -24,6 +24,9 @@ namespace Avalonia.Diagnostics.ViewModels public abstract bool? IsAttached { get; } public abstract void Update(); public abstract string PropertyType { get; } + public string Type => PropertyType == AssignedType + ? PropertyType + : $"{PropertyType} {{{AssignedType}}}"; protected static string GetTypeName(Type type) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml index 5dfc75106a..e6965884b1 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/ControlDetailsView.xaml @@ -33,7 +33,14 @@ - + + - + Date: Wed, 1 Sep 2021 17:03:26 +0200 Subject: [PATCH 07/20] feat: Added Post to IDispatcher --- src/Avalonia.Base/Threading/IDispatcher.cs | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/src/Avalonia.Base/Threading/IDispatcher.cs b/src/Avalonia.Base/Threading/IDispatcher.cs index 8f46f99283..538fa028b9 100644 --- a/src/Avalonia.Base/Threading/IDispatcher.cs +++ b/src/Avalonia.Base/Threading/IDispatcher.cs @@ -27,6 +27,15 @@ namespace Avalonia.Threading /// A task that can be used to track the method's execution. void Post(Action action, DispatcherPriority priority = DispatcherPriority.Normal); + /// + /// Invokes a method on the dispatcher thread. + /// + /// type of argument + /// The method to call. + /// The argument of method to call. + /// The priority with which to invoke the method. + void Post(Action action, T arg, DispatcherPriority priority = DispatcherPriority.Normal); + /// /// Posts an action that will be invoked on the dispatcher thread. /// @@ -59,4 +68,4 @@ namespace Avalonia.Threading /// A task that represents a proxy for the task returned by . Task InvokeAsync(Func> function, DispatcherPriority priority = DispatcherPriority.Normal); } -} \ No newline at end of file +} From 75bd449a6901f2a405daeeebc79c9d92e4d68b8a Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 1 Sep 2021 17:18:44 +0200 Subject: [PATCH 08/20] feat: Implement Post method of IDispatcher. --- src/Avalonia.Base/ApiCompatBaseline.txt | 3 + src/Avalonia.Base/Threading/Dispatcher.cs | 11 ++- src/Avalonia.Base/Threading/JobRunner.cs | 84 ++++++++++++++++--- .../Avalonia.UnitTests/ImmediateDispatcher.cs | 6 ++ 4 files changed, 90 insertions(+), 14 deletions(-) create mode 100644 src/Avalonia.Base/ApiCompatBaseline.txt diff --git a/src/Avalonia.Base/ApiCompatBaseline.txt b/src/Avalonia.Base/ApiCompatBaseline.txt new file mode 100644 index 0000000000..4701a83175 --- /dev/null +++ b/src/Avalonia.Base/ApiCompatBaseline.txt @@ -0,0 +1,3 @@ +Compat issues with assembly Avalonia.Base: +InterfacesShouldHaveSameMembers : Interface member 'public void Avalonia.Threading.IDispatcher.Post(System.Action, T, Avalonia.Threading.DispatcherPriority)' is present in the implementation but not in the contract. +Total Issues: 1 diff --git a/src/Avalonia.Base/Threading/Dispatcher.cs b/src/Avalonia.Base/Threading/Dispatcher.cs index fe2cec11f0..217f1b9c3f 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.cs @@ -81,7 +81,7 @@ namespace Avalonia.Threading Contract.Requires(action != null); return _jobRunner.InvokeAsync(action, priority); } - + /// public Task InvokeAsync(Func function, DispatcherPriority priority = DispatcherPriority.Normal) { @@ -110,6 +110,13 @@ namespace Avalonia.Threading _jobRunner.Post(action, priority); } + /// + public void Post(Action action, T arg, DispatcherPriority priority = DispatcherPriority.Normal) + { + Contract.Requires(action != null); + _jobRunner.Post(action, arg, priority); + } + /// /// 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 @@ -142,4 +149,4 @@ namespace Avalonia.Threading } } } -} \ No newline at end of file +} diff --git a/src/Avalonia.Base/Threading/JobRunner.cs b/src/Avalonia.Base/Threading/JobRunner.cs index 58f623fb3f..7aeb1972ea 100644 --- a/src/Avalonia.Base/Threading/JobRunner.cs +++ b/src/Avalonia.Base/Threading/JobRunner.cs @@ -13,7 +13,7 @@ namespace Avalonia.Threading { private IPlatformThreadingInterface _platform; - private readonly Queue[] _queues = Enumerable.Range(0, (int) DispatcherPriority.MaxValue + 1) + private readonly Queue[] _queues = Enumerable.Range(0, (int)DispatcherPriority.MaxValue + 1) .Select(_ => new Queue()).ToArray(); public JobRunner(IPlatformThreadingInterface platform) @@ -59,7 +59,7 @@ namespace Avalonia.Threading /// A task that can be used to track the method's execution. public Task InvokeAsync(Func function, DispatcherPriority priority) { - var job = new Job(function, priority); + var job = new JobWithResult(function, priority); AddJob(job); return job.Task; } @@ -75,6 +75,17 @@ namespace Avalonia.Threading AddJob(new Job(action, priority, true)); } + /// + /// Post action that will be invoked on main thread + /// + /// The method to call. + /// The parameter of method to call. + /// The priority with which to invoke the method. + internal void Post(Action action, T parameter, DispatcherPriority priority) + { + AddJob(new Job(action, parameter, priority, true)); + } + /// /// Allows unit tests to change the platform threading interface. /// @@ -86,7 +97,7 @@ namespace Avalonia.Threading private void AddJob(IJob job) { bool needWake; - var queue = _queues[(int) job.Priority]; + var queue = _queues[(int)job.Priority]; lock (queue) { needWake = queue.Count == 0; @@ -98,7 +109,7 @@ namespace Avalonia.Threading 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]; lock (q) @@ -109,14 +120,14 @@ namespace Avalonia.Threading } return null; } - + private interface IJob { /// /// Gets the job priority. /// DispatcherPriority Priority { get; } - + /// /// Runs the job. /// @@ -157,7 +168,7 @@ namespace Avalonia.Threading /// The task. /// public Task Task => _taskCompletionSource?.Task; - + /// void IJob.Run() { @@ -177,11 +188,60 @@ namespace Avalonia.Threading } } } - + /// - /// A job to run. + /// A tipizzed job to run. /// - private sealed class Job : IJob + private sealed class Job : IJob + { + private readonly Action _action; + private readonly T _parameter; + private readonly TaskCompletionSource _taskCompletionSource; + + /// + /// Initializes a new instance of the class. + /// + /// The method to call. + /// The parameter of method to call. + /// The job priority. + /// Do not wrap exception in TaskCompletionSource + + public Job(Action action, T parameter, DispatcherPriority priority, bool throwOnUiThread) + { + _action = action; + _parameter = parameter; + Priority = priority; + _taskCompletionSource = throwOnUiThread ? null : new TaskCompletionSource(); + } + + /// + public DispatcherPriority Priority { get; } + + /// + void IJob.Run() + { + if (_taskCompletionSource == null) + { + _action(_parameter); + return; + } + try + { + _action(_parameter); + _taskCompletionSource.SetResult(null); + } + catch (Exception e) + { + _taskCompletionSource.SetException(e); + } + } + } + + + /// + /// A job to run thath return value. + /// + private sealed class JobWithResult : IJob { private readonly Func _function; private readonly TaskCompletionSource _taskCompletionSource; @@ -191,7 +251,7 @@ namespace Avalonia.Threading /// /// The method to call. /// The job priority. - public Job(Func function, DispatcherPriority priority) + public JobWithResult(Func function, DispatcherPriority priority) { _function = function; Priority = priority; @@ -200,7 +260,7 @@ namespace Avalonia.Threading /// public DispatcherPriority Priority { get; } - + /// /// The task. /// diff --git a/tests/Avalonia.UnitTests/ImmediateDispatcher.cs b/tests/Avalonia.UnitTests/ImmediateDispatcher.cs index fac4ee64e7..5f0d41590f 100644 --- a/tests/Avalonia.UnitTests/ImmediateDispatcher.cs +++ b/tests/Avalonia.UnitTests/ImmediateDispatcher.cs @@ -21,6 +21,12 @@ namespace Avalonia.UnitTests action(); } + /// + public void Post(Action action, T arg, DispatcherPriority priority = DispatcherPriority.Normal) + { + action(arg); + } + /// public Task InvokeAsync(Action action, DispatcherPriority priority = DispatcherPriority.Normal) { From aa5df186f049065a7d2167a54f1d3725a8b23738 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Thu, 2 Sep 2021 09:40:54 +0200 Subject: [PATCH 09/20] fixes: XML Code Comment --- src/Avalonia.Base/Threading/JobRunner.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Base/Threading/JobRunner.cs b/src/Avalonia.Base/Threading/JobRunner.cs index 7aeb1972ea..357fbd9b59 100644 --- a/src/Avalonia.Base/Threading/JobRunner.cs +++ b/src/Avalonia.Base/Threading/JobRunner.cs @@ -190,8 +190,9 @@ namespace Avalonia.Threading } /// - /// A tipizzed job to run. + /// A typed job to run. /// + /// Type of job parameter private sealed class Job : IJob { private readonly Action _action; @@ -237,10 +238,10 @@ namespace Avalonia.Threading } } - /// /// A job to run thath return value. /// + /// Type of job result private sealed class JobWithResult : IJob { private readonly Func _function; From 4b3b37c961945978b1abdd07dc34f752c310d9de Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 13 Dec 2021 15:16:13 +0100 Subject: [PATCH 10/20] fixes(DevTools): The Type column does not update when the property value changes. --- .../Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs | 2 ++ .../Diagnostics/ViewModels/ClrPropertyViewModel.cs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs index 46f8614273..a09e17ea68 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/AvaloniaPropertyViewModel.cs @@ -45,6 +45,7 @@ namespace Avalonia.Diagnostics.ViewModels { var convertedValue = ConvertFromString(value, Property.PropertyType); _target.SetValue(Property, convertedValue); + Update(); } catch { } } @@ -84,6 +85,7 @@ namespace Avalonia.Diagnostics.ViewModels RaiseAndSetIfChanged(ref _group, "Unset", nameof(Group)); } } + RaisePropertyChanged(nameof(Type)); } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs index b6bdb00170..e87be9fa36 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/ClrPropertyViewModel.cs @@ -48,6 +48,7 @@ namespace Avalonia.Diagnostics.ViewModels { var convertedValue = ConvertFromString(value, Property.PropertyType); Property.SetValue(_target, convertedValue); + Update(); } catch { } } @@ -67,6 +68,7 @@ namespace Avalonia.Diagnostics.ViewModels var val = Property.GetValue(_target); RaiseAndSetIfChanged(ref _value, val, nameof(Value)); RaiseAndSetIfChanged(ref _assignedType, _value?.GetType() ?? Property.PropertyType, nameof(AssignedType)); + RaisePropertyChanged(nameof(Type)); } } } From 1ed495afe2d01b40a512506532bafe86c5b6f903 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 23 Dec 2021 20:39:35 +0300 Subject: [PATCH 11/20] Use microcom generator from nuget --- Avalonia.sln | 27 - build/MicroCom.targets | 35 -- nukebuild/MicroComGen.cs | 8 +- nukebuild/_build.csproj | 5 +- src/Avalonia.MicroCom/MicroComRuntime.cs | 6 + src/Avalonia.MicroCom/MicroComVtblBase.cs | 5 + src/Avalonia.Native/Avalonia.Native.csproj | 5 +- src/Directory.Build.props | 6 + src/Shared/ModuleInitializer.cs | 10 + .../Avalonia.Win32/Avalonia.Win32.csproj | 6 +- src/tools/MicroComGenerator/Ast.cs | 241 --------- src/tools/MicroComGenerator/AstParser.cs | 232 --------- .../CSharpGen.InterfaceGen.cs | 484 ------------------ .../MicroComGenerator/CSharpGen.Utils.cs | 111 ---- src/tools/MicroComGenerator/CSharpGen.cs | 155 ------ src/tools/MicroComGenerator/CppGen.cs | 119 ----- src/tools/MicroComGenerator/Extensions.cs | 97 ---- .../MicroComGenerator.csproj | 10 - src/tools/MicroComGenerator/ParseException.cs | 27 - src/tools/MicroComGenerator/Program.cs | 52 -- src/tools/MicroComGenerator/TokenParser.cs | 417 --------------- 21 files changed, 38 insertions(+), 2020 deletions(-) delete mode 100644 build/MicroCom.targets create mode 100644 src/Shared/ModuleInitializer.cs delete mode 100644 src/tools/MicroComGenerator/Ast.cs delete mode 100644 src/tools/MicroComGenerator/AstParser.cs delete mode 100644 src/tools/MicroComGenerator/CSharpGen.InterfaceGen.cs delete mode 100644 src/tools/MicroComGenerator/CSharpGen.Utils.cs delete mode 100644 src/tools/MicroComGenerator/CSharpGen.cs delete mode 100644 src/tools/MicroComGenerator/CppGen.cs delete mode 100644 src/tools/MicroComGenerator/Extensions.cs delete mode 100644 src/tools/MicroComGenerator/MicroComGenerator.csproj delete mode 100644 src/tools/MicroComGenerator/ParseException.cs delete mode 100644 src/tools/MicroComGenerator/Program.cs delete mode 100644 src/tools/MicroComGenerator/TokenParser.cs diff --git a/Avalonia.sln b/Avalonia.sln index 3c2fc7437b..4e7b4cc318 100644 --- a/Avalonia.sln +++ b/Avalonia.sln @@ -221,8 +221,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Avalonia.Markup.Xaml.Loader EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Sandbox", "samples\Sandbox\Sandbox.csproj", "{11BE52AF-E2DD-4CF0-B19A-05285ACAF571}" 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}" EndProject 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|iPhoneSimulator.ActiveCfg = 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.Build.0 = 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} {909A8CBD-7D0E-42FD-B841-022AD8925820} = {8B6A8209-894F-4BA1-B880-965FD453982C} {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} {25831348-EB2A-483E-9576-E8F6528674A5} = {86A3F706-DC3C-43C6-BE1B-B98F5BAAA268} {C08E9894-AA92-426E-BF56-033E262CAD3E} = {9B9E3891-2366-4253-A952-D08BCEB71098} diff --git a/build/MicroCom.targets b/build/MicroCom.targets deleted file mode 100644 index 029d7f95f5..0000000000 --- a/build/MicroCom.targets +++ /dev/null @@ -1,35 +0,0 @@ - - - - - - false - all - true - TargetFramework=net6.0 - - - - - - - - - - - - - - - - - - <_AvaloniaPatchComInterop>true - - - diff --git a/nukebuild/MicroComGen.cs b/nukebuild/MicroComGen.cs index 06c8acbf23..b1e546cb97 100644 --- a/nukebuild/MicroComGen.cs +++ b/nukebuild/MicroComGen.cs @@ -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()); }); } \ No newline at end of file diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index b28d3eb700..acb001f2e7 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -16,6 +16,7 @@ + @@ -37,10 +38,6 @@ - - MicroComGenerator\%(Filename)%(Extension) - - diff --git a/src/Avalonia.MicroCom/MicroComRuntime.cs b/src/Avalonia.MicroCom/MicroComRuntime.cs index 85507674d2..e0f524146a 100644 --- a/src/Avalonia.MicroCom/MicroComRuntime.cs +++ b/src/Avalonia.MicroCom/MicroComRuntime.cs @@ -36,7 +36,13 @@ namespace Avalonia.MicroCom public static T CreateProxyFor(void* pObject, bool ownsHandle) => (T)CreateProxyFor(typeof(T), new IntPtr(pObject), ownsHandle); public static T CreateProxyFor(IntPtr pObject, bool ownsHandle) => (T)CreateProxyFor(typeof(T), pObject, ownsHandle); + + public static T CreateProxyOrNullFor(void* pObject, bool ownsHandle) where T : class => + pObject == null ? null : (T)CreateProxyFor(typeof(T), new IntPtr(pObject), ownsHandle); + public static T CreateProxyOrNullFor(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 IntPtr GetNativeIntPtr(this T obj, bool owned = false) where T : IUnknown diff --git a/src/Avalonia.MicroCom/MicroComVtblBase.cs b/src/Avalonia.MicroCom/MicroComVtblBase.cs index 2f0607c0a8..7092f8131a 100644 --- a/src/Avalonia.MicroCom/MicroComVtblBase.cs +++ b/src/Avalonia.MicroCom/MicroComVtblBase.cs @@ -21,6 +21,11 @@ namespace Avalonia.MicroCom AddMethod((AddRefDelegate)Release); } + protected void AddMethod(void* f) + { + _methods.Add(new IntPtr(f)); + } + protected void AddMethod(Delegate d) { GCHandle.Alloc(d); diff --git a/src/Avalonia.Native/Avalonia.Native.csproj b/src/Avalonia.Native/Avalonia.Native.csproj index c37bef0488..366ce97955 100644 --- a/src/Avalonia.Native/Avalonia.Native.csproj +++ b/src/Avalonia.Native/Avalonia.Native.csproj @@ -6,6 +6,7 @@ true netstandard2.0;net6.0 true + Avalonia.MicroCom @@ -20,7 +21,7 @@ - + + - diff --git a/src/Directory.Build.props b/src/Directory.Build.props index a3f0c01a51..869602e452 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -2,4 +2,10 @@ + + + Shared\_ModuleInitializer.cs + false + + diff --git a/src/Shared/ModuleInitializer.cs b/src/Shared/ModuleInitializer.cs new file mode 100644 index 0000000000..c14b150c1a --- /dev/null +++ b/src/Shared/ModuleInitializer.cs @@ -0,0 +1,10 @@ +namespace System.Runtime.CompilerServices +{ +#if !NET5_0_OR_GREATER + internal class ModuleInitializerAttribute : Attribute + { + + } +#endif +} + diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index c8f08a5799..a0290ee5e8 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -8,9 +8,9 @@ - - + + + - diff --git a/src/tools/MicroComGenerator/Ast.cs b/src/tools/MicroComGenerator/Ast.cs deleted file mode 100644 index e9a55308be..0000000000 --- a/src/tools/MicroComGenerator/Ast.cs +++ /dev/null @@ -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 - { - 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, 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, 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, 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, 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 Enums { get; set; } = new List(); - public List Structs { get; set; } = new List(); - public List Interfaces { get; set; } = new List(); - - 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() - }; - } -} diff --git a/src/tools/MicroComGenerator/AstParser.cs b/src/tools/MicroComGenerator/AstParser.cs deleted file mode 100644 index 388a8eb018..0000000000 --- a/src/tools/MicroComGenerator/AstParser.cs +++ /dev/null @@ -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; - } - } -} diff --git a/src/tools/MicroComGenerator/CSharpGen.InterfaceGen.cs b/src/tools/MicroComGenerator/CSharpGen.InterfaceGen.cs deleted file mode 100644 index adb8faf938..0000000000 --- a/src/tools/MicroComGenerator/CSharpGen.InterfaceGen.cs +++ /dev/null @@ -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 body) - { - } - - public virtual void PreMarshalForReturn(List 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 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 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 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 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 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(); - 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(); - 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() - { - 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 _existing = new HashSet(); - - public ExpressionSyntax GetCaller(string returnType, List 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(); - 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)); - } - } -} diff --git a/src/tools/MicroComGenerator/CSharpGen.Utils.cs b/src/tools/MicroComGenerator/CSharpGen.Utils.cs deleted file mode 100644 index 28baaa65f8..0000000000 --- a/src/tools/MicroComGenerator/CSharpGen.Utils.cs +++ /dev/null @@ -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 decl) where T : TypeDeclarationSyntax - { - var replace = new Dictionary(); - foreach (var method in decl.Members.OfType().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); - - } -} diff --git a/src/tools/MicroComGenerator/CSharpGen.cs b/src/tools/MicroComGenerator/CSharpGen.cs deleted file mode 100644 index ff4c351fd9..0000000000 --- a/src/tools/MicroComGenerator/CSharpGen.cs +++ /dev/null @@ -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 _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 _typeMap = new Dictionary(); - - public AstRewriter(Dictionary 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(SeparatedList(e.Select(m => - DeclareField(m.Type.ToString(), m.Name, SyntaxKind.PublicKeyword))))) - ).ToArray()); - } - - - - } -} diff --git a/src/tools/MicroComGenerator/CppGen.cs b/src/tools/MicroComGenerator/CppGen.cs deleted file mode 100644 index b053088ca9..0000000000 --- a/src/tools/MicroComGenerator/CppGen.cs +++ /dev/null @@ -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(); - } - } -} diff --git a/src/tools/MicroComGenerator/Extensions.cs b/src/tools/MicroComGenerator/Extensions.cs deleted file mode 100644 index c8a4c8f45c..0000000000 --- a/src/tools/MicroComGenerator/Extensions.cs +++ /dev/null @@ -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(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; - } -} diff --git a/src/tools/MicroComGenerator/MicroComGenerator.csproj b/src/tools/MicroComGenerator/MicroComGenerator.csproj deleted file mode 100644 index 68895b96ca..0000000000 --- a/src/tools/MicroComGenerator/MicroComGenerator.csproj +++ /dev/null @@ -1,10 +0,0 @@ - - - Exe - net6.0 - - - - - - diff --git a/src/tools/MicroComGenerator/ParseException.cs b/src/tools/MicroComGenerator/ParseException.cs deleted file mode 100644 index cb54918100..0000000000 --- a/src/tools/MicroComGenerator/ParseException.cs +++ /dev/null @@ -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) - { - } - } -} diff --git a/src/tools/MicroComGenerator/Program.cs b/src/tools/MicroComGenerator/Program.cs deleted file mode 100644 index 2468b1b5a4..0000000000 --- a/src/tools/MicroComGenerator/Program.cs +++ /dev/null @@ -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(args); - if (p is NotParsed) - { - return 1; - } - - var opts = ((Parsed)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; - } - } -} diff --git a/src/tools/MicroComGenerator/TokenParser.cs b/src/tools/MicroComGenerator/TokenParser.cs deleted file mode 100644 index ea8850b8e4..0000000000 --- a/src/tools/MicroComGenerator/TokenParser.cs +++ /dev/null @@ -1,417 +0,0 @@ -using System; -using System.Globalization; -using System.IO; - -namespace MicroComGenerator -{ - internal ref struct TokenParser - { - private ReadOnlySpan _s; - public int Position { get; private set; } - public int Line { get; private set; } - public TokenParser(ReadOnlySpan 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 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 && 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 && 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 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 extraValidChars, out ReadOnlySpan res) - { - res = ReadOnlySpan.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 res) - { - res = ReadOnlySpan.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 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 res) - { - res = ReadOnlySpan.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(); - - } -} From e1e2c52e0d905fa582f30c5d7458eb8e5fff670a Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 23 Dec 2021 20:50:05 +0300 Subject: [PATCH 12/20] Detected package downgrade --- nukebuild/_build.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index acb001f2e7..f8e2f358d6 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -15,7 +15,6 @@ - From 767de9f642c9355b82054cfaae8e2cb7ddd2eb6f Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 23 Dec 2021 20:55:56 +0300 Subject: [PATCH 13/20] Namespace --- src/Windows/Avalonia.Win32/Avalonia.Win32.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index a0290ee5e8..8dfdd3159c 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -3,6 +3,7 @@ netstandard2.0;net6.0 true Avalonia.Win32 + Avalonia.MicroCom From 8f60013a1e22bfa6a2f098a66d4a7e781ff11af3 Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Thu, 23 Dec 2021 21:05:04 +0300 Subject: [PATCH 14/20] CI: generate c++ headers with nuke --- azure-pipelines.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 903146cdd7..497402fe4b 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -58,8 +58,10 @@ jobs: displayName: 'Generate avalonia-native' inputs: script: | - export PATH="`pwd`/sdk:$PATH" - cd src/tools/MicroComGenerator; dotnet run -f net6.0 -i ../../Avalonia.Native/avn.idl --cpp ../../../native/Avalonia.Native/inc/avalonia-native.h + export COREHOST_TRACE=0 + export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 + export DOTNET_CLI_TELEMETRY_OPTOUT=1 + ./build.sh --target GenerateCppHeaders --configuration Release - task: Xcode@5 inputs: From 494cb0110559c85c0793830fcf1bf902ce725a8a Mon Sep 17 00:00:00 2001 From: Nikita Tsukanov Date: Fri, 24 Dec 2021 11:12:13 +0300 Subject: [PATCH 15/20] Updated microcom --- nukebuild/_build.csproj | 2 +- src/Avalonia.Native/Avalonia.Native.csproj | 2 +- src/Windows/Avalonia.Win32/Avalonia.Win32.csproj | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/nukebuild/_build.csproj b/nukebuild/_build.csproj index f8e2f358d6..52b60b7d0f 100644 --- a/nukebuild/_build.csproj +++ b/nukebuild/_build.csproj @@ -15,7 +15,7 @@ - + diff --git a/src/Avalonia.Native/Avalonia.Native.csproj b/src/Avalonia.Native/Avalonia.Native.csproj index 366ce97955..9d2471b0c5 100644 --- a/src/Avalonia.Native/Avalonia.Native.csproj +++ b/src/Avalonia.Native/Avalonia.Native.csproj @@ -21,7 +21,7 @@ - + diff --git a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj index 8dfdd3159c..10edf42897 100644 --- a/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj +++ b/src/Windows/Avalonia.Win32/Avalonia.Win32.csproj @@ -9,7 +9,7 @@ - + From 215425c45a02bd7c0cad7a881d198a4fbac66568 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Mon, 27 Dec 2021 12:17:37 +0100 Subject: [PATCH 16/20] fixes(DevTools): NRE when click 'Visualizze dirty rects' and 'Show fps overly' in Option menu --- .../Diagnostics/Controls/Application.cs | 12 ++++++- .../Diagnostics/ViewModels/MainViewModel.cs | 32 +++++++++++++++++-- 2 files changed, 40 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs index b2f2974440..316e31b37e 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/Controls/Application.cs @@ -28,7 +28,12 @@ namespace Avalonia.Diagnostics.Controls }; controller.Exit += eh; } - + RendererRoot = application.ApplicationLifetime switch + { + Lifetimes.IClassicDesktopStyleApplicationLifetime classic => classic.MainWindow.Renderer, + Lifetimes.ISingleViewApplicationLifetime single => (single.MainView as VisualTree.IVisual)?.VisualRoot?.Renderer, + _ => null + }; } internal App Instance => _application; @@ -105,5 +110,10 @@ namespace Avalonia.Diagnostics.Controls /// public string? Name => _application.Name; + + /// + /// Gets the root of the visual tree, if the control is attached to a visual tree. + /// + internal Rendering.IRenderer? RendererRoot { get; } } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index d809768a47..c607542991 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -76,7 +76,20 @@ namespace Avalonia.Diagnostics.ViewModels get => _shouldVisualizeDirtyRects; set { - ((TopLevel)_root).Renderer.DrawDirtyRects = value; + var changed = true; + if (_root is TopLevel topLevel && topLevel.Renderer is { }) + { + topLevel.Renderer.DrawDirtyRects = value; + } + else if (_root is Controls.Application app && app.RendererRoot is { }) + { + app.RendererRoot.DrawDirtyRects = value; + } + else + { + changed = false; + } + if (changed) RaiseAndSetIfChanged(ref _shouldVisualizeDirtyRects, value); } } @@ -96,8 +109,21 @@ namespace Avalonia.Diagnostics.ViewModels get => _showFpsOverlay; set { - ((TopLevel)_root).Renderer.DrawFps = value; - RaiseAndSetIfChanged(ref _showFpsOverlay, value); + var changed = true; + if (_root is TopLevel topLevel && topLevel.Renderer is { }) + { + topLevel.Renderer.DrawFps = value; + } + else if (_root is Controls.Application app && app.RendererRoot is { }) + { + app.RendererRoot.DrawFps = value; + } + else + { + changed = false; + } + if(changed) + RaiseAndSetIfChanged(ref _showFpsOverlay, value); } } From 05021506e6165a004c7eec97e97318bc31203bde Mon Sep 17 00:00:00 2001 From: Max Katz Date: Mon, 27 Dec 2021 11:17:22 -0500 Subject: [PATCH 17/20] Update src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml --- src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml index 22f9ccf513..381fc68cf3 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml @@ -15,7 +15,7 @@ IsEnabled="False" /> - + Date: Wed, 29 Dec 2021 01:44:11 +0700 Subject: [PATCH 18/20] select text when clicking on a TextBox with shift key modifier --- src/Avalonia.Controls/TextBox.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/Avalonia.Controls/TextBox.cs b/src/Avalonia.Controls/TextBox.cs index 32428bea53..20d8a94c1a 100644 --- a/src/Avalonia.Controls/TextBox.cs +++ b/src/Avalonia.Controls/TextBox.cs @@ -1006,13 +1006,28 @@ namespace Avalonia.Controls if (text != null && clickInfo.Properties.IsLeftButtonPressed && !(clickInfo.Pointer?.Captured is Border)) { 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 switch (e.ClickCount) #pragma warning restore CS0618 // Type or member is obsolete { case 1: - SelectionStart = SelectionEnd = index; + if (clickToSelect) + { + SelectionStart = Math.Min(index, CaretIndex); + SelectionEnd = Math.Max(index, CaretIndex); + } + else + { + SelectionStart = SelectionEnd = index; + } break; case 2: if (!StringUtils.IsStartOfWord(text, index)) From 509d7c06f70b93d0ec65503255a5d56f9593f8f3 Mon Sep 17 00:00:00 2001 From: Giuseppe Lippolis Date: Wed, 29 Dec 2021 11:23:29 +0100 Subject: [PATCH 19/20] fixes: Null Annotaion --- src/Avalonia.Base/Threading/Dispatcher.cs | 2 +- src/Avalonia.Base/Threading/JobRunner.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/Avalonia.Base/Threading/Dispatcher.cs b/src/Avalonia.Base/Threading/Dispatcher.cs index b7d5d5a5d2..908f431776 100644 --- a/src/Avalonia.Base/Threading/Dispatcher.cs +++ b/src/Avalonia.Base/Threading/Dispatcher.cs @@ -113,7 +113,7 @@ namespace Avalonia.Threading /// public void Post(Action action, T arg, DispatcherPriority priority = DispatcherPriority.Normal) { - Contract.Requires(action != null); + _ = action ?? throw new ArgumentNullException(nameof(action)); _jobRunner.Post(action, arg, priority); } diff --git a/src/Avalonia.Base/Threading/JobRunner.cs b/src/Avalonia.Base/Threading/JobRunner.cs index 66f6243a35..f2aef0414c 100644 --- a/src/Avalonia.Base/Threading/JobRunner.cs +++ b/src/Avalonia.Base/Threading/JobRunner.cs @@ -197,7 +197,7 @@ namespace Avalonia.Threading { private readonly Action _action; private readonly T _parameter; - private readonly TaskCompletionSource _taskCompletionSource; + private readonly TaskCompletionSource? _taskCompletionSource; /// /// Initializes a new instance of the class. @@ -212,7 +212,7 @@ namespace Avalonia.Threading _action = action; _parameter = parameter; Priority = priority; - _taskCompletionSource = throwOnUiThread ? null : new TaskCompletionSource(); + _taskCompletionSource = throwOnUiThread ? null : new TaskCompletionSource(); } /// @@ -229,7 +229,7 @@ namespace Avalonia.Threading try { _action(_parameter); - _taskCompletionSource.SetResult(null); + _taskCompletionSource.SetResult(default); } catch (Exception e) { From eab9659487f370f8a589568c23d8df8f8d93f260 Mon Sep 17 00:00:00 2001 From: workgroupengineering Date: Thu, 30 Dec 2021 01:11:11 +0100 Subject: [PATCH 20/20] [DevTools] Allowed to take a screenshot of the currently selected control. (#4762) * [DevTools] Allowed to take a screenshot of the currently selected control. * feat(DevTools): Allowed to customize screenshot root folder. * feat(DevTools): Allowed screenshot filename by convention * fixes(DevTools): Mark Shot as async and catch eventualy RenderTo excepion * fixes: allignment of code comment * fixes: CS0173 when build on linux and macOS * Refactoring Shot method * fixes: typo in DefaultScreenshotRoot * fixes: assembly detection * fixes: code comment * fixes: nullable warnings * fixes: merge issue * fixes: removed unnecessary using * fixes: removed Dispatcher.UIThread.InvokeAsync * fixes: unexpected to the user results after call RenderTo * fixes: screenshot Clip * fixes: typo * fixes: remove workaraund * Apply suggestions from code review * feat(DevTools): Add Shot Button * feat(DevTools): Custom Screenshot handler * fixes(DevTools): Renamed Screenshots.FileHandler to Screenshots.FileConvetionHandler * feat(DevTools): Screenshots.BaseRenderToStreamHandler * feat(DevTools): Screenshots.FilePickerHandler * fixes(DevTools): marked VisualExtensions as internal * fixes(DevTools): Strip out FileConvetionHandler * fixes(DevTools): FilePickerHandler promote Title and ScreenshotsRoot properties to ctor * fixes(DevTools): Remove screenshot HotKey description form status bar * fixes(DevTools): Xml Comment * fixes(DevTools): Moved Screenshot command to File menu * fixes(DevTools): Removed reference to Avalonia.Dialogs Co-authored-by: Giuseppe Lippolis Co-authored-by: Jumar Macato <16554748+jmacato@users.noreply.github.com> Co-authored-by: Max Katz --- .../Diagnostics/Convetions.cs | 17 ++++ .../Diagnostics/DevToolsOptions.cs | 10 ++- .../Diagnostics/IScreenshotHandler.cs | 17 ++++ .../Screenshots/BaseRenderToStreamHandler.cs | 29 +++++++ .../Screenshots/FilePickerHandler.cs | 85 +++++++++++++++++++ .../Diagnostics/ViewModels/MainViewModel.cs | 34 ++++++++ .../Diagnostics/Views/MainView.xaml | 30 +++++++ .../Diagnostics/Views/MainWindow.xaml | 4 +- .../Diagnostics/VisualExtensions.cs | 71 ++++++++++++++++ 9 files changed, 295 insertions(+), 2 deletions(-) create mode 100644 src/Avalonia.Diagnostics/Diagnostics/Convetions.cs create mode 100644 src/Avalonia.Diagnostics/Diagnostics/IScreenshotHandler.cs create mode 100644 src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs create mode 100644 src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs create mode 100644 src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs diff --git a/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs b/src/Avalonia.Diagnostics/Diagnostics/Convetions.cs new file mode 100644 index 0000000000..b41755f239 --- /dev/null +++ b/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(); + } +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs index 4181989388..33166f1147 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/DevToolsOptions.cs @@ -1,4 +1,5 @@ -using Avalonia.Input; +using System; +using Avalonia.Input; namespace Avalonia.Diagnostics { @@ -28,5 +29,12 @@ namespace Avalonia.Diagnostics /// Get or set the startup screen index where the DevTools window will be displayed. /// public int? StartupScreenIndex { get; set; } + + /// + /// Allow to customizze SreenshotHandler + /// + /// Default handler is + public IScreenshotHandler ScreenshotHandler { get; set; } + = Convetions.DefaultScreenshotHandler; } } diff --git a/src/Avalonia.Diagnostics/Diagnostics/IScreenshotHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/IScreenshotHandler.cs new file mode 100644 index 0000000000..3503ab56b3 --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/IScreenshotHandler.cs @@ -0,0 +1,17 @@ +using System.Threading.Tasks; +using Avalonia.Controls; + +namespace Avalonia.Diagnostics +{ + /// + /// Allowed to define custom handler for Shreeshot + /// + public interface IScreenshotHandler + { + /// + /// Handle the Screenshot + /// + /// + Task Take(IControl control); + } +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs new file mode 100644 index 0000000000..c111b045d3 --- /dev/null +++ b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/BaseRenderToStreamHandler.cs @@ -0,0 +1,29 @@ +using System.Threading.Tasks; +using Avalonia.Controls; + +namespace Avalonia.Diagnostics.Screenshots +{ + /// + /// Base class for render Screenshto to stream + /// + public abstract class BaseRenderToStreamHandler : IScreenshotHandler + { + + /// + /// Get stream + /// + /// + /// stream to render the control + protected abstract Task GetStream(IControl control); + + public async Task Take(IControl control) + { + using var output = await GetStream(control); + if (output is { }) + { + control.RenderTo(output); + await output.FlushAsync(); + } + } + } +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs b/src/Avalonia.Diagnostics/Diagnostics/Screenshots/FilePickerHandler.cs new file mode 100644 index 0000000000..fb57058ae9 --- /dev/null +++ b/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 +{ + /// + /// Show a FileSavePicker to select where save screenshot + /// + public sealed class FilePickerHandler : BaseRenderToStreamHandler + { + /// + /// Instance FilePickerHandler + /// + public FilePickerHandler() + { + + } + /// + /// Instance FilePickerHandler with specificated parameter + /// + /// SaveFilePicker Title + /// + public FilePickerHandler(string? title + , string? screenshotRoot = default + ) + { + if (title is { }) + Title = title; + if (screenshotRoot is { }) + ScreenshotsRoot = screenshotRoot; + } + /// + /// Get the root folder where screeshots well be stored. + /// The default root folder is [Environment.SpecialFolder.MyPictures]/Screenshots. + /// + public string ScreenshotsRoot { get; } + = Convetions.DefaultScreenshotsRoot; + + /// + /// SaveFilePicker Title + /// + 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 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; + } + } +} diff --git a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs index 8bc13a9525..c96ddf129e 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs +++ b/src/Avalonia.Diagnostics/Diagnostics/ViewModels/MainViewModel.cs @@ -1,8 +1,11 @@ using System; using System.ComponentModel; +using System.Reactive.Linq; +using System.Threading.Tasks; using Avalonia.Controls; using Avalonia.Diagnostics.Models; using Avalonia.Input; +using Avalonia.Metadata; using Avalonia.Threading; using System.Reactive.Linq; using System.Linq; @@ -26,7 +29,9 @@ namespace Avalonia.Diagnostics.ViewModels private bool _freezePopups; private string? _pointerOverElementName; private IInputRoot? _pointerOverRoot; + private IScreenshotHandler? _screenshotHandler; private bool _showPropertyType; + public MainViewModel(AvaloniaObject root) { _root = root; @@ -285,9 +290,38 @@ namespace Avalonia.Diagnostics.ViewModels } 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) { + _screenshotHandler = options.ScreenshotHandler; StartupScreenIndex = options.StartupScreenIndex; } diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml index 381fc68cf3..91c8eb0983 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainView.xaml @@ -6,6 +6,36 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml index 70d70f0b79..45bfe5ff81 100644 --- a/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml +++ b/src/Avalonia.Diagnostics/Diagnostics/Views/MainWindow.xaml @@ -18,6 +18,8 @@ - + + + diff --git a/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs b/src/Avalonia.Diagnostics/Diagnostics/VisualExtensions.cs new file mode 100644 index 0000000000..a80ed2d4ef --- /dev/null +++ b/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 + { + /// + /// Render control to the destination stream. + /// + /// Control to be rendered. + /// Destination stream. + /// Dpi quality. + 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(); + } + } + } +}