A game framework written with osu! in mind.
at master 203 lines 7.4 kB view raw
1// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. 2// See the LICENCE file in the repository root for full licence text. 3 4using System; 5using System.Collections.Generic; 6 7namespace osu.Framework.Bindables 8{ 9 public abstract class RangeConstrainedBindable<T> : Bindable<T> 10 { 11 public event Action<T> MinValueChanged; 12 13 public event Action<T> MaxValueChanged; 14 15 private T minValue; 16 17 public T MinValue 18 { 19 get => minValue; 20 set 21 { 22 if (EqualityComparer<T>.Default.Equals(value, minValue)) 23 return; 24 25 SetMinValue(value, true, this); 26 } 27 } 28 29 private T maxValue; 30 31 public T MaxValue 32 { 33 get => maxValue; 34 set 35 { 36 if (EqualityComparer<T>.Default.Equals(value, maxValue)) 37 return; 38 39 SetMaxValue(value, true, this); 40 } 41 } 42 43 public override T Value 44 { 45 get => base.Value; 46 set => setValue(value); 47 } 48 49 /// <summary> 50 /// The default <see cref="MinValue"/>. This should be equal to the minimum value of type <typeparamref name="T"/>. 51 /// </summary> 52 protected abstract T DefaultMinValue { get; } 53 54 /// <summary> 55 /// The default <see cref="MaxValue"/>. This should be equal to the maximum value of type <typeparamref name="T"/>. 56 /// </summary> 57 protected abstract T DefaultMaxValue { get; } 58 59 /// <summary> 60 /// Whether this bindable has a user-defined range that is not the full range of the <typeparamref name="T"/> type. 61 /// </summary> 62 public bool HasDefinedRange => !EqualityComparer<T>.Default.Equals(MinValue, DefaultMinValue) || 63 !EqualityComparer<T>.Default.Equals(MaxValue, DefaultMaxValue); 64 65 protected RangeConstrainedBindable(T defaultValue = default) 66 : base(defaultValue) 67 { 68 minValue = DefaultMinValue; 69 maxValue = DefaultMaxValue; 70 71 // Reapply the default value here for respecting the defined default min/max values. 72 setValue(defaultValue); 73 } 74 75 /// <summary> 76 /// Sets the minimum value. This method does no equality comparisons. 77 /// </summary> 78 /// <param name="minValue">The new minimum value.</param> 79 /// <param name="updateCurrentValue">Whether to update the current value after the minimum value is set.</param> 80 /// <param name="source">The bindable that triggered this. A null value represents the current bindable instance.</param> 81 internal void SetMinValue(T minValue, bool updateCurrentValue, RangeConstrainedBindable<T> source) 82 { 83 this.minValue = minValue; 84 TriggerMinValueChange(source); 85 86 if (updateCurrentValue) 87 { 88 // Reapply the current value to respect the new minimum value. 89 setValue(Value); 90 } 91 } 92 93 /// <summary> 94 /// Sets the maximum value. This method does no equality comparisons. 95 /// </summary> 96 /// <param name="maxValue">The new maximum value.</param> 97 /// <param name="updateCurrentValue">Whether to update the current value after the maximum value is set.</param> 98 /// <param name="source">The bindable that triggered this. A null value represents the current bindable instance.</param> 99 internal void SetMaxValue(T maxValue, bool updateCurrentValue, RangeConstrainedBindable<T> source) 100 { 101 this.maxValue = maxValue; 102 TriggerMaxValueChange(source); 103 104 if (updateCurrentValue) 105 { 106 // Reapply the current value to respect the new maximum value. 107 setValue(Value); 108 } 109 } 110 111 public override void TriggerChange() 112 { 113 base.TriggerChange(); 114 115 TriggerMinValueChange(this, false); 116 TriggerMaxValueChange(this, false); 117 } 118 119 protected void TriggerMinValueChange(RangeConstrainedBindable<T> source = null, bool propagateToBindings = true) 120 { 121 // check a bound bindable hasn't changed the value again (it will fire its own event) 122 T beforePropagation = minValue; 123 124 if (propagateToBindings && Bindings != null) 125 { 126 foreach (var b in Bindings) 127 { 128 if (b == source) continue; 129 130 if (b is RangeConstrainedBindable<T> cb) 131 cb.SetMinValue(minValue, false, this); 132 } 133 } 134 135 if (EqualityComparer<T>.Default.Equals(beforePropagation, minValue)) 136 MinValueChanged?.Invoke(minValue); 137 } 138 139 protected void TriggerMaxValueChange(RangeConstrainedBindable<T> source = null, bool propagateToBindings = true) 140 { 141 // check a bound bindable hasn't changed the value again (it will fire its own event) 142 T beforePropagation = maxValue; 143 144 if (propagateToBindings && Bindings != null) 145 { 146 foreach (var b in Bindings) 147 { 148 if (b == source) continue; 149 150 if (b is RangeConstrainedBindable<T> cb) 151 cb.SetMaxValue(maxValue, false, this); 152 } 153 } 154 155 if (EqualityComparer<T>.Default.Equals(beforePropagation, maxValue)) 156 MaxValueChanged?.Invoke(maxValue); 157 } 158 159 public override void BindTo(Bindable<T> them) 160 { 161 if (them is RangeConstrainedBindable<T> other) 162 { 163 if (!IsValidRange(other.MinValue, other.MaxValue)) 164 { 165 throw new ArgumentOutOfRangeException( 166 nameof(them), $"The target bindable has specified an invalid range of [{other.MinValue} - {other.MaxValue}]."); 167 } 168 169 MinValue = other.MinValue; 170 MaxValue = other.MaxValue; 171 } 172 173 base.BindTo(them); 174 } 175 176 public override void UnbindEvents() 177 { 178 base.UnbindEvents(); 179 180 MinValueChanged = null; 181 MaxValueChanged = null; 182 } 183 184 public new RangeConstrainedBindable<T> GetBoundCopy() => (RangeConstrainedBindable<T>)base.GetBoundCopy(); 185 186 public new RangeConstrainedBindable<T> GetUnboundCopy() => (RangeConstrainedBindable<T>)base.GetUnboundCopy(); 187 188 /// <summary> 189 /// Clamps <paramref name="value"/> to the range defined by <paramref name="minValue"/> and <paramref name="maxValue"/>. 190 /// </summary> 191 protected abstract T ClampValue(T value, T minValue, T maxValue); 192 193 /// <summary> 194 /// Whether <paramref name="min"/> and <paramref name="max"/> constitute a valid range 195 /// (usually used to check that <paramref name="min"/> is indeed lesser than or equal to <paramref name="max"/>). 196 /// </summary> 197 /// <param name="min">The range's minimum value.</param> 198 /// <param name="max">The range's maximum value.</param> 199 protected abstract bool IsValidRange(T min, T max); 200 201 private void setValue(T value) => base.Value = ClampValue(value, minValue, maxValue); 202 } 203}