Skip to content

Commit

Permalink
feat: Every RoutedEvent should be usable as Attached Event
Browse files Browse the repository at this point in the history
  • Loading branch information
workgroupengineering committed Apr 9, 2024
1 parent 9adf6f2 commit 3f82e64
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Diagnostics.CodeAnalysis;
using System.Linq;
using Avalonia.Markup.Xaml.Loader.CompilerExtensions.Transformers;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.GroupTransformers;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using XamlX;
Expand Down Expand Up @@ -86,6 +87,8 @@ void InsertBeforeMany(Type[] types, params IXamlAstTransformer[] t)
InsertBeforeMany(new [] { typeof(DeferredContentTransformer), typeof(AvaloniaXamlIlCompiledBindingsMetadataRemover) },
new AvaloniaXamlIlDeferredResourceTransformer());

InsertBefore<AvaloniaXamlIlTransformInstanceAttachedProperties>(new AvaloniaXamlIlTransformRoutedEvent());

Transformers.Add(new AvaloniaXamlIlControlTemplatePriorityTransformer());
Transformers.Add(new AvaloniaXamlIlMetadataRemover());
Transformers.Add(new AvaloniaXamlIlEnsureResourceDictionaryCapacityTransformer());
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
using System.Collections.Generic;
using System.Linq;
using Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers;
using XamlX.Ast;
using XamlX.Emit;
using XamlX.IL;
using XamlX.Transform;
using XamlX.TypeSystem;

namespace Avalonia.Markup.Xaml.Loader.CompilerExtensions.Transformers;

internal class AvaloniaXamlIlTransformRoutedEvent : IXamlAstTransformer
{
public IXamlAstNode Transform(AstTransformationContext context, IXamlAstNode node)
{
if (node is XamlAstNamePropertyReference prop
&& prop.TargetType is XamlAstClrTypeReference targetRef
&& prop.DeclaringType is XamlAstClrTypeReference declaringRef)
{
var xkt = context.GetAvaloniaTypes();
var interactiveType = xkt.Interactivity.Interactive;
var routedEventType = xkt.Interactivity.RoutedEvent;
var AddHandlerT = xkt.Interactivity.AddHandlerT;

if (interactiveType.IsAssignableFrom(targetRef.Type))
{
var eventName = $"{prop.Name}Event";
if (declaringRef.Type.GetAllFields().FirstOrDefault(f => f.IsStatic && f.Name == eventName) is { } eventField)
{
if (routedEventType.IsAssignableFrom(eventField.FieldType))
{
var instance = new XamlAstClrProperty(prop
, prop.Name
, targetRef.Type
, null
);
instance.Setters.Add(new XamlDirectCallAddHandler(eventField,
targetRef.Type,
xkt.Interactivity.AddHandler,
xkt.Interactivity.RoutedEventHandler
)
);
if (eventField.FieldType.GenericArguments?.Count == 1)
{
var agrument = eventField.FieldType.GenericArguments[0];
if (!agrument.Equals(xkt.Interactivity.RoutedEventArgs))
{
instance.Setters.Add(new XamlDirectCallAddHandler(eventField,
targetRef.Type,
xkt.Interactivity.AddHandlerT.MakeGenericMethod(new[] { agrument }),
context.Configuration.TypeSystem.FindType("System.EventHandler`1").MakeGenericType(agrument)
)
);
}
}
return instance;
}
else
{
//TODO: Throw Exception?
}
}
}
}
return node;
}

private sealed class XamlDirectCallAddHandler : IXamlILOptimizedEmitablePropertySetter
{
private readonly IXamlField _eventField;
private readonly IXamlType _declaringType;
private readonly IXamlMethod _addMethod;

public XamlDirectCallAddHandler(IXamlField eventField,
IXamlType declaringType,
IXamlMethod addMethod,
IXamlType routedEventHandler
)
{
Parameters = new[] { routedEventHandler };
_eventField = eventField;
_declaringType = declaringType;
_addMethod = addMethod;
}

public IXamlType TargetType => _declaringType;
public PropertySetterBinderParameters BinderParameters { get; } = new PropertySetterBinderParameters();
public IReadOnlyList<IXamlType> Parameters { get; }

public void Emit(IXamlILEmitter emitter)
=> emitter.EmitCall(_addMethod, true);

public void EmitWithArguments(XamlEmitContextWithLocals<IXamlILEmitter, XamlILNodeEmitResult> context,
IXamlILEmitter emitter,
IReadOnlyList<IXamlAstValueNode> arguments)
{

using (var loc = emitter.LocalsPool.GetLocal(_declaringType))
emitter
.Ldloc(loc.Local);

emitter.Ldfld(_eventField);

for (var i = 0; i < arguments.Count; ++i)
context.Emit(arguments[i], emitter, Parameters[i]);

emitter.Ldc_I4(5);
emitter.Ldc_I4(0);

emitter.EmitCall(_addMethod, true);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@

namespace Avalonia.Markup.Xaml.XamlIl.CompilerExtensions.Transformers
{
class AvaloniaXamlIlWellKnownTypes

sealed class AvaloniaXamlIlWellKnownTypes
{
public IXamlType RuntimeHelpers { get; }
public IXamlType AvaloniaObject { get; }
Expand Down Expand Up @@ -125,6 +126,48 @@ class AvaloniaXamlIlWellKnownTypes
public IXamlType IReadOnlyListOfT { get; }
public IXamlType ControlTemplate { get; }

sealed internal class InteractivityWellKnownTypes
{
public IXamlType Interactive { get; }
public IXamlType RoutedEvent { get; }
public IXamlType RoutedEventArgs { get; }
public IXamlType RoutedEventHandler { get; }
public IXamlMethod AddHandler { get; }
public IXamlMethod AddHandlerT { get; }

internal InteractivityWellKnownTypes(TransformerConfiguration cfg)
{
var ts = cfg.TypeSystem;
Interactive = ts.FindType("Avalonia.Interactivity.Interactive");
RoutedEvent = ts.FindType("Avalonia.Interactivity.RoutedEvent");
RoutedEventArgs = ts.FindType("Avalonia.Interactivity.RoutedEventArgs");
var eventHanlderT = ts.FindType("System.EventHandler`1");
RoutedEventHandler = eventHanlderT.MakeGenericType(RoutedEventArgs);
AddHandler = Interactive.FindMethod(m => m.IsPublic
&& !m.IsStatic
&& m.Name == "AddHandler"
&& m.Parameters.Count == 4
&& m.Parameters[0].Equals(RoutedEvent)
&& m.Parameters[1].Equals(cfg.WellKnownTypes.Delegate)
&& m.Parameters[2].IsEnum
&& m.Parameters[3].Equals(cfg.WellKnownTypes.Boolean)
);
AddHandlerT = Interactive.FindMethod(m => m.IsPublic
&& !m.IsStatic
&& m.Name == "AddHandler"
&& m.Parameters.Count == 4
&& RoutedEvent.IsAssignableFrom(m.Parameters[0])
&& m.Parameters[0].GenericArguments?.Count == 1 // This is specific this case workaround to check is generici method
&& (cfg.WellKnownTypes.Delegate).IsAssignableFrom(m.Parameters[1])
&& m.Parameters[2].IsEnum
&& m.Parameters[3].Equals(cfg.WellKnownTypes.Boolean) == true
);

}
}

public InteractivityWellKnownTypes Interactivity { get; }

public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
{
RuntimeHelpers = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.XamlIl.Runtime.XamlIlRuntimeHelpers");
Expand Down Expand Up @@ -242,7 +285,7 @@ public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
StyledElementClassesProperty =
StyledElement.Properties.First(x => x.Name == "Classes" && x.PropertyType.Equals(Classes));
ClassesBindMethod = cfg.TypeSystem.GetType("Avalonia.StyledElementExtensions")
.FindMethod( "BindClass", IDisposable, false, StyledElement,
.FindMethod("BindClass", IDisposable, false, StyledElement,
cfg.WellKnownTypes.String,
IBinding, cfg.WellKnownTypes.Object);

Expand Down Expand Up @@ -273,6 +316,8 @@ public AvaloniaXamlIlWellKnownTypes(TransformerConfiguration cfg)
ControlTheme = cfg.TypeSystem.GetType("Avalonia.Styling.ControlTheme");
ControlTemplate = cfg.TypeSystem.GetType("Avalonia.Markup.Xaml.Templates.ControlTemplate");
IReadOnlyListOfT = cfg.TypeSystem.GetType("System.Collections.Generic.IReadOnlyList`1");

Interactivity = new InteractivityWellKnownTypes(cfg);
}
}

Expand All @@ -293,7 +338,7 @@ public static AvaloniaXamlIlWellKnownTypes GetAvaloniaTypes(this XamlEmitContext
ctx.SetItem(rv = new AvaloniaXamlIlWellKnownTypes(ctx.Configuration));
return rv;
}

public static AvaloniaXamlIlWellKnownTypes GetAvaloniaTypes(this AstGroupTransformationContext ctx)
{
if (ctx.TryGetItem<AvaloniaXamlIlWellKnownTypes>(out var rv))
Expand Down

0 comments on commit 3f82e64

Please sign in to comment.