diff --git a/src/HotAvalonia/AvaloniaAssetManager.cs b/src/HotAvalonia/AvaloniaAssetManager.cs index 50edd3a..41bdd44 100644 --- a/src/HotAvalonia/AvaloniaAssetManager.cs +++ b/src/HotAvalonia/AvaloniaAssetManager.cs @@ -10,6 +10,7 @@ using Avalonia.Platform; using HotAvalonia.Assets; using HotAvalonia.DependencyInjection; +using HotAvalonia.Helpers; using HotAvalonia.Reflection.Inject; namespace HotAvalonia; @@ -45,34 +46,13 @@ public AvaloniaAssetManager(IServiceProvider serviceProvider) { _serviceProvider = serviceProvider ?? throw new ArgumentNullException(nameof(serviceProvider)); - Type[] converterParameters = [typeof(ITypeDescriptorContext), typeof(CultureInfo), typeof(object)]; - MethodInfo toIcon = typeof(IconTypeConverter).GetMethod(nameof(IconTypeConverter.ConvertFrom), converterParameters)!; - MethodInfo toBitmap = typeof(BitmapTypeConverter).GetMethod(nameof(BitmapTypeConverter.ConvertFrom), converterParameters)!; - - _injections = - [ - // Ideally, we would inject into something like - // `Avalonia.PropertyStore.EffectiveValue`1.SetLocalValueAndRaise` - // to catch all these scenarios, including custom ones, automatically. - // However, generic injections are quite flaky to say the least, - // so it's better to avoid them if we want predictable results. - // - // Perhaps we could add automatic detection of similar cases via reflection? - ..new (Type Type, string Name, AvaloniaProperty Property)[] - { - (typeof(Window), nameof(Window.Icon), Window.IconProperty), - (typeof(Image), nameof(Image.Source), Image.SourceProperty), - (typeof(ImageBrush), nameof(ImageBrush.Source), ImageBrush.SourceProperty), - (typeof(CroppedBitmap), nameof(CroppedBitmap.Source), CroppedBitmap.SourceProperty), - (typeof(ImageDrawing), nameof(ImageDrawing.ImageSource), ImageDrawing.ImageSourceProperty), - }.Select(static x => CallbackInjector.Inject( - x.Type.GetProperty(x.Name).SetMethod, - ([Caller] AvaloniaObject obj, object value) => TryBindDynamicAsset(obj, x.Property, value)) - ), - - CallbackInjector.Inject(toIcon, TryLoadDynamicAsset), - CallbackInjector.Inject(toBitmap, TryLoadDynamicAsset), - ]; + if (!TryInjectAssetCallbacks(out _injections)) + { + LoggingHelper.Logger?.Log( + this, + "Failed to subscribe to asset loading events. Icons and images won't be reloaded upon file changes." + ); + } } /// @@ -147,4 +127,54 @@ private static void TryBindDynamicAsset(AvaloniaObject obj, AvaloniaProperty pro if (value is IObservable observableValue) obj.Bind(property, observableValue); } + + /// + /// Attempts to inject asset-related callbacks for various Avalonia control properties. + /// + /// + /// When this method returns, contains an array of instances + /// for each successful callback injection. + /// + /// + /// true if the callback injections were successful; + /// otherwise, false. + /// + private static bool TryInjectAssetCallbacks(out IInjection[] injections) + { + if (!CallbackInjector.SupportsOptimizedMethods) + { + injections = []; + return false; + } + + Type[] converterParameters = [typeof(ITypeDescriptorContext), typeof(CultureInfo), typeof(object)]; + MethodInfo toIcon = typeof(IconTypeConverter).GetMethod(nameof(IconTypeConverter.ConvertFrom), converterParameters)!; + MethodInfo toBitmap = typeof(BitmapTypeConverter).GetMethod(nameof(BitmapTypeConverter.ConvertFrom), converterParameters)!; + + injections = + [ + // Ideally, we would inject into something like + // `Avalonia.PropertyStore.EffectiveValue`1.SetLocalValueAndRaise` + // to catch all these scenarios, including custom ones, automatically. + // However, generic injections are quite flaky to say the least, + // so it's better to avoid them if we want predictable results. + // + // Perhaps we could add automatic detection of similar cases via reflection? + ..new (Type Type, string Name, AvaloniaProperty Property)[] + { + (typeof(Window), nameof(Window.Icon), Window.IconProperty), + (typeof(Image), nameof(Image.Source), Image.SourceProperty), + (typeof(ImageBrush), nameof(ImageBrush.Source), ImageBrush.SourceProperty), + (typeof(CroppedBitmap), nameof(CroppedBitmap.Source), CroppedBitmap.SourceProperty), + (typeof(ImageDrawing), nameof(ImageDrawing.ImageSource), ImageDrawing.ImageSourceProperty), + }.Select(static x => CallbackInjector.Inject( + x.Type.GetProperty(x.Name).SetMethod, + ([Caller] AvaloniaObject obj, object value) => TryBindDynamicAsset(obj, x.Property, value)) + ), + + CallbackInjector.Inject(toIcon, TryLoadDynamicAsset), + CallbackInjector.Inject(toBitmap, TryLoadDynamicAsset), + ]; + return true; + } } diff --git a/src/HotAvalonia/Helpers/AvaloniaControlHelper.cs b/src/HotAvalonia/Helpers/AvaloniaControlHelper.cs index da2d650..e94a25a 100644 --- a/src/HotAvalonia/Helpers/AvaloniaControlHelper.cs +++ b/src/HotAvalonia/Helpers/AvaloniaControlHelper.cs @@ -56,7 +56,7 @@ static AvaloniaControlHelper() Type? sreMethodBuilder = xamlLoaderAssembly.GetType("XamlX.IL.SreTypeSystem+SreTypeBuilder+SreMethodBuilder"); ConstructorInfo? sreMethodBuilderCtor = sreMethodBuilder?.GetConstructors(InstanceMember).FirstOrDefault(x => x.GetParameters().Length > 1); - if (sreMethodBuilderCtor is not null) + if (sreMethodBuilderCtor is not null && CallbackInjector.SupportsOptimizedMethods) s_sreMethodBuilderInjection = CallbackInjector.Inject(sreMethodBuilderCtor, OnNewSreMethodBuilder); } diff --git a/src/HotAvalonia/Reflection/Inject/CallbackInjector.cs b/src/HotAvalonia/Reflection/Inject/CallbackInjector.cs index 1786e4a..1fced9d 100644 --- a/src/HotAvalonia/Reflection/Inject/CallbackInjector.cs +++ b/src/HotAvalonia/Reflection/Inject/CallbackInjector.cs @@ -19,6 +19,11 @@ internal static class CallbackInjector /// public static bool IsSupported => MethodInjector.IsSupported; + /// + /// Indicates whether callback injection is supported in optimized assemblies. + /// + public static bool SupportsOptimizedMethods => MethodInjector.SupportsOptimizedMethods; + /// /// Throws an exception if callback injection is not supported in the current runtime environment. /// diff --git a/src/HotAvalonia/Reflection/Inject/MethodInjector.cs b/src/HotAvalonia/Reflection/Inject/MethodInjector.cs index 989c328..32f1ff5 100644 --- a/src/HotAvalonia/Reflection/Inject/MethodInjector.cs +++ b/src/HotAvalonia/Reflection/Inject/MethodInjector.cs @@ -38,6 +38,11 @@ internal static class MethodInjector /// public static bool IsSupported => InjectionType is not InjectionType.None; + /// + /// Indicates whether method injection is supported in optimized assemblies. + /// + public static bool SupportsOptimizedMethods => InjectionType is InjectionType.Native; + /// /// Injects a replacement method implementation for the specified source method. ///