namespace Terminal.Gui.App;
#nullable enable
///
/// Provides helper methods for executing property change workflows in the Cancellable Work Pattern (CWP).
///
///
///
/// Used for workflows where a property value is modified, such as in or
/// , allowing pre- and post-change events to customize or cancel the change.
///
///
///
///
public static class CWPPropertyHelper
{
///
/// Executes a CWP workflow for a property change, with pre- and post-change events.
///
///
/// The type of the property value, which may be a nullable reference type (e.g.,
/// ?).
///
///
/// Reference to the current property value, which may be null for nullable types. If the change is not cancelled, this
/// will be set to .
///
/// The proposed new property value, which may be null for nullable types.
/// The virtual method invoked before the change, returning true to cancel.
/// The pre-change event raised to allow modification or cancellation.
/// The action that performs the actual work of setting the property (e.g., updating backing field, calling related methods).
/// The virtual method invoked after the change.
/// The post-change event raised to notify of the completed change.
///
/// The final value after the workflow, reflecting any modifications, which may be null for
/// nullable types.
///
/// True if the property was changed, false if cancelled.
///
/// Thrown if is null for non-nullable reference types after the
/// workflow.
///
///
///
/// string? current = _schemeName;
/// string? proposed = "Base";
/// Func<ValueChangingEventArgs<string?>, bool> onChanging = OnSchemeNameChanging;
/// EventHandler<ValueChangingEventArgs<string?>>? changingEvent = SchemeNameChanging;
/// Action<string?> doWork = value => _schemeName = value;
/// Action<ValueChangedEventArgs<string?>>? onChanged = OnSchemeNameChanged;
/// EventHandler<ValueChangedEventArgs<string?>>? changedEvent = SchemeNameChanged;
/// bool changed = CWPPropertyHelper.ChangeProperty(
/// current, proposed, onChanging, changingEvent, doWork, onChanged, changedEvent, out string? final);
///
///
public static bool ChangeProperty (
ref T currentValue,
T newValue,
Func, bool>? onChanging,
EventHandler>? changingEvent,
Action doWork,
Action>? onChanged,
EventHandler>? changedEvent,
out T finalValue
)
{
if (EqualityComparer.Default.Equals (currentValue, newValue))
{
finalValue = currentValue;
return false;
}
ValueChangingEventArgs args = new (currentValue, newValue);
if (onChanging is { })
{
bool cancelled = onChanging (args) || args.Handled;
if (cancelled)
{
finalValue = currentValue;
return false;
}
}
changingEvent?.Invoke (null, args);
if (args.Handled)
{
finalValue = currentValue;
return false;
}
// Validate NewValue for non-nullable reference types
if (args.NewValue is null && !typeof (T).IsValueType && !Nullable.GetUnderlyingType (typeof (T))?.IsValueType == true)
{
throw new InvalidOperationException ("NewValue cannot be null for non-nullable reference types.");
}
finalValue = args.NewValue;
// Do the work (set backing field, update related properties, etc.) BEFORE raising Changed events
doWork (finalValue);
ValueChangedEventArgs changedArgs = new (currentValue, finalValue);
currentValue = finalValue;
onChanged?.Invoke (changedArgs);
changedEvent?.Invoke (null, changedArgs);
return true;
}
}