// 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 osu.Framework.Bindables; using osu.Game.Beatmaps; namespace osu.Game.Rulesets.Mods { public class DifficultyBindable : Bindable { /// /// Whether the extended limits should be applied to this bindable. /// public readonly BindableBool ExtendedLimits = new BindableBool(); /// /// An internal numeric bindable to hold and propagate min/max/precision. /// The value of this bindable should not be set. /// internal readonly BindableFloat CurrentNumber = new BindableFloat { MinValue = 0, MaxValue = 10, }; /// /// A function that can extract the current value of this setting from a beatmap difficulty for display purposes. /// public Func ReadCurrentFromDifficulty; public float Precision { set => CurrentNumber.Precision = value; } public float MinValue { set => CurrentNumber.MinValue = value; } private float maxValue; public float MaxValue { set { if (value == maxValue) return; maxValue = value; updateMaxValue(); } } private float? extendedMaxValue; /// /// The maximum value to be used when extended limits are applied. /// public float? ExtendedMaxValue { set { if (value == extendedMaxValue) return; extendedMaxValue = value; updateMaxValue(); } } public DifficultyBindable() : this(null) { } public DifficultyBindable(float? defaultValue = null) : base(defaultValue) { ExtendedLimits.BindValueChanged(_ => updateMaxValue()); } public override float? Value { get => base.Value; set { // Ensure that in the case serialisation runs in the wrong order (and limit extensions aren't applied yet) the deserialised value is still propagated. if (value != null) CurrentNumber.MaxValue = MathF.Max(CurrentNumber.MaxValue, value.Value); base.Value = value; } } private void updateMaxValue() { CurrentNumber.MaxValue = ExtendedLimits.Value && extendedMaxValue != null ? extendedMaxValue.Value : maxValue; } public override void BindTo(Bindable them) { if (!(them is DifficultyBindable otherDifficultyBindable)) throw new InvalidOperationException($"Cannot bind to a non-{nameof(DifficultyBindable)}."); ReadCurrentFromDifficulty = otherDifficultyBindable.ReadCurrentFromDifficulty; // the following max value copies are only safe as long as these values are effectively constants. MaxValue = otherDifficultyBindable.maxValue; ExtendedMaxValue = otherDifficultyBindable.extendedMaxValue; ExtendedLimits.BindTarget = otherDifficultyBindable.ExtendedLimits; // the actual values need to be copied after the max value constraints. CurrentNumber.BindTarget = otherDifficultyBindable.CurrentNumber; base.BindTo(them); } public override void UnbindFrom(IUnbindable them) { if (!(them is DifficultyBindable otherDifficultyBindable)) throw new InvalidOperationException($"Cannot unbind from a non-{nameof(DifficultyBindable)}."); base.UnbindFrom(them); CurrentNumber.UnbindFrom(otherDifficultyBindable.CurrentNumber); ExtendedLimits.UnbindFrom(otherDifficultyBindable.ExtendedLimits); } public new DifficultyBindable GetBoundCopy() => new DifficultyBindable { BindTarget = this }; } }