// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Collections.Generic; namespace osu.Framework.Bindables { public abstract class RangeConstrainedBindable : Bindable { public event Action MinValueChanged; public event Action MaxValueChanged; private T minValue; public T MinValue { get => minValue; set { if (EqualityComparer.Default.Equals(value, minValue)) return; SetMinValue(value, true, this); } } private T maxValue; public T MaxValue { get => maxValue; set { if (EqualityComparer.Default.Equals(value, maxValue)) return; SetMaxValue(value, true, this); } } public override T Value { get => base.Value; set => setValue(value); } /// /// The default . This should be equal to the minimum value of type . /// protected abstract T DefaultMinValue { get; } /// /// The default . This should be equal to the maximum value of type . /// protected abstract T DefaultMaxValue { get; } /// /// Whether this bindable has a user-defined range that is not the full range of the type. /// public bool HasDefinedRange => !EqualityComparer.Default.Equals(MinValue, DefaultMinValue) || !EqualityComparer.Default.Equals(MaxValue, DefaultMaxValue); protected RangeConstrainedBindable(T defaultValue = default) : base(defaultValue) { minValue = DefaultMinValue; maxValue = DefaultMaxValue; // Reapply the default value here for respecting the defined default min/max values. setValue(defaultValue); } /// /// Sets the minimum value. This method does no equality comparisons. /// /// The new minimum value. /// Whether to update the current value after the minimum value is set. /// The bindable that triggered this. A null value represents the current bindable instance. internal void SetMinValue(T minValue, bool updateCurrentValue, RangeConstrainedBindable source) { this.minValue = minValue; TriggerMinValueChange(source); if (updateCurrentValue) { // Reapply the current value to respect the new minimum value. setValue(Value); } } /// /// Sets the maximum value. This method does no equality comparisons. /// /// The new maximum value. /// Whether to update the current value after the maximum value is set. /// The bindable that triggered this. A null value represents the current bindable instance. internal void SetMaxValue(T maxValue, bool updateCurrentValue, RangeConstrainedBindable source) { this.maxValue = maxValue; TriggerMaxValueChange(source); if (updateCurrentValue) { // Reapply the current value to respect the new maximum value. setValue(Value); } } public override void TriggerChange() { base.TriggerChange(); TriggerMinValueChange(this, false); TriggerMaxValueChange(this, false); } protected void TriggerMinValueChange(RangeConstrainedBindable source = null, bool propagateToBindings = true) { // check a bound bindable hasn't changed the value again (it will fire its own event) T beforePropagation = minValue; if (propagateToBindings && Bindings != null) { foreach (var b in Bindings) { if (b == source) continue; if (b is RangeConstrainedBindable cb) cb.SetMinValue(minValue, false, this); } } if (EqualityComparer.Default.Equals(beforePropagation, minValue)) MinValueChanged?.Invoke(minValue); } protected void TriggerMaxValueChange(RangeConstrainedBindable source = null, bool propagateToBindings = true) { // check a bound bindable hasn't changed the value again (it will fire its own event) T beforePropagation = maxValue; if (propagateToBindings && Bindings != null) { foreach (var b in Bindings) { if (b == source) continue; if (b is RangeConstrainedBindable cb) cb.SetMaxValue(maxValue, false, this); } } if (EqualityComparer.Default.Equals(beforePropagation, maxValue)) MaxValueChanged?.Invoke(maxValue); } public override void BindTo(Bindable them) { if (them is RangeConstrainedBindable other) { if (!IsValidRange(other.MinValue, other.MaxValue)) { throw new ArgumentOutOfRangeException( nameof(them), $"The target bindable has specified an invalid range of [{other.MinValue} - {other.MaxValue}]."); } MinValue = other.MinValue; MaxValue = other.MaxValue; } base.BindTo(them); } public override void UnbindEvents() { base.UnbindEvents(); MinValueChanged = null; MaxValueChanged = null; } public new RangeConstrainedBindable GetBoundCopy() => (RangeConstrainedBindable)base.GetBoundCopy(); public new RangeConstrainedBindable GetUnboundCopy() => (RangeConstrainedBindable)base.GetUnboundCopy(); /// /// Clamps to the range defined by and . /// protected abstract T ClampValue(T value, T minValue, T maxValue); /// /// Whether and constitute a valid range /// (usually used to check that is indeed lesser than or equal to ). /// /// The range's minimum value. /// The range's maximum value. protected abstract bool IsValidRange(T min, T max); private void setValue(T value) => base.Value = ClampValue(value, minValue, maxValue); } }