// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. #nullable disable using System; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; namespace osu.Game.Rulesets.Mods { public partial class DifficultyAdjustSettingsControl : SettingsItem { [Resolved] private IBindable beatmap { get; set; } /// /// Used to track the display value on the setting slider. /// /// /// When the mod is overriding a default, this will match the value of . /// When there is no override (ie. is null), this value will match the beatmap provided default via . /// private readonly BindableNumber sliderDisplayCurrent = new BindableNumber(); protected sealed override Drawable CreateControl() => new SliderControl(sliderDisplayCurrent, CreateSlider); protected virtual RoundedSliderBar CreateSlider(BindableNumber current) => new RoundedSliderBar { RelativeSizeAxes = Axes.X, Current = current, KeyboardStep = 0.1f, }; /// /// Guards against beatmap values displayed on slider bars being transferred to user override. /// private bool isInternalChange; private DifficultyBindable difficultyBindable; public override Bindable Current { get => base.Current; set { // Intercept and extract the internal number bindable from DifficultyBindable. // This will provide bounds and precision specifications for the slider bar. difficultyBindable = (DifficultyBindable)value.GetBoundCopy(); sliderDisplayCurrent.BindTo(difficultyBindable.CurrentNumber); base.Current = difficultyBindable; } } protected override void LoadComplete() { base.LoadComplete(); Current.BindValueChanged(_ => updateCurrentFromSlider()); beatmap.BindValueChanged(_ => updateCurrentFromSlider(), true); sliderDisplayCurrent.BindValueChanged(number => { // this handles the transfer of the slider value to the main bindable. // as such, should be skipped if the slider is being updated via updateFromDifficulty(). if (!isInternalChange) Current.Value = number.NewValue; }); } private void updateCurrentFromSlider() { if (Current.Value != null) { // a user override has been added or updated. sliderDisplayCurrent.Value = Current.Value.Value; return; } var difficulty = beatmap.Value.BeatmapInfo.Difficulty; // generally should always be implemented, else the slider will have a zero default. if (difficultyBindable.ReadCurrentFromDifficulty == null) return; isInternalChange = true; sliderDisplayCurrent.Value = difficultyBindable.ReadCurrentFromDifficulty(difficulty); isInternalChange = false; } private partial class SliderControl : CompositeDrawable, IHasCurrentValue { // This is required as SettingsItem relies heavily on this bindable for internal use. // The actual update flow is done via the bindable provided in the constructor. private readonly DifficultyBindableWithCurrent current = new DifficultyBindableWithCurrent(); public Bindable Current { get => current.Current; set => current.Current = value; } public SliderControl(BindableNumber currentNumber, Func, RoundedSliderBar> createSlider) { InternalChildren = new Drawable[] { createSlider(currentNumber) }; AutoSizeAxes = Axes.Y; RelativeSizeAxes = Axes.X; } } private class DifficultyBindableWithCurrent : DifficultyBindable, IHasCurrentValue { private Bindable currentBound; public Bindable Current { get => this; set { ArgumentNullException.ThrowIfNull(value); if (currentBound != null) UnbindFrom(currentBound); BindTo(currentBound = value); } } public DifficultyBindableWithCurrent(float? defaultValue = default) : base(defaultValue) { } protected override Bindable CreateInstance() => new DifficultyBindableWithCurrent(); } } }