mirror of
https://github.com/ppy/osu.git
synced 2024-11-15 15:17:44 +08:00
Merge pull request #28382 from Hecatia-Lapislazuli/move-already-placed-objects-when-adjusting-offset-bpm
Implemented ability to adjust already-placed objects when changing timing offsets
This commit is contained in:
commit
8605639e67
161
osu.Game.Tests/Editing/TimingSectionAdjustmentsTest.cs
Normal file
161
osu.Game.Tests/Editing/TimingSectionAdjustmentsTest.cs
Normal file
@ -0,0 +1,161 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -196,6 +196,7 @@ namespace osu.Game.Configuration
|
|||||||
SetDefault(OsuSetting.EditorShowSpeedChanges, false);
|
SetDefault(OsuSetting.EditorShowSpeedChanges, false);
|
||||||
SetDefault(OsuSetting.EditorScaleOrigin, EditorOrigin.GridCentre);
|
SetDefault(OsuSetting.EditorScaleOrigin, EditorOrigin.GridCentre);
|
||||||
SetDefault(OsuSetting.EditorRotationOrigin, EditorOrigin.GridCentre);
|
SetDefault(OsuSetting.EditorRotationOrigin, EditorOrigin.GridCentre);
|
||||||
|
SetDefault(OsuSetting.EditorAdjustExistingObjectsOnTimingChanges, true);
|
||||||
|
|
||||||
SetDefault(OsuSetting.HideCountryFlags, false);
|
SetDefault(OsuSetting.HideCountryFlags, false);
|
||||||
|
|
||||||
@ -442,5 +443,6 @@ namespace osu.Game.Configuration
|
|||||||
EditorScaleOrigin,
|
EditorScaleOrigin,
|
||||||
EditorRotationOrigin,
|
EditorRotationOrigin,
|
||||||
EditorTimelineShowBreaks,
|
EditorTimelineShowBreaks,
|
||||||
|
EditorAdjustExistingObjectsOnTimingChanges,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,11 @@ namespace osu.Game.Localisation
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public static LocalisableString SetPreviewPointToCurrent => new TranslatableString(getKey(@"set_preview_point_to_current"), @"Set preview point to current time");
|
public static LocalisableString SetPreviewPointToCurrent => new TranslatableString(getKey(@"set_preview_point_to_current"), @"Set preview point to current time");
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// "Move already placed objects when changing timing"
|
||||||
|
/// </summary>
|
||||||
|
public static LocalisableString AdjustExistingObjectsOnTimingChanges => new TranslatableString(getKey(@"adjust_existing_objects_on_timing_changes"), @"Move already placed objects when changing timing");
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// "For editing (.olz)"
|
/// "For editing (.olz)"
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -421,7 +421,7 @@ namespace osu.Game.Screens.Edit
|
|||||||
{
|
{
|
||||||
Items = new MenuItem[]
|
Items = new MenuItem[]
|
||||||
{
|
{
|
||||||
new EditorMenuItem(EditorStrings.SetPreviewPointToCurrent, MenuItemType.Standard, SetPreviewPointToCurrentTime)
|
new EditorMenuItem(EditorStrings.SetPreviewPointToCurrent, MenuItemType.Standard, SetPreviewPointToCurrentTime),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,6 +7,7 @@ using osu.Framework.Bindables;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
@ -25,6 +26,9 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
protected EditorBeatmap Beatmap { get; private set; } = null!;
|
protected EditorBeatmap Beatmap { get; private set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuConfigManager configManager { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private EditorClock clock { get; set; } = null!;
|
private EditorClock clock { get; set; } = null!;
|
||||||
|
|
||||||
@ -110,7 +114,16 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
Beatmap.ControlPointInfo.RemoveGroup(SelectedGroup.Value);
|
Beatmap.ControlPointInfo.RemoveGroup(SelectedGroup.Value);
|
||||||
|
|
||||||
foreach (var cp in currentGroupItems)
|
foreach (var cp in currentGroupItems)
|
||||||
|
{
|
||||||
|
// Only adjust hit object offsets if the group contains a timing control point
|
||||||
|
if (cp is TimingControlPoint tp && configManager.Get<bool>(OsuSetting.EditorAdjustExistingObjectsOnTimingChanges))
|
||||||
|
{
|
||||||
|
TimingSectionAdjustments.AdjustHitObjectOffset(Beatmap, tp, time - SelectedGroup.Value.Time);
|
||||||
|
Beatmap.UpdateAllHitObjects();
|
||||||
|
}
|
||||||
|
|
||||||
Beatmap.ControlPointInfo.Add(time, cp);
|
Beatmap.ControlPointInfo.Add(time, cp);
|
||||||
|
}
|
||||||
|
|
||||||
// the control point might not necessarily exist yet, if currentGroupItems was empty.
|
// the control point might not necessarily exist yet, if currentGroupItems was empty.
|
||||||
SelectedGroup.Value = Beatmap.ControlPointInfo.GroupAt(time, true);
|
SelectedGroup.Value = Beatmap.ControlPointInfo.GroupAt(time, true);
|
||||||
|
@ -10,6 +10,7 @@ using osu.Framework.Graphics.Shapes;
|
|||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
@ -26,6 +27,9 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
[Resolved]
|
[Resolved]
|
||||||
private EditorBeatmap beatmap { get; set; } = null!;
|
private EditorBeatmap beatmap { get; set; } = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuConfigManager configManager { get; set; } = null!;
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private Bindable<ControlPointGroup> selectedGroup { get; set; } = null!;
|
private Bindable<ControlPointGroup> selectedGroup { get; set; } = null!;
|
||||||
|
|
||||||
@ -202,15 +206,25 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
// VERY TEMPORARY
|
// VERY TEMPORARY
|
||||||
var currentGroupItems = selectedGroup.Value.ControlPoints.ToArray();
|
var currentGroupItems = selectedGroup.Value.ControlPoints.ToArray();
|
||||||
|
|
||||||
|
beatmap.BeginChange();
|
||||||
beatmap.ControlPointInfo.RemoveGroup(selectedGroup.Value);
|
beatmap.ControlPointInfo.RemoveGroup(selectedGroup.Value);
|
||||||
|
|
||||||
double newOffset = selectedGroup.Value.Time + adjust;
|
double newOffset = selectedGroup.Value.Time + adjust;
|
||||||
|
|
||||||
foreach (var cp in currentGroupItems)
|
foreach (var cp in currentGroupItems)
|
||||||
|
{
|
||||||
|
if (cp is TimingControlPoint tp)
|
||||||
|
{
|
||||||
|
TimingSectionAdjustments.AdjustHitObjectOffset(beatmap, tp, adjust);
|
||||||
|
beatmap.UpdateAllHitObjects();
|
||||||
|
}
|
||||||
|
|
||||||
beatmap.ControlPointInfo.Add(newOffset, cp);
|
beatmap.ControlPointInfo.Add(newOffset, cp);
|
||||||
|
}
|
||||||
|
|
||||||
// the control point might not necessarily exist yet, if currentGroupItems was empty.
|
// the control point might not necessarily exist yet, if currentGroupItems was empty.
|
||||||
selectedGroup.Value = beatmap.ControlPointInfo.GroupAt(newOffset, true);
|
selectedGroup.Value = beatmap.ControlPointInfo.GroupAt(newOffset, true);
|
||||||
|
beatmap.EndChange();
|
||||||
|
|
||||||
if (!editorClock.IsRunning && wasAtStart)
|
if (!editorClock.IsRunning && wasAtStart)
|
||||||
editorClock.Seek(newOffset);
|
editorClock.Seek(newOffset);
|
||||||
@ -223,7 +237,16 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
if (timing == null)
|
if (timing == null)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
double oldBeatLength = timing.BeatLength;
|
||||||
timing.BeatLength = 60000 / (timing.BPM + adjust);
|
timing.BeatLength = 60000 / (timing.BPM + adjust);
|
||||||
|
|
||||||
|
if (configManager.Get<bool>(OsuSetting.EditorAdjustExistingObjectsOnTimingChanges))
|
||||||
|
{
|
||||||
|
beatmap.BeginChange();
|
||||||
|
TimingSectionAdjustments.SetHitObjectBPM(beatmap, timing, oldBeatLength);
|
||||||
|
beatmap.UpdateAllHitObjects();
|
||||||
|
beatmap.EndChange();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private partial class InlineButton : OsuButton
|
private partial class InlineButton : OsuButton
|
||||||
|
@ -1,11 +1,14 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
|
using System;
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Configuration;
|
||||||
using osu.Game.Graphics.UserInterfaceV2;
|
using osu.Game.Graphics.UserInterfaceV2;
|
||||||
|
using osu.Game.Localisation;
|
||||||
|
|
||||||
namespace osu.Game.Screens.Edit.Timing
|
namespace osu.Game.Screens.Edit.Timing
|
||||||
{
|
{
|
||||||
@ -15,11 +18,20 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
private LabelledSwitchButton omitBarLine = null!;
|
private LabelledSwitchButton omitBarLine = null!;
|
||||||
private BPMTextBox bpmTextEntry = null!;
|
private BPMTextBox bpmTextEntry = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuConfigManager configManager { get; set; } = null!;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load()
|
private void load()
|
||||||
{
|
{
|
||||||
Flow.AddRange(new Drawable[]
|
Flow.AddRange(new Drawable[]
|
||||||
{
|
{
|
||||||
|
new LabelledSwitchButton
|
||||||
|
{
|
||||||
|
Label = EditorStrings.AdjustExistingObjectsOnTimingChanges,
|
||||||
|
FixedLabelWidth = 220,
|
||||||
|
Current = configManager.GetBindable<bool>(OsuSetting.EditorAdjustExistingObjectsOnTimingChanges),
|
||||||
|
},
|
||||||
new TapTimingControl(),
|
new TapTimingControl(),
|
||||||
bpmTextEntry = new BPMTextBox(),
|
bpmTextEntry = new BPMTextBox(),
|
||||||
timeSignature = new LabelledTimeSignature
|
timeSignature = new LabelledTimeSignature
|
||||||
@ -42,6 +54,17 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
{
|
{
|
||||||
if (!isRebinding) ChangeHandler?.SaveState();
|
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;
|
private bool isRebinding;
|
||||||
@ -74,6 +97,8 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
|
|
||||||
private partial class BPMTextBox : LabelledTextBox
|
private partial class BPMTextBox : LabelledTextBox
|
||||||
{
|
{
|
||||||
|
public new Action<double, double>? OnCommit { get; set; }
|
||||||
|
|
||||||
private readonly BindableNumber<double> beatLengthBindable = new TimingControlPoint().BeatLengthBindable;
|
private readonly BindableNumber<double> beatLengthBindable = new TimingControlPoint().BeatLengthBindable;
|
||||||
|
|
||||||
public BPMTextBox()
|
public BPMTextBox()
|
||||||
@ -81,10 +106,12 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
Label = "BPM";
|
Label = "BPM";
|
||||||
SelectAllOnFocus = true;
|
SelectAllOnFocus = true;
|
||||||
|
|
||||||
OnCommit += (_, isNew) =>
|
base.OnCommit += (_, isNew) =>
|
||||||
{
|
{
|
||||||
if (!isNew) return;
|
if (!isNew) return;
|
||||||
|
|
||||||
|
double oldBeatLength = beatLengthBindable.Value;
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
if (double.TryParse(Current.Value, out double doubleVal) && doubleVal > 0)
|
if (double.TryParse(Current.Value, out double doubleVal) && doubleVal > 0)
|
||||||
@ -98,6 +125,7 @@ namespace osu.Game.Screens.Edit.Timing
|
|||||||
// This is run regardless of parsing success as the parsed number may not actually trigger a change
|
// 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.
|
// due to bindable clamping. Even in such a case we want to update the textbox to a sane visual state.
|
||||||
beatLengthBindable.TriggerChange();
|
beatLengthBindable.TriggerChange();
|
||||||
|
OnCommit?.Invoke(oldBeatLength, beatLengthBindable.Value);
|
||||||
};
|
};
|
||||||
|
|
||||||
beatLengthBindable.BindValueChanged(val =>
|
beatLengthBindable.BindValueChanged(val =>
|
||||||
|
55
osu.Game/Screens/Edit/Timing/TimingSectionAdjustments.cs
Normal file
55
osu.Game/Screens/Edit/Timing/TimingSectionAdjustments.cs
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. 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 osu.Framework.Utils;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
|
using osu.Game.Rulesets.Objects;
|
||||||
|
using osu.Game.Rulesets.Objects.Types;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.Edit.Timing
|
||||||
|
{
|
||||||
|
public static class TimingSectionAdjustments
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Returns all objects from <paramref name="beatmap"/> which are affected by the supplied <paramref name="timingControlPoint"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static List<HitObject> HitObjectsInTimingRange(IBeatmap beatmap, TimingControlPoint timingControlPoint)
|
||||||
|
{
|
||||||
|
// If the first group, we grab all hitobjects prior to the next, if the last group, we grab all remaining hitobjects
|
||||||
|
double startTime = beatmap.ControlPointInfo.TimingPoints.Any(x => x.Time < timingControlPoint.Time) ? timingControlPoint.Time : double.MinValue;
|
||||||
|
double endTime = beatmap.ControlPointInfo.TimingPoints.FirstOrDefault(x => x.Time > timingControlPoint.Time)?.Time ?? double.MaxValue;
|
||||||
|
|
||||||
|
return beatmap.HitObjects.Where(x => Precision.AlmostBigger(x.StartTime, startTime) && Precision.DefinitelyBigger(endTime, x.StartTime)).ToList();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Moves all relevant objects after <paramref name="timingControlPoint"/>'s offset has been changed by <paramref name="adjustment"/>.
|
||||||
|
/// </summary>
|
||||||
|
public static void AdjustHitObjectOffset(IBeatmap beatmap, TimingControlPoint timingControlPoint, double adjustment)
|
||||||
|
{
|
||||||
|
foreach (HitObject hitObject in HitObjectsInTimingRange(beatmap, timingControlPoint))
|
||||||
|
{
|
||||||
|
hitObject.StartTime += adjustment;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Ensures all relevant objects are still snapped to the same beats after <paramref name="timingControlPoint"/>'s beat length / BPM has been changed.
|
||||||
|
/// </summary>
|
||||||
|
public static void SetHitObjectBPM(IBeatmap beatmap, TimingControlPoint timingControlPoint, double oldBeatLength)
|
||||||
|
{
|
||||||
|
foreach (HitObject hitObject in HitObjectsInTimingRange(beatmap, timingControlPoint))
|
||||||
|
{
|
||||||
|
double beat = (hitObject.StartTime - timingControlPoint.Time) / oldBeatLength;
|
||||||
|
|
||||||
|
hitObject.StartTime = (beat * timingControlPoint.BeatLength) + timingControlPoint.Time;
|
||||||
|
|
||||||
|
if (hitObject is not IHasRepeats && hitObject is IHasDuration hitObjectWithDuration)
|
||||||
|
hitObjectWithDuration.Duration *= timingControlPoint.BeatLength / oldBeatLength;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user