-
-
Notifications
You must be signed in to change notification settings - Fork 2.3k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #28382 from Hecatia-Lapislazuli/move-already-place…
…d-objects-when-adjusting-offset-bpm Implemented ability to adjust already-placed objects when changing timing offsets
- Loading branch information
Showing
8 changed files
with
289 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,161 @@ | ||
// Copyright (c) ppy Pty Ltd <[email protected]>. Licensed under the MIT Licence. | ||
// See the LICENCE file in the repository root for full licence text. | ||
|
||
using System.Collections.Generic; | ||
using System.Linq; | ||
using NUnit.Framework; | ||
using osu.Game.Beatmaps; | ||
using osu.Game.Beatmaps.ControlPoints; | ||
using osu.Game.Rulesets.Objects; | ||
using osu.Game.Rulesets.Osu.Objects; | ||
using osu.Game.Screens.Edit.Timing; | ||
|
||
namespace osu.Game.Tests.Editing | ||
{ | ||
[TestFixture] | ||
public class TimingSectionAdjustmentsTest | ||
{ | ||
[Test] | ||
public void TestOffsetAdjustment() | ||
{ | ||
var controlPoints = new ControlPointInfo(); | ||
|
||
controlPoints.Add(100, new TimingControlPoint { BeatLength = 100 }); | ||
controlPoints.Add(50_000, new TimingControlPoint { BeatLength = 200 }); | ||
controlPoints.Add(100_000, new TimingControlPoint { BeatLength = 50 }); | ||
|
||
var beatmap = new Beatmap | ||
{ | ||
ControlPointInfo = controlPoints, | ||
HitObjects = new List<HitObject> | ||
{ | ||
new HitCircle { StartTime = 0 }, | ||
new HitCircle { StartTime = 200 }, | ||
new HitCircle { StartTime = 49_900 }, | ||
new HitCircle { StartTime = 50_000 }, | ||
new HitCircle { StartTime = 50_200 }, | ||
new HitCircle { StartTime = 99_800 }, | ||
new HitCircle { StartTime = 100_000 }, | ||
new HitCircle { StartTime = 100_050 }, | ||
new HitCircle { StartTime = 100_550 }, | ||
} | ||
}; | ||
|
||
moveTimingPoint(beatmap, 100, -50); | ||
|
||
Assert.Multiple(() => | ||
{ | ||
Assert.That(beatmap.HitObjects[0].StartTime, Is.EqualTo(-50)); | ||
Assert.That(beatmap.HitObjects[1].StartTime, Is.EqualTo(150)); | ||
Assert.That(beatmap.HitObjects[2].StartTime, Is.EqualTo(49_850)); | ||
Assert.That(beatmap.HitObjects[3].StartTime, Is.EqualTo(50_000)); | ||
}); | ||
|
||
moveTimingPoint(beatmap, 50_000, 1_000); | ||
|
||
Assert.Multiple(() => | ||
{ | ||
Assert.That(beatmap.HitObjects[2].StartTime, Is.EqualTo(49_850)); | ||
Assert.That(beatmap.HitObjects[3].StartTime, Is.EqualTo(51_000)); | ||
Assert.That(beatmap.HitObjects[4].StartTime, Is.EqualTo(51_200)); | ||
Assert.That(beatmap.HitObjects[5].StartTime, Is.EqualTo(100_800)); | ||
Assert.That(beatmap.HitObjects[6].StartTime, Is.EqualTo(100_000)); | ||
}); | ||
|
||
moveTimingPoint(beatmap, 100_000, 10_000); | ||
|
||
Assert.Multiple(() => | ||
{ | ||
Assert.That(beatmap.HitObjects[4].StartTime, Is.EqualTo(51_200)); | ||
Assert.That(beatmap.HitObjects[5].StartTime, Is.EqualTo(110_800)); | ||
Assert.That(beatmap.HitObjects[6].StartTime, Is.EqualTo(110_000)); | ||
Assert.That(beatmap.HitObjects[7].StartTime, Is.EqualTo(110_050)); | ||
Assert.That(beatmap.HitObjects[8].StartTime, Is.EqualTo(110_550)); | ||
}); | ||
} | ||
|
||
[Test] | ||
public void TestBPMAdjustment() | ||
{ | ||
var controlPoints = new ControlPointInfo(); | ||
|
||
controlPoints.Add(100, new TimingControlPoint { BeatLength = 100 }); | ||
controlPoints.Add(50_000, new TimingControlPoint { BeatLength = 200 }); | ||
controlPoints.Add(100_000, new TimingControlPoint { BeatLength = 50 }); | ||
|
||
var beatmap = new Beatmap | ||
{ | ||
ControlPointInfo = controlPoints, | ||
HitObjects = new List<HitObject> | ||
{ | ||
new HitCircle { StartTime = 0 }, | ||
new HitCircle { StartTime = 200 }, | ||
new Spinner { StartTime = 500, EndTime = 1000 }, | ||
new HitCircle { StartTime = 49_900 }, | ||
new HitCircle { StartTime = 50_000 }, | ||
new HitCircle { StartTime = 50_200 }, | ||
new HitCircle { StartTime = 99_800 }, | ||
new HitCircle { StartTime = 100_000 }, | ||
new HitCircle { StartTime = 100_050 }, | ||
new HitCircle { StartTime = 100_550 }, | ||
} | ||
}; | ||
|
||
adjustBeatLength(beatmap, 100, 50); | ||
|
||
Assert.Multiple(() => | ||
{ | ||
Assert.That(beatmap.HitObjects[0].StartTime, Is.EqualTo(50)); | ||
Assert.That(beatmap.HitObjects[1].StartTime, Is.EqualTo(150)); | ||
Assert.That(beatmap.HitObjects[2].StartTime, Is.EqualTo(300)); | ||
Assert.That(beatmap.HitObjects[2].GetEndTime(), Is.EqualTo(550)); | ||
Assert.That(beatmap.HitObjects[3].StartTime, Is.EqualTo(25_000)); | ||
Assert.That(beatmap.HitObjects[4].StartTime, Is.EqualTo(50_000)); | ||
}); | ||
|
||
adjustBeatLength(beatmap, 50_000, 400); | ||
|
||
Assert.Multiple(() => | ||
{ | ||
Assert.That(beatmap.HitObjects[2].StartTime, Is.EqualTo(300)); | ||
Assert.That(beatmap.HitObjects[2].GetEndTime(), Is.EqualTo(550)); | ||
Assert.That(beatmap.HitObjects[3].StartTime, Is.EqualTo(25_000)); | ||
Assert.That(beatmap.HitObjects[4].StartTime, Is.EqualTo(50_000)); | ||
Assert.That(beatmap.HitObjects[5].StartTime, Is.EqualTo(50_400)); | ||
Assert.That(beatmap.HitObjects[6].StartTime, Is.EqualTo(149_600)); | ||
Assert.That(beatmap.HitObjects[7].StartTime, Is.EqualTo(100_000)); | ||
}); | ||
|
||
adjustBeatLength(beatmap, 100_000, 100); | ||
|
||
Assert.Multiple(() => | ||
{ | ||
Assert.That(beatmap.HitObjects[5].StartTime, Is.EqualTo(50_400)); | ||
Assert.That(beatmap.HitObjects[6].StartTime, Is.EqualTo(199_200)); | ||
Assert.That(beatmap.HitObjects[7].StartTime, Is.EqualTo(100_000)); | ||
Assert.That(beatmap.HitObjects[8].StartTime, Is.EqualTo(100_100)); | ||
Assert.That(beatmap.HitObjects[9].StartTime, Is.EqualTo(101_100)); | ||
}); | ||
} | ||
|
||
private static void moveTimingPoint(IBeatmap beatmap, double originalTime, double adjustment) | ||
{ | ||
var controlPoints = beatmap.ControlPointInfo; | ||
var controlPointGroup = controlPoints.GroupAt(originalTime); | ||
var timingPoint = controlPointGroup.ControlPoints.OfType<TimingControlPoint>().Single(); | ||
controlPoints.RemoveGroup(controlPointGroup); | ||
TimingSectionAdjustments.AdjustHitObjectOffset(beatmap, timingPoint, adjustment); | ||
controlPoints.Add(originalTime - adjustment, timingPoint); | ||
} | ||
|
||
private static void adjustBeatLength(IBeatmap beatmap, double groupTime, double newBeatLength) | ||
{ | ||
var controlPoints = beatmap.ControlPointInfo; | ||
var controlPointGroup = controlPoints.GroupAt(groupTime); | ||
var timingPoint = controlPointGroup.ControlPoints.OfType<TimingControlPoint>().Single(); | ||
double oldBeatLength = timingPoint.BeatLength; | ||
timingPoint.BeatLength = newBeatLength; | ||
TimingSectionAdjustments.SetHitObjectBPM(beatmap, timingPoint, oldBeatLength); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,11 +1,14 @@ | ||
// Copyright (c) ppy Pty Ltd <[email protected]>. 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.Configuration; | ||
using osu.Game.Graphics.UserInterfaceV2; | ||
using osu.Game.Localisation; | ||
|
||
namespace osu.Game.Screens.Edit.Timing | ||
{ | ||
|
@@ -15,11 +18,20 @@ internal partial class TimingSection : Section<TimingControlPoint> | |
private LabelledSwitchButton omitBarLine = null!; | ||
private BPMTextBox bpmTextEntry = null!; | ||
|
||
[Resolved] | ||
private OsuConfigManager configManager { get; set; } = null!; | ||
|
||
[BackgroundDependencyLoader] | ||
private void load() | ||
{ | ||
Flow.AddRange(new Drawable[] | ||
{ | ||
new LabelledSwitchButton | ||
{ | ||
Label = EditorStrings.AdjustExistingObjectsOnTimingChanges, | ||
FixedLabelWidth = 220, | ||
Current = configManager.GetBindable<bool>(OsuSetting.EditorAdjustExistingObjectsOnTimingChanges), | ||
}, | ||
new TapTimingControl(), | ||
bpmTextEntry = new BPMTextBox(), | ||
timeSignature = new LabelledTimeSignature | ||
|
@@ -42,6 +54,17 @@ void saveChanges() | |
{ | ||
if (!isRebinding) ChangeHandler?.SaveState(); | ||
} | ||
|
||
bpmTextEntry.OnCommit = (oldBeatLength, _) => | ||
{ | ||
if (!configManager.Get<bool>(OsuSetting.EditorAdjustExistingObjectsOnTimingChanges) || ControlPoint.Value == null) | ||
return; | ||
|
||
Beatmap.BeginChange(); | ||
TimingSectionAdjustments.SetHitObjectBPM(Beatmap, ControlPoint.Value, oldBeatLength); | ||
Beatmap.UpdateAllHitObjects(); | ||
Beatmap.EndChange(); | ||
}; | ||
} | ||
|
||
private bool isRebinding; | ||
|
@@ -74,17 +97,21 @@ protected override TimingControlPoint CreatePoint() | |
|
||
private partial class BPMTextBox : LabelledTextBox | ||
{ | ||
public new Action<double, double>? OnCommit { get; set; } | ||
|
||
private readonly BindableNumber<double> beatLengthBindable = new TimingControlPoint().BeatLengthBindable; | ||
|
||
public BPMTextBox() | ||
{ | ||
Label = "BPM"; | ||
SelectAllOnFocus = true; | ||
|
||
OnCommit += (_, isNew) => | ||
base.OnCommit += (_, isNew) => | ||
{ | ||
if (!isNew) return; | ||
|
||
double oldBeatLength = beatLengthBindable.Value; | ||
|
||
try | ||
{ | ||
if (double.TryParse(Current.Value, out double doubleVal) && doubleVal > 0) | ||
|
@@ -98,6 +125,7 @@ public BPMTextBox() | |
// This is run regardless of parsing success as the parsed number may not actually trigger a change | ||
// due to bindable clamping. Even in such a case we want to update the textbox to a sane visual state. | ||
beatLengthBindable.TriggerChange(); | ||
OnCommit?.Invoke(oldBeatLength, beatLengthBindable.Value); | ||
}; | ||
|
||
beatLengthBindable.BindValueChanged(val => | ||
|
Oops, something went wrong.