diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 906644ce14..879363ba08 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -1,30 +1,30 @@ // 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.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.Timing; +using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Overlays.Settings; namespace osu.Game.Screens.Edit.Timing { internal class TimingSection : Section { - private SettingsSlider bpm; + private SettingsSlider bpmSlider; private SettingsEnumDropdown timeSignature; + private BPMTextBox bpmTextEntry; [BackgroundDependencyLoader] private void load() { Flow.AddRange(new Drawable[] { - bpm = new BPMSlider - { - Bindable = new TimingControlPoint().BeatLengthBindable, - LabelText = "BPM", - }, + bpmTextEntry = new BPMTextBox(), + bpmSlider = new BPMSlider(), timeSignature = new SettingsEnumDropdown { LabelText = "Time Signature" @@ -36,7 +36,8 @@ namespace osu.Game.Screens.Edit.Timing { if (point.NewValue != null) { - bpm.Bindable = point.NewValue.BeatLengthBindable; + bpmSlider.Bindable = point.NewValue.BeatLengthBindable; + bpmTextEntry.Bindable = point.NewValue.BeatLengthBindable; timeSignature.Bindable = point.NewValue.TimeSignatureBindable; } } @@ -52,34 +53,88 @@ namespace osu.Game.Screens.Edit.Timing }; } + private class BPMTextBox : LabelledTextBox + { + private readonly BindableNumber beatLengthBindable = new TimingControlPoint().BeatLengthBindable; + + public BPMTextBox() + { + Label = "BPM"; + + OnCommit += (val, isNew) => + { + if (!isNew) return; + + if (double.TryParse(Current.Value, out double doubleVal)) + { + try + { + beatLengthBindable.Value = beatLengthToBpm(doubleVal); + } + catch + { + // will restore the previous text value on failure. + beatLengthBindable.TriggerChange(); + } + } + }; + + beatLengthBindable.BindValueChanged(val => + { + Current.Value = beatLengthToBpm(val.NewValue).ToString("N2"); + }, true); + } + + public Bindable Bindable + { + get => beatLengthBindable; + set + { + // incoming will be beat length, not bpm + beatLengthBindable.UnbindBindings(); + beatLengthBindable.BindTo(value); + } + } + } + private class BPMSlider : SettingsSlider { - private readonly BindableDouble beatLengthBindable = new BindableDouble(); + private const double sane_minimum = 60; + private const double sane_maximum = 240; - private BindableDouble bpmBindable; + private readonly BindableNumber beatLengthBindable = new TimingControlPoint().BeatLengthBindable; + private readonly BindableDouble bpmBindable = new BindableDouble(); + + public BPMSlider() + { + beatLengthBindable.BindValueChanged(beatLength => updateCurrent(beatLengthToBpm(beatLength.NewValue)), true); + bpmBindable.BindValueChanged(bpm => bpmBindable.Default = beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue)); + + base.Bindable = bpmBindable; + } public override Bindable Bindable { get => base.Bindable; set { - // incoming will be beatlength - + // incoming will be beat length, not bpm beatLengthBindable.UnbindBindings(); beatLengthBindable.BindTo(value); - - base.Bindable = bpmBindable = new BindableDouble(beatLengthToBpm(beatLengthBindable.Value)) - { - MinValue = beatLengthToBpm(beatLengthBindable.MaxValue), - MaxValue = beatLengthToBpm(beatLengthBindable.MinValue), - Default = beatLengthToBpm(beatLengthBindable.Default), - }; - - bpmBindable.BindValueChanged(bpm => beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue)); } } - private double beatLengthToBpm(double beatLength) => 60000 / beatLength; + private void updateCurrent(double newValue) + { + // we use a more sane range for the slider display unless overridden by the user. + // if a value comes in outside our range, we should expand temporarily. + bpmBindable.MinValue = Math.Min(newValue, sane_minimum); + bpmBindable.MaxValue = Math.Max(newValue, sane_maximum); + + bpmBindable.Value = newValue; + } } + + private static double beatLengthToBpm(double beatLength) => 60000 / beatLength; } }