From 40825fcb5f1350acbc23bad7f9d6a479cfe435c1 Mon Sep 17 00:00:00 2001 From: Matthieu LAURENT Date: Thu, 29 Jan 2026 21:24:06 +0100 Subject: [PATCH 1/6] add VisibleWhenAttribute and EnabledWhenAttribute to DialogAttributes This allows attributes in SimpleEffectDialog to automatically set their enabled state/visibility based on a condition --- Pinta.Core/Classes/DialogAttributes.cs | 25 +++++ .../Dialogs/SimpleEffectDialog.cs | 98 ++++++++++++++++++- 2 files changed, 118 insertions(+), 5 deletions(-) diff --git a/Pinta.Core/Classes/DialogAttributes.cs b/Pinta.Core/Classes/DialogAttributes.cs index b5c2e87056..fb321e3a8d 100644 --- a/Pinta.Core/Classes/DialogAttributes.cs +++ b/Pinta.Core/Classes/DialogAttributes.cs @@ -75,3 +75,28 @@ public sealed class StaticListAttribute : Attribute public string DictionaryName { get; set; } } + +/// +/// Attribute for controlling the visibility of a control based on a condition. +/// The control will be hidden when the condition evaluates to false. +/// +[AttributeUsage (AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)] +public sealed class VisibleWhenAttribute : Attribute +{ + public VisibleWhenAttribute (string conditionMethodName) => ConditionMethodName = conditionMethodName; + + public string ConditionMethodName { get; } +} + +/// +/// Attribute for controlling the enabled state of a control based on a condition. +/// The control will be disabled when the condition evaluates to false. +/// +[AttributeUsage (AttributeTargets.Field | AttributeTargets.Property, Inherited = false, AllowMultiple = false)] +public sealed class EnabledWhenAttribute : Attribute +{ + public EnabledWhenAttribute (string conditionMethodName) => ConditionMethodName = conditionMethodName; + + public string ConditionMethodName { get; } +} + diff --git a/Pinta.Gui.Widgets/Dialogs/SimpleEffectDialog.cs b/Pinta.Gui.Widgets/Dialogs/SimpleEffectDialog.cs index e586ee5342..41c2f63ce1 100644 --- a/Pinta.Gui.Widgets/Dialogs/SimpleEffectDialog.cs +++ b/Pinta.Gui.Widgets/Dialogs/SimpleEffectDialog.cs @@ -48,6 +48,17 @@ public sealed class SimpleEffectDialog : Gtk.Dialog private delegate bool TimeoutHandler (); TimeoutHandler? timeout_func; + private readonly EffectData effect_data; + + // Track widgets with conditional visibility/enabled, so we can update + // them when the effect data changes + private sealed record ConditionalWidget ( + Gtk.Widget Widget, + string? VisibleWhenMethodName, + string? EnabledWhenMethodName + ); + private readonly List conditional_widgets = new (); + /// Since this dialog is used by add-ins, the IAddinLocalizer allows for translations to be /// fetched from the appropriate place. /// @@ -82,6 +93,9 @@ public SimpleEffectDialog ( contentAreaBox.Append (widget); OnClose += (_, _) => HandleClose (); + + // Keep reference to effect data, so it can be used for handling conditional widgets + effect_data = effectData; } /// @@ -132,8 +146,8 @@ private void HandleClose () .GetMembers () .Where (IsInstanceFieldOrProperty) .Where (IsCustomProperty) + .Where (member => !member.GetCustomAttributes (false).Any ()) .Select (CreateSettings) - .Where (settings => !settings.skip) .Select (settings => GenerateWidgetsForMember (settings, effectData, localizer, workspace)) .SelectMany (widgets => widgets); @@ -160,7 +174,9 @@ private sealed record MemberSettings ( MemberReflector reflector, string caption, string? hint, - bool skip); + string? visibleWhenMethodName, + string? enabledWhenMethodName + ); private static MemberSettings CreateSettings (MemberInfo memberInfo) { @@ -172,11 +188,24 @@ private static MemberSettings CreateSettings (MemberInfo memberInfo) .Select (h => h.Caption) .FirstOrDefault (); + string? visibleConditionMethodName = + reflector.Attributes + .OfType () + .Select (v => v.ConditionMethodName) + .FirstOrDefault (); + + string? enabledConditionMethodName = + reflector.Attributes + .OfType () + .Select (e => e.ConditionMethodName) + .FirstOrDefault (); + return new ( reflector: reflector, caption: caption ?? MakeCaption (memberInfo.Name), hint: reflector.Attributes.OfType ().Select (h => h.Hint).FirstOrDefault (), - skip: reflector.Attributes.OfType ().Any ()); + visibleWhenMethodName: visibleConditionMethodName, + enabledWhenMethodName: enabledConditionMethodName); } private static string MakeCaption (string name) @@ -209,6 +238,42 @@ static IEnumerable GenerateCharacters (string name) } } + /// + /// Evaluates a condition from a property or method of EffectData. + /// + private static bool EvaluateCondition (object effectData, string methodName) + { + Type type = effectData.GetType (); + + // Try to find a property first + PropertyInfo? property = type.GetProperty (methodName, BindingFlags.Public | BindingFlags.Instance); + if (property is not null && property.PropertyType == typeof (bool)) { + return (bool) property.GetValue (effectData)!; + } + // If we couldn't find a property, try to find a method + MethodInfo? method = type.GetMethod (methodName, BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null); + if (method is not null && method.ReturnType == typeof (bool)) { + return (bool) method.Invoke (effectData, null)!; + } + + System.Diagnostics.Debug.WriteLine ($"Warning: Could not find condition property/method '{methodName}' on type '{type.Name}'."); + return true; // Fallback to true + } + + /// + /// Updates all widgets with conditional attributes. + /// + private void UpdateConditionalWidgets (EffectData effectData) + { + foreach (var widget in conditional_widgets) { + if (widget.VisibleWhenMethodName is not null) + widget.Widget.Visible = EvaluateCondition (effectData, widget.VisibleWhenMethodName); + + if (widget.EnabledWhenMethodName is not null) + widget.Widget.Sensitive = EvaluateCondition (effectData, widget.EnabledWhenMethodName); + } + } + private IEnumerable GenerateWidgetsForMember ( MemberSettings settings, EffectData effectData, @@ -217,8 +282,28 @@ static IEnumerable GenerateCharacters (string name) { WidgetFactory? widgetFactory = GetWidgetFactory (settings); - if (widgetFactory is not null) - yield return widgetFactory (localizer.GetString (settings.caption), effectData, settings, workspace); + if (widgetFactory is not null) { + Gtk.Widget widget = widgetFactory (localizer.GetString (settings.caption), effectData, settings, workspace); + + // Keep a reference to widget if it has conditional attributes so we can update it later + if (settings.visibleWhenMethodName is not null || settings.enabledWhenMethodName is not null) { + + // Apply the initial state + if (settings.visibleWhenMethodName is not null) + widget.Visible = EvaluateCondition (effectData, settings.visibleWhenMethodName); + + if (settings.enabledWhenMethodName is not null) + widget.Sensitive = EvaluateCondition (effectData, settings.enabledWhenMethodName); + + conditional_widgets.Add (new ConditionalWidget ( + widget, + settings.visibleWhenMethodName, + settings.enabledWhenMethodName) + ); + } + + yield return widget; + } if (settings.hint != null) yield return CreateHintLabel (localizer.GetString (settings.hint)); @@ -530,6 +615,9 @@ private void SetAndNotify (MemberReflector reflector, object o, object val) { reflector.SetValue (o, val); EffectDataChanged?.Invoke (this, new PropertyChangedEventArgs (reflector.OriginalMemberInfo.Name)); + + // Update conditional widgets when any property changes + UpdateConditionalWidgets (effect_data); } private Gtk.Widget CreateSeed ( From eb1155ecdbc2cbe105beb6bafcaaee0beb95942e Mon Sep 17 00:00:00 2001 From: Matthieu LAURENT Date: Thu, 29 Jan 2026 21:48:48 +0100 Subject: [PATCH 2/6] Retrofit effects with VisibleWhen --- Pinta.Effects/Effects/CellsEffect.cs | 8 ++++++++ Pinta.Effects/Effects/CloudsEffect.cs | 8 ++++++++ Pinta.Effects/Effects/JuliaFractalEffect.cs | 8 ++++++++ Pinta.Effects/Effects/MandelbrotFractalEffect.cs | 8 ++++++++ Pinta.Effects/Effects/VoronoiDiagramEffect.cs | 9 +++++++++ 5 files changed, 41 insertions(+) diff --git a/Pinta.Effects/Effects/CellsEffect.cs b/Pinta.Effects/Effects/CellsEffect.cs index 5b810dcc01..43a16ab3bf 100644 --- a/Pinta.Effects/Effects/CellsEffect.cs +++ b/Pinta.Effects/Effects/CellsEffect.cs @@ -181,9 +181,11 @@ public sealed class CellsData : EffectData public ColorSchemeSource ColorSchemeSource { get; set; } = ColorSchemeSource.PresetGradient; [Caption ("Color Scheme")] + [VisibleWhen (nameof (ShowColorScheme))] public PresetGradients ColorScheme { get; set; } = PresetGradients.BlackAndWhite; [Caption ("Random Color Scheme Seed")] + [VisibleWhen (nameof (ShowColorSchemeSeed))] public RandomSeed ColorSchemeSeed { get; set; } = new (0); [Caption ("Reverse Color Scheme")] @@ -195,5 +197,11 @@ public sealed class CellsData : EffectData [Caption ("Quality")] [MinimumValue (1), MaximumValue (4)] public int Quality { get; set; } = 3; + + [Skip] + public bool ShowColorScheme => ColorSchemeSource == ColorSchemeSource.PresetGradient; + + [Skip] + public bool ShowColorSchemeSeed => ColorSchemeSource == ColorSchemeSource.Random; } } diff --git a/Pinta.Effects/Effects/CloudsEffect.cs b/Pinta.Effects/Effects/CloudsEffect.cs index 6fb5d6a0ee..cd55d70c39 100644 --- a/Pinta.Effects/Effects/CloudsEffect.cs +++ b/Pinta.Effects/Effects/CloudsEffect.cs @@ -146,12 +146,20 @@ public sealed class CloudsData : EffectData public ColorSchemeSource ColorSchemeSource { get; set; } = ColorSchemeSource.SelectedColors; [Caption ("Color Scheme")] + [VisibleWhen (nameof (ShowColorScheme))] public PresetGradients ColorScheme { get; set; } = PresetGradients.BeautifulItaly; [Caption ("Random Color Scheme Seed")] + [VisibleWhen (nameof (ShowColorSchemeSeed))] public RandomSeed ColorSchemeSeed { get; set; } = new (0); [Caption ("Reverse Color Scheme")] public bool ReverseColorScheme { get; set; } = false; + + [Skip] + public bool ShowColorScheme => ColorSchemeSource == ColorSchemeSource.PresetGradient; + + [Skip] + public bool ShowColorSchemeSeed => ColorSchemeSource == ColorSchemeSource.Random; } } diff --git a/Pinta.Effects/Effects/JuliaFractalEffect.cs b/Pinta.Effects/Effects/JuliaFractalEffect.cs index ee25721a8f..7b0fbdd3e4 100644 --- a/Pinta.Effects/Effects/JuliaFractalEffect.cs +++ b/Pinta.Effects/Effects/JuliaFractalEffect.cs @@ -156,9 +156,11 @@ public sealed class JuliaFractalData : EffectData public ColorSchemeSource ColorSchemeSource { get; set; } = ColorSchemeSource.PresetGradient; [Caption ("Color Scheme")] + [VisibleWhen (nameof (ShowColorScheme))] public PresetGradients ColorScheme { get; set; } = PresetGradients.Bonfire; [Caption ("Random Color Scheme Seed")] + [VisibleWhen (nameof (ShowColorSchemeSeed))] public RandomSeed ColorSchemeSeed { get; set; } = new (0); [Caption ("Reverse Color Scheme")] @@ -166,5 +168,11 @@ public sealed class JuliaFractalData : EffectData [Caption ("Angle")] public DegreesAngle Angle { get; set; } = new (0); + + [Skip] + public bool ShowColorScheme => ColorSchemeSource == ColorSchemeSource.PresetGradient; + + [Skip] + public bool ShowColorSchemeSeed => ColorSchemeSource == ColorSchemeSource.Random; } } diff --git a/Pinta.Effects/Effects/MandelbrotFractalEffect.cs b/Pinta.Effects/Effects/MandelbrotFractalEffect.cs index eaa387cd68..8f3d825960 100644 --- a/Pinta.Effects/Effects/MandelbrotFractalEffect.cs +++ b/Pinta.Effects/Effects/MandelbrotFractalEffect.cs @@ -172,9 +172,11 @@ public sealed class MandelbrotFractalData : EffectData public ColorSchemeSource ColorSchemeSource { get; set; } = ColorSchemeSource.PresetGradient; [Caption ("Color Scheme")] + [VisibleWhen (nameof (ShowColorScheme))] public PresetGradients ColorScheme { get; set; } = PresetGradients.Electric; [Caption ("Random Color Scheme Seed")] + [VisibleWhen (nameof (ShowColorSchemeSeed))] public RandomSeed ColorSchemeSeed { get; set; } = new (0); [Caption ("Reverse Color Scheme")] @@ -182,5 +184,11 @@ public sealed class MandelbrotFractalData : EffectData [Caption ("Invert Colors")] public bool InvertColors { get; set; } = false; + + [Skip] + public bool ShowColorScheme => ColorSchemeSource == ColorSchemeSource.PresetGradient; + + [Skip] + public bool ShowColorSchemeSeed => ColorSchemeSource == ColorSchemeSource.Random; } } diff --git a/Pinta.Effects/Effects/VoronoiDiagramEffect.cs b/Pinta.Effects/Effects/VoronoiDiagramEffect.cs index 0fb9d2ba56..b94e74a0e8 100644 --- a/Pinta.Effects/Effects/VoronoiDiagramEffect.cs +++ b/Pinta.Effects/Effects/VoronoiDiagramEffect.cs @@ -202,6 +202,7 @@ public sealed class VoronoiDiagramData : EffectData public PointArrangement PointArrangement { get; set; } = PointArrangement.Random; [Caption ("Random Point Locations")] + [VisibleWhen (nameof (ShowRandomPointLocation))] public RandomSeed RandomPointLocations { get; set; } = new (0); [Caption ("Show Points")] @@ -209,14 +210,22 @@ public sealed class VoronoiDiagramData : EffectData [Caption ("Point Size")] [MinimumValue (1), MaximumValue (16), IncrementValue (1)] + [VisibleWhen (nameof (ShowPointConfig))] public double PointSize { get; set; } = 4; [Caption ("Point Color")] + [VisibleWhen (nameof (ShowPointConfig))] public Color PointColor { get; set; } = Color.Black; [Caption ("Quality")] [MinimumValue (1), MaximumValue (4)] public int Quality { get; set; } = 3; + + [Skip] + public bool ShowRandomPointLocation => PointArrangement == PointArrangement.Random; + + [Skip] + public bool ShowPointConfig => ShowPoints; } public enum ColorSorting From 8382d2de721ccd3628d820b6c7fe87b591292313 Mon Sep 17 00:00:00 2001 From: Matthieu LAURENT Date: Thu, 29 Jan 2026 22:14:54 +0100 Subject: [PATCH 3/6] update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 673c8d6d42..8112598fb7 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,13 @@ Thanks to the following contributors who worked on this release: - @cameronwhite - @Lehonti - @spaghetti22 +- @Matthieu-LAURENT39 ### Added - The splatter brush now allows the minimum and maximum splatter size to be configured separately from the brush width ### Changed +- Effect dialogs now hide options that are not currently relevant (#1960) ### Fixed - Fixed a bug where duplicate submenus could be produced by add-ins with effect categories that were not translated (#1933, #1935) From cb67b69d046ebd028df566edb10134f9a5f355ef Mon Sep 17 00:00:00 2001 From: Matthieu LAURENT Date: Fri, 30 Jan 2026 07:58:35 +0100 Subject: [PATCH 4/6] Move EvaluateCondition to ReflectionHelper --- Pinta.Gui.Widgets/Dialogs/ReflectionHelper.cs | 25 +++++++++++++++ .../Dialogs/SimpleEffectDialog.cs | 32 +++---------------- 2 files changed, 29 insertions(+), 28 deletions(-) diff --git a/Pinta.Gui.Widgets/Dialogs/ReflectionHelper.cs b/Pinta.Gui.Widgets/Dialogs/ReflectionHelper.cs index b9b73fb744..02a08ddcff 100644 --- a/Pinta.Gui.Widgets/Dialogs/ReflectionHelper.cs +++ b/Pinta.Gui.Widgets/Dialogs/ReflectionHelper.cs @@ -20,4 +20,29 @@ internal static class ReflectionHelper else throw new ArgumentException ($"Member \'{name}\' does not exist"); } + + /// + /// Evaluates a condition from a property or method of an object. + /// + /// The object containing the property or method. + /// The name of the property or method to evaluate. + /// The boolean result of the property or method. + public static bool EvaluateCondition (object source, string methodName) + { + Type type = source.GetType (); + + // Try to find a property first + PropertyInfo? property = type.GetProperty (methodName, BindingFlags.Public | BindingFlags.Instance); + if (property is not null && property.PropertyType == typeof (bool)) { + return (bool) property.GetValue (source)!; + } + // If we couldn't find a property, try to find a method + MethodInfo? method = type.GetMethod (methodName, BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null); + if (method is not null && method.ReturnType == typeof (bool)) { + return (bool) method.Invoke (source, null)!; + } + + System.Diagnostics.Debug.WriteLine ($"Warning: Could not find condition property/method '{methodName}' on type '{type.Name}'."); + return true; // Fallback to true + } } diff --git a/Pinta.Gui.Widgets/Dialogs/SimpleEffectDialog.cs b/Pinta.Gui.Widgets/Dialogs/SimpleEffectDialog.cs index 41c2f63ce1..24521e67ca 100644 --- a/Pinta.Gui.Widgets/Dialogs/SimpleEffectDialog.cs +++ b/Pinta.Gui.Widgets/Dialogs/SimpleEffectDialog.cs @@ -238,28 +238,6 @@ static IEnumerable GenerateCharacters (string name) } } - /// - /// Evaluates a condition from a property or method of EffectData. - /// - private static bool EvaluateCondition (object effectData, string methodName) - { - Type type = effectData.GetType (); - - // Try to find a property first - PropertyInfo? property = type.GetProperty (methodName, BindingFlags.Public | BindingFlags.Instance); - if (property is not null && property.PropertyType == typeof (bool)) { - return (bool) property.GetValue (effectData)!; - } - // If we couldn't find a property, try to find a method - MethodInfo? method = type.GetMethod (methodName, BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null); - if (method is not null && method.ReturnType == typeof (bool)) { - return (bool) method.Invoke (effectData, null)!; - } - - System.Diagnostics.Debug.WriteLine ($"Warning: Could not find condition property/method '{methodName}' on type '{type.Name}'."); - return true; // Fallback to true - } - /// /// Updates all widgets with conditional attributes. /// @@ -267,13 +245,12 @@ private void UpdateConditionalWidgets (EffectData effectData) { foreach (var widget in conditional_widgets) { if (widget.VisibleWhenMethodName is not null) - widget.Widget.Visible = EvaluateCondition (effectData, widget.VisibleWhenMethodName); + widget.Widget.Visible = ReflectionHelper.EvaluateCondition (effectData, widget.VisibleWhenMethodName); if (widget.EnabledWhenMethodName is not null) - widget.Widget.Sensitive = EvaluateCondition (effectData, widget.EnabledWhenMethodName); + widget.Widget.Sensitive = ReflectionHelper.EvaluateCondition (effectData, widget.EnabledWhenMethodName); } } - private IEnumerable GenerateWidgetsForMember ( MemberSettings settings, EffectData effectData, @@ -290,10 +267,10 @@ private void UpdateConditionalWidgets (EffectData effectData) // Apply the initial state if (settings.visibleWhenMethodName is not null) - widget.Visible = EvaluateCondition (effectData, settings.visibleWhenMethodName); + widget.Visible = ReflectionHelper.EvaluateCondition (effectData, settings.visibleWhenMethodName); if (settings.enabledWhenMethodName is not null) - widget.Sensitive = EvaluateCondition (effectData, settings.enabledWhenMethodName); + widget.Sensitive = ReflectionHelper.EvaluateCondition (effectData, settings.enabledWhenMethodName); conditional_widgets.Add (new ConditionalWidget ( widget, @@ -301,7 +278,6 @@ private void UpdateConditionalWidgets (EffectData effectData) settings.enabledWhenMethodName) ); } - yield return widget; } From 2bacafd745e12b6e92715793ea83c1ff42c01367 Mon Sep 17 00:00:00 2001 From: Matthieu LAURENT Date: Fri, 30 Jan 2026 08:15:09 +0100 Subject: [PATCH 5/6] Only evaluate reflextion once; add CreateConditionDelegate This also makes CreateConditionDelegate (and EvaluateCondition) hard-fail if the proprety/method is missing --- Pinta.Gui.Widgets/Dialogs/ReflectionHelper.cs | 17 +++++++--- .../Dialogs/SimpleEffectDialog.cs | 33 ++++++++++++------- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/Pinta.Gui.Widgets/Dialogs/ReflectionHelper.cs b/Pinta.Gui.Widgets/Dialogs/ReflectionHelper.cs index 02a08ddcff..07b74ebb1f 100644 --- a/Pinta.Gui.Widgets/Dialogs/ReflectionHelper.cs +++ b/Pinta.Gui.Widgets/Dialogs/ReflectionHelper.cs @@ -28,21 +28,30 @@ internal static class ReflectionHelper /// The name of the property or method to evaluate. /// The boolean result of the property or method. public static bool EvaluateCondition (object source, string methodName) + => CreateConditionDelegate (source, methodName) (); + + /// + /// Creates a delegate for evaluating a condition from a property or method of an object. + /// This avoids needing to redo reflection lookups when the condition needs to be evaluated frequently. + /// + /// The object containing the property or method. + /// The name of the property or method to evaluate. + /// A delegate that evaluates the condition, giving the boolean result of the property or method. + public static Func CreateConditionDelegate (object source, string methodName) { Type type = source.GetType (); // Try to find a property first PropertyInfo? property = type.GetProperty (methodName, BindingFlags.Public | BindingFlags.Instance); if (property is not null && property.PropertyType == typeof (bool)) { - return (bool) property.GetValue (source)!; + return () => (bool) property.GetValue (source)!; } // If we couldn't find a property, try to find a method MethodInfo? method = type.GetMethod (methodName, BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null); if (method is not null && method.ReturnType == typeof (bool)) { - return (bool) method.Invoke (source, null)!; + return () => (bool) method.Invoke (source, null)!; } - System.Diagnostics.Debug.WriteLine ($"Warning: Could not find condition property/method '{methodName}' on type '{type.Name}'."); - return true; // Fallback to true + throw new ArgumentException ($"Member \'{methodName}\' is not a boolean property or method"); } } diff --git a/Pinta.Gui.Widgets/Dialogs/SimpleEffectDialog.cs b/Pinta.Gui.Widgets/Dialogs/SimpleEffectDialog.cs index 24521e67ca..2afc93b99b 100644 --- a/Pinta.Gui.Widgets/Dialogs/SimpleEffectDialog.cs +++ b/Pinta.Gui.Widgets/Dialogs/SimpleEffectDialog.cs @@ -54,8 +54,8 @@ public sealed class SimpleEffectDialog : Gtk.Dialog // them when the effect data changes private sealed record ConditionalWidget ( Gtk.Widget Widget, - string? VisibleWhenMethodName, - string? EnabledWhenMethodName + Func? VisibleWhenDelegate, + Func? EnabledWhenDelegate ); private readonly List conditional_widgets = new (); @@ -244,11 +244,11 @@ static IEnumerable GenerateCharacters (string name) private void UpdateConditionalWidgets (EffectData effectData) { foreach (var widget in conditional_widgets) { - if (widget.VisibleWhenMethodName is not null) - widget.Widget.Visible = ReflectionHelper.EvaluateCondition (effectData, widget.VisibleWhenMethodName); + if (widget.VisibleWhenDelegate is not null) + widget.Widget.Visible = widget.VisibleWhenDelegate (); - if (widget.EnabledWhenMethodName is not null) - widget.Widget.Sensitive = ReflectionHelper.EvaluateCondition (effectData, widget.EnabledWhenMethodName); + if (widget.EnabledWhenDelegate is not null) + widget.Widget.Sensitive = widget.EnabledWhenDelegate (); } } private IEnumerable GenerateWidgetsForMember ( @@ -265,17 +265,26 @@ private void UpdateConditionalWidgets (EffectData effectData) // Keep a reference to widget if it has conditional attributes so we can update it later if (settings.visibleWhenMethodName is not null || settings.enabledWhenMethodName is not null) { + // Create delegates for condition evaluation (do reflection once, not on every update) + Func? visibleDelegate = settings.visibleWhenMethodName is not null + ? ReflectionHelper.CreateConditionDelegate (effectData, settings.visibleWhenMethodName) + : null; + + Func? enabledDelegate = settings.enabledWhenMethodName is not null + ? ReflectionHelper.CreateConditionDelegate (effectData, settings.enabledWhenMethodName) + : null; + // Apply the initial state - if (settings.visibleWhenMethodName is not null) - widget.Visible = ReflectionHelper.EvaluateCondition (effectData, settings.visibleWhenMethodName); + if (visibleDelegate is not null) + widget.Visible = visibleDelegate (); - if (settings.enabledWhenMethodName is not null) - widget.Sensitive = ReflectionHelper.EvaluateCondition (effectData, settings.enabledWhenMethodName); + if (enabledDelegate is not null) + widget.Sensitive = enabledDelegate (); conditional_widgets.Add (new ConditionalWidget ( widget, - settings.visibleWhenMethodName, - settings.enabledWhenMethodName) + visibleDelegate, + enabledDelegate) ); } yield return widget; From dc014577e4aab4725d02c73aacc51dc80440876e Mon Sep 17 00:00:00 2001 From: Matthieu LAURENT Date: Sat, 31 Jan 2026 15:35:37 +0100 Subject: [PATCH 6/6] Optimize CreateConditionDelegate by using CreateDelegate --- Pinta.Gui.Widgets/Dialogs/ReflectionHelper.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/Pinta.Gui.Widgets/Dialogs/ReflectionHelper.cs b/Pinta.Gui.Widgets/Dialogs/ReflectionHelper.cs index 07b74ebb1f..29f8428108 100644 --- a/Pinta.Gui.Widgets/Dialogs/ReflectionHelper.cs +++ b/Pinta.Gui.Widgets/Dialogs/ReflectionHelper.cs @@ -44,12 +44,15 @@ public static Func CreateConditionDelegate (object source, string methodNa // Try to find a property first PropertyInfo? property = type.GetProperty (methodName, BindingFlags.Public | BindingFlags.Instance); if (property is not null && property.PropertyType == typeof (bool)) { - return () => (bool) property.GetValue (source)!; + MethodInfo? getter = property.GetGetMethod (); + if (getter is not null) { + return (Func) getter.CreateDelegate (typeof (Func), source); + } } // If we couldn't find a property, try to find a method MethodInfo? method = type.GetMethod (methodName, BindingFlags.Public | BindingFlags.Instance, null, Type.EmptyTypes, null); if (method is not null && method.ReturnType == typeof (bool)) { - return () => (bool) method.Invoke (source, null)!; + return (Func) method.CreateDelegate (typeof (Func), source); } throw new ArgumentException ($"Member \'{methodName}\' is not a boolean property or method");