diff --git a/MonkeyLoader/Configuration/ConfigKeyBidirectionalBinding.cs b/MonkeyLoader/Configuration/ConfigKeyBidirectionalBinding.cs new file mode 100644 index 0000000..807f2c0 --- /dev/null +++ b/MonkeyLoader/Configuration/ConfigKeyBidirectionalBinding.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace MonkeyLoader.Configuration +{ + /// + /// Represents the functionality for an , + /// which propagate any changes in value in both directions + /// between the two associated config items. + /// + /// + public sealed class ConfigKeyBidirectionalBinding : IConfigKeyBidirectionalBinding + { + /// + public IDefiningConfigKey Owner { get; private set; } = null!; + + IDefiningConfigKey IConfigKeyBidirectionalBinding.Owner => Owner; + + /// + public IDefiningConfigKey Target { get; } + + IDefiningConfigKey IConfigKeyBidirectionalBinding.Target => Target; + + /// + /// Creates a new bidirectional binding targeting the given config item. + /// + /// The other config item to propagate changes to and from. + public ConfigKeyBidirectionalBinding(IDefiningConfigKey target) + { + Target = target; + } + + /// + /// Adds the Changed event + /// listeners to propagate changes between the linked config items. + /// + /// When the binding has already been initialized or is targeted at itself. + /// + public void Initialize(IDefiningConfigKey entity) + { + if (Owner is not null) + throw new InvalidOperationException($"This binding targetting [{Target}] is already owned by [{Owner}]!"); + + if (ReferenceEquals(Target, entity)) + throw new InvalidOperationException($"Can't bind [{Target}] to itself!"); + + Owner = entity; + + // Shouldn't need circular check because Changed event is only fired for actual changes + Owner.Changed += (_, args) => Target.SetValue(args.NewValue!, args.GetPropagatedEventLabel(ConfigKeyBindings.SetFromBidirectionalOwnerEventLabel)); + Target.Changed += (_, args) => Owner.SetValue(args.NewValue!, args.GetPropagatedEventLabel(ConfigKeyBindings.SetFromBidirectionalTargetEventLabel)); + } + } + + /// The type of the config item's value. + /// + public interface IConfigKeyBidirectionalBinding : IConfigKeyComponent>, IConfigKeyBidirectionalBinding + { + /// + public new IDefiningConfigKey Owner { get; } + + /// + public new IDefiningConfigKey Target { get; } + } + + /// + /// Defines the interface for config key components, + /// which propagate any changes in value in both directions + /// between the two associated config items. + /// + public interface IConfigKeyBidirectionalBinding + { + /// + /// Gets the config item that this component was initialized to. + /// + public IDefiningConfigKey Owner { get; } + + /// + /// Gets the config item that this binding targets. + /// + public IDefiningConfigKey Target { get; } + } +} \ No newline at end of file diff --git a/MonkeyLoader/Configuration/ConfigKeyBindings.cs b/MonkeyLoader/Configuration/ConfigKeyBindings.cs new file mode 100644 index 0000000..7502f52 --- /dev/null +++ b/MonkeyLoader/Configuration/ConfigKeyBindings.cs @@ -0,0 +1,47 @@ +using MonkeyLoader.Components; +using MonkeyLoader.Meta; + +namespace MonkeyLoader.Configuration +{ + /// + /// Contains constants and extensions for . + /// + public static class ConfigKeyBindings + { + /// + /// The base event label used when a config item's value is set from a + /// 's changed value being propagated. + /// + /// + /// The actual event label will have the format: + /// BidirectionalBindingOwner:TriggerFullId:TriggerLabel. + /// + public const string SetFromBidirectionalOwnerEventLabel = "BidirectionalBindingOwner"; + + /// + /// The base event label used when a config item's value is set from a + /// 's changed value being propagated. + /// + /// + /// The actual event label will have the format: + /// BidirectionalBindingTarget:TriggerFullId:TriggerLabel. + /// + public const string SetFromBidirectionalTargetEventLabel = "BidirectionalBindingTarget"; + + /// + /// Creates a new bidirectional binding that propagates changes between the two config items. + /// + /// The type of the config item's value. + /// The config item that the component will be initialized to. + /// The config item to propagate changes to and from. + /// The newly created bidirectional binding component. + public static IConfigKeyBidirectionalBinding BindBidirectionallyTo(this IDefiningConfigKey owner, IDefiningConfigKey target) + { + var binding = new ConfigKeyBidirectionalBinding(target); + + owner.Add(binding); + + return binding; + } + } +} \ No newline at end of file diff --git a/MonkeyLoader/Configuration/ConfigSystemExtensions.cs b/MonkeyLoader/Configuration/ConfigSystemExtensions.cs new file mode 100644 index 0000000..6fa9b13 --- /dev/null +++ b/MonkeyLoader/Configuration/ConfigSystemExtensions.cs @@ -0,0 +1,25 @@ +using MonkeyLoader.Meta; +using System; +using System.Collections.Generic; +using System.Text; + +namespace MonkeyLoader.Configuration +{ + /// + /// Contains extensions methods related to the config system. + /// + public static class ConfigSystemExtensions + { + /// + /// Gets the Label for + /// a propagated Changed event.
+ /// The created event label will have this format: + /// $"{}:{.Key.FullId}:{.Label}". + ///
+ /// The changed event that triggered this propagation. + /// The new base label for the event. + /// The formatted label for the propagated event. + public static string GetPropagatedEventLabel(this IConfigKeyChangedEventArgs changedEventArgs, string baseLabel) + => $"{baseLabel}:{changedEventArgs.Key.FullId}:{changedEventArgs.Label}"; + } +} \ No newline at end of file diff --git a/MonkeyLoader/Configuration/DefiningConfigKey.cs b/MonkeyLoader/Configuration/DefiningConfigKey.cs index 00d05bf..50f1ac2 100644 --- a/MonkeyLoader/Configuration/DefiningConfigKey.cs +++ b/MonkeyLoader/Configuration/DefiningConfigKey.cs @@ -233,6 +233,9 @@ void IDefiningConfigKey.SetValue(object? value, string? eventLabel) throw new ArgumentException($"Tried to set key [{Id}] to invalid value!", nameof(value)); } + /// + public override string ToString() => $"{Id} ({(_value is null ? "null" : _value.ToString())})"; + /// bool IDefiningConfigKey.TryComputeDefault(out object? defaultValue) {