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)
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.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
diff --git a/Pinta.Gui.Widgets/Dialogs/ReflectionHelper.cs b/Pinta.Gui.Widgets/Dialogs/ReflectionHelper.cs
index b9b73fb744..29f8428108 100644
--- a/Pinta.Gui.Widgets/Dialogs/ReflectionHelper.cs
+++ b/Pinta.Gui.Widgets/Dialogs/ReflectionHelper.cs
@@ -20,4 +20,41 @@ 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)
+ => 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)) {
+ 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 (Func) method.CreateDelegate (typeof (Func), source);
+ }
+
+ 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 e586ee5342..2afc93b99b 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,
+ Func? VisibleWhenDelegate,
+ Func? EnabledWhenDelegate
+ );
+ 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,19 @@ static IEnumerable GenerateCharacters (string name)
}
}
+ ///
+ /// Updates all widgets with conditional attributes.
+ ///
+ private void UpdateConditionalWidgets (EffectData effectData)
+ {
+ foreach (var widget in conditional_widgets) {
+ if (widget.VisibleWhenDelegate is not null)
+ widget.Widget.Visible = widget.VisibleWhenDelegate ();
+
+ if (widget.EnabledWhenDelegate is not null)
+ widget.Widget.Sensitive = widget.EnabledWhenDelegate ();
+ }
+ }
private IEnumerable GenerateWidgetsForMember (
MemberSettings settings,
EffectData effectData,
@@ -217,8 +259,36 @@ 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) {
+
+ // 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 (visibleDelegate is not null)
+ widget.Visible = visibleDelegate ();
+
+ if (enabledDelegate is not null)
+ widget.Sensitive = enabledDelegate ();
+
+ conditional_widgets.Add (new ConditionalWidget (
+ widget,
+ visibleDelegate,
+ enabledDelegate)
+ );
+ }
+ yield return widget;
+ }
if (settings.hint != null)
yield return CreateHintLabel (localizer.GetString (settings.hint));
@@ -530,6 +600,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 (