1
0
mirror of https://github.com/ppy/osu.git synced 2024-11-11 09:27:29 +08:00

Merge pull request #14737 from bdach/editor-difficulty-switch-shared-state

Preserve current time and clipboard contents when switching between difficulties
This commit is contained in:
Dean Herbert 2021-09-15 12:38:56 +09:00 committed by GitHub
commit 1ba716d9f1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 110 additions and 6 deletions

View File

@ -55,6 +55,59 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("stack empty", () => Stack.CurrentScreen == null);
}
[Test]
public void TestClockPositionPreservedBetweenSwitches()
{
BeatmapInfo targetDifficulty = null;
AddStep("seek editor to 00:05:00", () => EditorClock.Seek(5000));
AddStep("set target difficulty", () => targetDifficulty = importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo)));
switchToDifficulty(() => targetDifficulty);
confirmEditingBeatmap(() => targetDifficulty);
AddAssert("editor clock at 00:05:00", () => EditorClock.CurrentTime == 5000);
AddStep("exit editor", () => Stack.Exit());
// ensure editor loader didn't resume.
AddAssert("stack empty", () => Stack.CurrentScreen == null);
}
[Test]
public void TestClipboardPreservedAfterSwitch([Values] bool sameRuleset)
{
BeatmapInfo targetDifficulty = null;
AddStep("select first object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects.First()));
AddStep("copy object", () => Editor.Copy());
AddStep("set target difficulty", () =>
{
targetDifficulty = sameRuleset
? importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.RulesetID == Beatmap.Value.BeatmapInfo.RulesetID)
: importedBeatmapSet.Beatmaps.Last(beatmap => !beatmap.Equals(Beatmap.Value.BeatmapInfo) && beatmap.RulesetID != Beatmap.Value.BeatmapInfo.RulesetID);
});
switchToDifficulty(() => targetDifficulty);
confirmEditingBeatmap(() => targetDifficulty);
AddAssert("no objects selected", () => !EditorBeatmap.SelectedHitObjects.Any());
AddStep("paste object", () => Editor.Paste());
if (sameRuleset)
AddAssert("object was pasted", () => EditorBeatmap.SelectedHitObjects.Any());
else
AddAssert("object was not pasted", () => !EditorBeatmap.SelectedHitObjects.Any());
AddStep("exit editor", () => Stack.Exit());
if (sameRuleset)
{
AddUntilStep("prompt for save dialog shown", () => DialogOverlay.CurrentDialog is PromptForSaveDialog);
AddStep("discard changes", () => ((PromptForSaveDialog)DialogOverlay.CurrentDialog).PerformOkAction());
}
// ensure editor loader didn't resume.
AddAssert("stack empty", () => Stack.CurrentScreen == null);
}
[Test]
public void TestPreventSwitchDueToUnsavedChanges()
{
@ -118,7 +171,7 @@ namespace osu.Game.Tests.Visual.Editing
private void confirmEditingBeatmap(Func<BeatmapInfo> targetDifficulty)
{
AddUntilStep("current beatmap is correct", () => Beatmap.Value.BeatmapInfo.Equals(targetDifficulty.Invoke()));
AddUntilStep("current screen is editor", () => Stack.CurrentScreen is Editor);
AddUntilStep("current screen is editor", () => Stack.CurrentScreen == Editor && Editor?.IsLoaded == true);
}
}
}

View File

@ -80,7 +80,7 @@ namespace osu.Game.Screens.Edit.Compose
public bool OnPressed(PlatformAction action)
{
if (action == PlatformAction.Copy)
host.GetClipboard().SetText(formatSelectionAsString());
host.GetClipboard()?.SetText(formatSelectionAsString());
return false;
}

View File

@ -317,6 +317,16 @@ namespace osu.Game.Screens.Edit
/// </summary>
public void UpdateClockSource() => clock.ChangeSource(Beatmap.Value.Track);
/// <summary>
/// Restore the editor to a provided state.
/// </summary>
/// <param name="state">The state to restore.</param>
public void RestoreState([NotNull] EditorState state) => Schedule(() =>
{
clock.Seek(state.Time);
clipboard.Value = state.ClipboardContent;
});
protected void Save()
{
// no longer new after first user-triggered save.
@ -740,7 +750,11 @@ namespace osu.Game.Screens.Edit
return new DifficultyMenuItem(beatmapInfo, isCurrentDifficulty, SwitchToDifficulty);
}
protected void SwitchToDifficulty(BeatmapInfo beatmapInfo) => loader?.ScheduleDifficultySwitch(beatmapInfo);
protected void SwitchToDifficulty(BeatmapInfo nextBeatmap) => loader?.ScheduleDifficultySwitch(nextBeatmap, new EditorState
{
Time = clock.CurrentTimeAccurate,
ClipboardContent = editorBeatmap.BeatmapInfo.RulesetID == nextBeatmap.RulesetID ? clipboard.Value : string.Empty
});
private void cancelExit() => loader?.CancelPendingDifficultySwitch();

View File

@ -20,6 +20,13 @@ namespace osu.Game.Screens.Edit
/// </summary>
public class EditorLoader : ScreenWithBeatmapBackground
{
/// <summary>
/// The stored state from the last editor opened.
/// This will be read by the next editor instance to be opened to restore any relevant previous state.
/// </summary>
[CanBeNull]
private EditorState state;
public override float BackgroundParallaxAmount => 0.1f;
public override bool AllowBackButton => false;
@ -61,7 +68,7 @@ namespace osu.Game.Screens.Edit
}
}
public void ScheduleDifficultySwitch(BeatmapInfo beatmapInfo)
public void ScheduleDifficultySwitch(BeatmapInfo nextBeatmap, EditorState editorState)
{
scheduledDifficultySwitch?.Cancel();
ValidForResume = true;
@ -70,7 +77,8 @@ namespace osu.Game.Screens.Edit
scheduledDifficultySwitch = Schedule(() =>
{
Beatmap.Value = beatmapManager.GetWorkingBeatmap(beatmapInfo);
Beatmap.Value = beatmapManager.GetWorkingBeatmap(nextBeatmap);
state = editorState;
// This screen is a weird exception to the rule that nothing after song select changes the global beatmap.
// Because of this, we need to update the background stack's beatmap to match.
@ -83,7 +91,13 @@ namespace osu.Game.Screens.Edit
private void pushEditor()
{
this.Push(CreateEditor());
var editor = CreateEditor();
this.Push(editor);
if (state != null)
editor.RestoreState(state);
ValidForResume = false;
}

View File

@ -0,0 +1,23 @@
// 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.
#nullable enable
namespace osu.Game.Screens.Edit
{
/// <summary>
/// Structure used to convey the general state of an <see cref="Editor"/> instance.
/// </summary>
public class EditorState
{
/// <summary>
/// The current audio time.
/// </summary>
public double Time { get; set; }
/// <summary>
/// The editor clipboard content.
/// </summary>
public string ClipboardContent { get; set; } = string.Empty;
}
}