// 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 JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Extensions.ObjectExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Logging; using osu.Framework.Screens; using osu.Framework.Threading; using osu.Game.Beatmaps; using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; using osu.Game.Screens.Menu; using osu.Game.Screens.Play; namespace osu.Game.Screens.Edit { /// /// Transition screen for the editor. /// Used to avoid backing out to main menu/song select when switching difficulties from within the editor. /// public partial class EditorLoader : ScreenWithBeatmapBackground { /// /// 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. /// [CanBeNull] private EditorState state; public override float BackgroundParallaxAmount => 0.1f; public override bool AllowBackButton => false; public override bool HideOverlaysOnEnter => true; public override bool DisallowExternalBeatmapRulesetChanges => true; public override bool? AllowGlobalTrackControl => false; [Resolved] private BeatmapManager beatmapManager { get; set; } [CanBeNull] private ScheduledDelegate scheduledDifficultySwitch; [BackgroundDependencyLoader] private void load() { AddRangeInternal(new Drawable[] { new LoadingSpinner(true) { State = { Value = Visibility.Visible }, } }); } protected override void LoadComplete() { base.LoadComplete(); // will be restored via lease, see `DisallowExternalBeatmapRulesetChanges`. if (!(Beatmap.Value is DummyWorkingBeatmap)) Ruleset.Value = Beatmap.Value.BeatmapInfo.Ruleset; Mods.Value = Array.Empty(); } protected virtual Editor CreateEditor() => new Editor(this); protected override void LogoArriving(OsuLogo logo, bool resuming) { base.LogoArriving(logo, resuming); if (!resuming) { // the push cannot happen in OnEntering() or similar (even if scheduled), because the transition from main menu will look bad. // that is because this screen pushing the editor makes it no longer current, and OsuScreen checks if the screen is current // before enqueueing this screen's LogoArriving onto the logo animation sequence. pushEditor(); } } public void ScheduleSwitchToNewDifficulty(BeatmapInfo referenceBeatmapInfo, RulesetInfo rulesetInfo, bool createCopy, EditorState editorState) => scheduleDifficultySwitch(() => { try { // fetch a fresh detached reference from database to avoid polluting model instances attached to cached working beatmaps. var targetBeatmapSet = beatmapManager.QueryBeatmap(b => b.ID == referenceBeatmapInfo.ID).AsNonNull().BeatmapSet.AsNonNull(); var referenceWorkingBeatmap = beatmapManager.GetWorkingBeatmap(referenceBeatmapInfo); return createCopy ? beatmapManager.CopyExistingDifficulty(targetBeatmapSet, referenceWorkingBeatmap) : beatmapManager.CreateNewDifficulty(targetBeatmapSet, referenceWorkingBeatmap, rulesetInfo); } catch (Exception ex) { // if the beatmap creation fails (e.g. due to duplicated difficulty names), // bring the user back to the previous beatmap as a best-effort. Logger.Error(ex, ex.Message); return Beatmap.Value; } }, editorState); public void ScheduleSwitchToExistingDifficulty(BeatmapInfo beatmapInfo, EditorState editorState) => scheduleDifficultySwitch(() => beatmapManager.GetWorkingBeatmap(beatmapInfo), editorState); private void scheduleDifficultySwitch(Func nextBeatmap, EditorState editorState) { scheduledDifficultySwitch?.Cancel(); ValidForResume = true; this.MakeCurrent(); scheduledDifficultySwitch = Schedule(() => { var workingBeatmap = nextBeatmap.Invoke(); Ruleset.Value = workingBeatmap.BeatmapInfo.Ruleset; Beatmap.Value = workingBeatmap; 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. // If we don't do this, the editor will see a discrepancy and create a new background, along with an unnecessary transition. ApplyToBackground(b => b.Beatmap = Beatmap.Value); pushEditor(); }); } private void pushEditor() { var editor = CreateEditor(); this.Push(editor); if (state != null) editor.RestoreState(state); ValidForResume = false; } public void CancelPendingDifficultySwitch() { scheduledDifficultySwitch?.Cancel(); ValidForResume = false; } } }