diff --git a/.config/dotnet-tools.json b/.config/dotnet-tools.json index 985fc09df3..4177c402aa 100644 --- a/.config/dotnet-tools.json +++ b/.config/dotnet-tools.json @@ -27,7 +27,7 @@ ] }, "ppy.localisationanalyser.tools": { - "version": "2021.1210.0", + "version": "2022.320.0", "commands": [ "localisation" ] diff --git a/.vscode/extensions.json b/.vscode/extensions.json new file mode 100644 index 0000000000..5b7a98f4ba --- /dev/null +++ b/.vscode/extensions.json @@ -0,0 +1,5 @@ +{ + "recommendations": [ + "ms-dotnettools.csharp" + ] +} diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs index ecd4035edd..b109234fec 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorBeatmapCreation.cs @@ -13,6 +13,7 @@ using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Database; +using osu.Game.Overlays.Dialog; using osu.Game.Rulesets; using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Osu; @@ -23,6 +24,7 @@ using osu.Game.Screens.Edit.Setup; using osu.Game.Storyboards; using osu.Game.Tests.Resources; using osuTK; +using osuTK.Input; using SharpCompress.Archives; using SharpCompress.Archives.Zip; @@ -63,13 +65,19 @@ namespace osu.Game.Tests.Visual.Editing EditorBeatmap editorBeatmap = null; AddStep("store editor beatmap", () => editorBeatmap = EditorBeatmap); - AddStep("exit without save", () => + + AddStep("exit without save", () => Editor.Exit()); + AddStep("hold to confirm", () => { - Editor.Exit(); - DialogOverlay.CurrentDialog.PerformOkAction(); + var confirmButton = DialogOverlay.CurrentDialog.ChildrenOfType().First(); + + InputManager.MoveMouseTo(confirmButton); + InputManager.PressButton(MouseButton.Left); }); AddUntilStep("wait for exit", () => !Editor.IsCurrentScreen()); + AddStep("release", () => InputManager.ReleaseButton(MouseButton.Left)); + AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == editorBeatmap.BeatmapInfo.BeatmapSet.ID)?.Value.DeletePending == true); } diff --git a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs index d1c1558003..e75c7f25a3 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneEditorSaving.cs @@ -17,6 +17,13 @@ namespace osu.Game.Tests.Visual.Editing { public class TestSceneEditorSaving : EditorSavingTestScene { + [Test] + public void TestCantExitWithoutSaving() + { + AddRepeatStep("Exit", () => InputManager.Key(Key.Escape), 10); + AddAssert("Editor is still active screen", () => Game.ScreenStack.CurrentScreen is Editor); + } + [Test] public void TestMetadata() { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs index e74345aae9..38d83058c0 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSkinEditor.cs @@ -1,14 +1,19 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Testing; using osu.Game.Overlays; +using osu.Game.Overlays.Settings; using osu.Game.Rulesets; using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Play.HUD.HitErrorMeters; using osu.Game.Skinning.Editor; +using osuTK.Input; namespace osu.Game.Tests.Visual.Gameplay { @@ -29,7 +34,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddStep("reload skin editor", () => { skinEditor?.Expire(); - Player.ScaleTo(0.8f); + Player.ScaleTo(0.4f); LoadComponentAsync(skinEditor = new SkinEditor(Player), Add); }); } @@ -40,6 +45,36 @@ namespace osu.Game.Tests.Visual.Gameplay AddToggleStep("toggle editor visibility", visible => skinEditor.ToggleVisibility()); } + [Test] + public void TestEditComponent() + { + BarHitErrorMeter hitErrorMeter = null; + + AddStep("select bar hit error blueprint", () => + { + var blueprint = skinEditor.ChildrenOfType().First(b => b.Item is BarHitErrorMeter); + + hitErrorMeter = (BarHitErrorMeter)blueprint.Item; + skinEditor.SelectedComponents.Clear(); + skinEditor.SelectedComponents.Add(blueprint.Item); + }); + + AddAssert("value is default", () => hitErrorMeter.JudgementLineThickness.IsDefault); + + AddStep("hover first slider", () => + { + InputManager.MoveMouseTo( + skinEditor.ChildrenOfType().First() + .ChildrenOfType>().First() + .ChildrenOfType>().First() + ); + }); + + AddStep("adjust slider via keyboard", () => InputManager.Key(Key.Left)); + + AddAssert("value is less than default", () => hitErrorMeter.JudgementLineThickness.Value < hitErrorMeter.JudgementLineThickness.Default); + } + protected override Ruleset CreatePlayerRuleset() => new OsuRuleset(); } } diff --git a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs index f2e6aa1e16..394976eb43 100644 --- a/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs +++ b/osu.Game.Tests/Visual/Navigation/TestSceneScreenNavigation.cs @@ -7,6 +7,7 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Extensions; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -14,6 +15,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online.Leaderboards; using osu.Game.Overlays; using osu.Game.Overlays.Mods; +using osu.Game.Overlays.Settings; using osu.Game.Overlays.Toolbar; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; @@ -21,10 +23,12 @@ using osu.Game.Scoring; using osu.Game.Screens.Menu; using osu.Game.Screens.OnlinePlay.Lounge; using osu.Game.Screens.Play; +using osu.Game.Screens.Play.HUD.HitErrorMeters; using osu.Game.Screens.Ranking; using osu.Game.Screens.Select; using osu.Game.Screens.Select.Leaderboards; using osu.Game.Screens.Select.Options; +using osu.Game.Skinning.Editor; using osu.Game.Tests.Beatmaps.IO; using osuTK; using osuTK.Input; @@ -66,6 +70,73 @@ namespace osu.Game.Tests.Visual.Navigation AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible); } + [Test] + public void TestEditComponentDuringGameplay() + { + Screens.Select.SongSelect songSelect = null; + PushAndConfirm(() => songSelect = new TestPlaySongSelect()); + AddUntilStep("wait for song select", () => songSelect.BeatmapSetsLoaded); + + AddStep("import beatmap", () => BeatmapImportHelper.LoadQuickOszIntoOsu(Game).WaitSafely()); + + AddUntilStep("wait for selected", () => !Game.Beatmap.IsDefault); + + SkinEditor skinEditor = null; + + AddStep("open skin editor", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.PressKey(Key.ShiftLeft); + InputManager.Key(Key.S); + InputManager.ReleaseKey(Key.ControlLeft); + InputManager.ReleaseKey(Key.ShiftLeft); + }); + + AddUntilStep("get skin editor", () => (skinEditor = Game.ChildrenOfType().FirstOrDefault()) != null); + + AddStep("Click gameplay scene button", () => + { + skinEditor.ChildrenOfType().First(b => b.Text == "Gameplay").TriggerClick(); + }); + + AddUntilStep("wait for player", () => + { + // dismiss any notifications that may appear (ie. muted notification). + clickMouseInCentre(); + return Game.ScreenStack.CurrentScreen is Player; + }); + + BarHitErrorMeter hitErrorMeter = null; + + AddUntilStep("select bar hit error blueprint", () => + { + var blueprint = skinEditor.ChildrenOfType().FirstOrDefault(b => b.Item is BarHitErrorMeter); + + if (blueprint == null) + return false; + + hitErrorMeter = (BarHitErrorMeter)blueprint.Item; + skinEditor.SelectedComponents.Clear(); + skinEditor.SelectedComponents.Add(blueprint.Item); + return true; + }); + + AddAssert("value is default", () => hitErrorMeter.JudgementLineThickness.IsDefault); + + AddStep("hover first slider", () => + { + InputManager.MoveMouseTo( + skinEditor.ChildrenOfType().First() + .ChildrenOfType>().First() + .ChildrenOfType>().First() + ); + }); + + AddStep("adjust slider via keyboard", () => InputManager.Key(Key.Left)); + + AddAssert("value is less than default", () => hitErrorMeter.JudgementLineThickness.Value < hitErrorMeter.JudgementLineThickness.Default); + } + [Test] public void TestRetryCountIncrements() { @@ -120,7 +191,8 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("press back button", () => Game.ChildrenOfType().First().Action()); - AddStep("show local scores", () => Game.ChildrenOfType().First().Current.Value = new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Local)); + AddStep("show local scores", + () => Game.ChildrenOfType().First().Current.Value = new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Local)); AddUntilStep("wait for score displayed", () => (scorePanel = Game.ChildrenOfType().FirstOrDefault(s => s.Score.Equals(score))) != null); @@ -152,7 +224,8 @@ namespace osu.Game.Tests.Visual.Navigation AddStep("press back button", () => Game.ChildrenOfType().First().Action()); - AddStep("show local scores", () => Game.ChildrenOfType().First().Current.Value = new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Local)); + AddStep("show local scores", + () => Game.ChildrenOfType().First().Current.Value = new BeatmapDetailAreaLeaderboardTabItem(BeatmapLeaderboardScope.Local)); AddUntilStep("wait for score displayed", () => (scorePanel = Game.ChildrenOfType().FirstOrDefault(s => s.Score.Equals(score))) != null); @@ -262,6 +335,20 @@ namespace osu.Game.Tests.Visual.Navigation exitViaBackButtonAndConfirm(); } + [Test] + public void TestModsResetOnEnteringMultiplayer() + { + var osuAutomationMod = new OsuModAutoplay(); + + AddStep("Enable autoplay", () => { Game.SelectedMods.Value = new[] { osuAutomationMod }; }); + + PushAndConfirm(() => new Screens.OnlinePlay.Multiplayer.Multiplayer()); + AddUntilStep("Mods are removed", () => Game.SelectedMods.Value.Count == 0); + + AddStep("Return to menu", () => Game.ScreenStack.CurrentScreen.Exit()); + AddUntilStep("Mods are restored", () => Game.SelectedMods.Value.Contains(osuAutomationMod)); + } + [Test] public void TestExitMultiWithEscape() { diff --git a/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs index 8e53c7c402..6bd6115e68 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestScenePopupDialog.cs @@ -40,6 +40,10 @@ namespace osu.Game.Tests.Visual.UserInterface { Text = @"You're a fake!", }, + new PopupDialogDangerousButton + { + Text = @"Careful with this one..", + }, }; } } diff --git a/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs b/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs index 999dd183aa..b2f08eee0a 100644 --- a/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs +++ b/osu.Game/Graphics/Containers/HoldToConfirmContainer.cs @@ -28,6 +28,14 @@ namespace osu.Game.Graphics.Containers /// protected virtual bool AllowMultipleFires => false; + /// + /// Specify a custom activation delay, overriding the game-wide user setting. + /// + /// + /// This should be used in special cases where we want to be extra sure the user knows what they are doing. An example is when changes would be lost. + /// + protected virtual double? HoldActivationDelay => null; + public Bindable Progress = new BindableDouble(); private Bindable holdActivationDelay; @@ -35,7 +43,9 @@ namespace osu.Game.Graphics.Containers [BackgroundDependencyLoader] private void load(OsuConfigManager config) { - holdActivationDelay = config.GetBindable(OsuSetting.UIHoldActivationDelay); + holdActivationDelay = HoldActivationDelay != null + ? new Bindable(HoldActivationDelay.Value) + : config.GetBindable(OsuSetting.UIHoldActivationDelay); } protected void BeginConfirm() diff --git a/osu.Game/Graphics/UserInterface/DialogButton.cs b/osu.Game/Graphics/UserInterface/DialogButton.cs index 2f9e4dae51..ad69ec4078 100644 --- a/osu.Game/Graphics/UserInterface/DialogButton.cs +++ b/osu.Game/Graphics/UserInterface/DialogButton.cs @@ -45,8 +45,9 @@ namespace osu.Game.Graphics.UserInterface } } + protected readonly Container ColourContainer; + private readonly Container backgroundContainer; - private readonly Container colourContainer; private readonly Container glowContainer; private readonly Box leftGlow; private readonly Box centerGlow; @@ -113,7 +114,7 @@ namespace osu.Game.Graphics.UserInterface Masking = true, Children = new Drawable[] { - colourContainer = new Container + ColourContainer = new Container { RelativeSizeAxes = Axes.Both, Origin = Anchor.Centre, @@ -182,7 +183,7 @@ namespace osu.Game.Graphics.UserInterface { buttonColour = value; updateGlow(); - colourContainer.Colour = value; + ColourContainer.Colour = value; } } @@ -230,11 +231,11 @@ namespace osu.Game.Graphics.UserInterface Alpha = 0.05f }; - colourContainer.Add(flash); + ColourContainer.Add(flash); flash.FadeOutFromOne(100).Expire(); clickAnimating = true; - colourContainer.ResizeWidthTo(colourContainer.Width * 1.05f, 100, Easing.OutQuint) + ColourContainer.ResizeWidthTo(ColourContainer.Width * 1.05f, 100, Easing.OutQuint) .OnComplete(_ => { clickAnimating = false; @@ -246,14 +247,14 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnMouseDown(MouseDownEvent e) { - colourContainer.ResizeWidthTo(hover_width * 0.98f, click_duration * 4, Easing.OutQuad); + ColourContainer.ResizeWidthTo(hover_width * 0.98f, click_duration * 4, Easing.OutQuad); return base.OnMouseDown(e); } protected override void OnMouseUp(MouseUpEvent e) { if (State == SelectionState.Selected) - colourContainer.ResizeWidthTo(hover_width, click_duration, Easing.In); + ColourContainer.ResizeWidthTo(hover_width, click_duration, Easing.In); base.OnMouseUp(e); } @@ -279,12 +280,12 @@ namespace osu.Game.Graphics.UserInterface if (newState == SelectionState.Selected) { spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutElastic); - colourContainer.ResizeWidthTo(hover_width, hover_duration, Easing.OutElastic); + ColourContainer.ResizeWidthTo(hover_width, hover_duration, Easing.OutElastic); glowContainer.FadeIn(hover_duration, Easing.OutQuint); } else { - colourContainer.ResizeWidthTo(idle_width, hover_duration, Easing.OutElastic); + ColourContainer.ResizeWidthTo(idle_width, hover_duration, Easing.OutElastic); spriteText.TransformSpacingTo(Vector2.Zero, hover_duration, Easing.OutElastic); glowContainer.FadeOut(hover_duration, Easing.OutQuint); } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 25bd3d71de..4cd954a646 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -1046,6 +1046,10 @@ namespace osu.Game switch (e.Action) { + case GlobalAction.ToggleSkinEditor: + skinEditor.ToggleVisibility(); + return true; + case GlobalAction.ResetInputSettings: Host.ResetInputHandlers(); frameworkConfig.GetBindable(FrameworkSetting.ConfineMouseMode).SetDefault(); diff --git a/osu.Game/Overlays/Dialog/PopupDialog.cs b/osu.Game/Overlays/Dialog/PopupDialog.cs index 0f953f92bb..a70a7f26cc 100644 --- a/osu.Game/Overlays/Dialog/PopupDialog.cs +++ b/osu.Game/Overlays/Dialog/PopupDialog.cs @@ -219,7 +219,12 @@ namespace osu.Game.Overlays.Dialog /// /// Programmatically clicks the first . /// - public void PerformOkAction() => Buttons.OfType().First().TriggerClick(); + public void PerformOkAction() => PerformAction(); + + /// + /// Programmatically clicks the first button of the provided type. + /// + public void PerformAction() where T : PopupDialogButton => Buttons.OfType().First().TriggerClick(); protected override bool OnKeyDown(KeyDownEvent e) { diff --git a/osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs b/osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs new file mode 100644 index 0000000000..1911a4fa56 --- /dev/null +++ b/osu.Game/Overlays/Dialog/PopupDialogDangerousButton.cs @@ -0,0 +1,59 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; + +namespace osu.Game.Overlays.Dialog +{ + public class PopupDialogDangerousButton : PopupDialogButton + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + ButtonColour = colours.Red3; + + ColourContainer.Add(new ConfirmFillBox + { + Action = () => Action(), + RelativeSizeAxes = Axes.Both, + Blending = BlendingParameters.Additive, + }); + } + + private class ConfirmFillBox : HoldToConfirmContainer + { + private Box box; + + protected override double? HoldActivationDelay => 500; + + protected override void LoadComplete() + { + base.LoadComplete(); + + Child = box = new Box + { + RelativeSizeAxes = Axes.Both, + }; + + Progress.BindValueChanged(progress => box.Width = (float)progress.NewValue, true); + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + BeginConfirm(); + return true; + } + + protected override void OnMouseUp(MouseUpEvent e) + { + if (!e.HasAnyButtonPressed) + AbortConfirm(); + } + } + } +} diff --git a/osu.Game/Overlays/Settings/Sections/SkinSection.cs b/osu.Game/Overlays/Settings/Sections/SkinSection.cs index 475c4bff8d..a34776ddf0 100644 --- a/osu.Game/Overlays/Settings/Sections/SkinSection.cs +++ b/osu.Game/Overlays/Settings/Sections/SkinSection.cs @@ -64,7 +64,7 @@ namespace osu.Game.Overlays.Settings.Sections new SettingsButton { Text = SkinSettingsStrings.SkinLayoutEditor, - Action = () => skinEditor?.Toggle(), + Action = () => skinEditor?.ToggleVisibility(), }, new ExportSkinButton(), }; diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index dcb7e3a282..57f7429e06 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -97,7 +97,7 @@ namespace osu.Game.Screens.Edit private bool canSave; - private bool exitConfirmed; + protected bool ExitConfirmed { get; private set; } private string lastSavedHash; @@ -586,7 +586,7 @@ namespace osu.Game.Screens.Edit public override bool OnExiting(IScreen next) { - if (!exitConfirmed) + if (!ExitConfirmed) { // dialog overlay may not be available in visual tests. if (dialogOverlay == null) @@ -595,12 +595,9 @@ namespace osu.Game.Screens.Edit return true; } - // if the dialog is already displayed, confirm exit with no save. - if (dialogOverlay.CurrentDialog is PromptForSaveDialog saveDialog) - { - saveDialog.PerformOkAction(); + // if the dialog is already displayed, block exiting until the user explicitly makes a decision. + if (dialogOverlay.CurrentDialog is PromptForSaveDialog) return true; - } if (isNewBeatmap || HasUnsavedChanges) { @@ -645,7 +642,7 @@ namespace osu.Game.Screens.Edit { Save(); - exitConfirmed = true; + ExitConfirmed = true; this.Exit(); } @@ -668,7 +665,7 @@ namespace osu.Game.Screens.Edit Beatmap.SetDefault(); } - exitConfirmed = true; + ExitConfirmed = true; this.Exit(); } diff --git a/osu.Game/Screens/Edit/PromptForSaveDialog.cs b/osu.Game/Screens/Edit/PromptForSaveDialog.cs index e308a9533d..4f70491ade 100644 --- a/osu.Game/Screens/Edit/PromptForSaveDialog.cs +++ b/osu.Game/Screens/Edit/PromptForSaveDialog.cs @@ -17,12 +17,12 @@ namespace osu.Game.Screens.Edit Buttons = new PopupDialogButton[] { - new PopupDialogCancelButton + new PopupDialogOkButton { Text = @"Save my masterpiece!", Action = saveAndExit }, - new PopupDialogOkButton + new PopupDialogDangerousButton { Text = @"Forget all changes", Action = exit diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs index bf1699dca0..c56d04d5ac 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayScreen.cs @@ -115,6 +115,8 @@ namespace osu.Game.Screens.OnlinePlay this.FadeIn(); waves.Show(); + Mods.SetDefault(); + if (loungeSubScreen.IsCurrentScreen()) loungeSubScreen.OnEntering(last); else diff --git a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs index 9fc233d3e3..497283a820 100644 --- a/osu.Game/Skinning/Editor/SkinEditorOverlay.cs +++ b/osu.Game/Skinning/Editor/SkinEditorOverlay.cs @@ -19,15 +19,15 @@ namespace osu.Game.Skinning.Editor /// A container which handles loading a skin editor on user request for a specified target. /// This also handles the scaling / positioning adjustment of the target. /// - public class SkinEditorOverlay : CompositeDrawable, IKeyBindingHandler + public class SkinEditorOverlay : OverlayContainer, IKeyBindingHandler { private readonly ScalingContainer scalingContainer; + protected override bool BlockNonPositionalInput => true; + [CanBeNull] private SkinEditor skinEditor; - public const float VISIBLE_TARGET_SCALE = 0.8f; - [Resolved(canBeNull: true)] private OsuGame game { get; set; } @@ -49,33 +49,13 @@ namespace osu.Game.Skinning.Editor Hide(); return true; - - case GlobalAction.ToggleSkinEditor: - Toggle(); - return true; } return false; } - public void Toggle() + protected override void PopIn() { - if (skinEditor == null) - Show(); - else - skinEditor.ToggleVisibility(); - } - - public override void Hide() - { - // base call intentionally omitted. - skinEditor?.Hide(); - } - - public override void Show() - { - // base call intentionally omitted as we have custom behaviour. - if (skinEditor != null) { skinEditor.Show(); @@ -83,29 +63,24 @@ namespace osu.Game.Skinning.Editor } var editor = new SkinEditor(); + editor.State.BindValueChanged(visibility => updateComponentVisibility()); skinEditor = editor; - // Schedule ensures that if `Show` is called before this overlay is loaded, - // it will not throw (LoadComponentAsync requires the load target to be in a loaded state). - Schedule(() => + LoadComponentAsync(editor, _ => { if (editor != skinEditor) return; - LoadComponentAsync(editor, _ => - { - if (editor != skinEditor) - return; + AddInternal(editor); - AddInternal(editor); - - SetTarget(lastTargetScreen); - }); + SetTarget(lastTargetScreen); }); } + protected override void PopOut() => skinEditor?.Hide(); + private void updateComponentVisibility() { Debug.Assert(skinEditor != null); diff --git a/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs b/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs index eac13922d7..d126eff075 100644 --- a/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs +++ b/osu.Game/Skinning/Editor/SkinEditorSceneLibrary.cs @@ -104,7 +104,7 @@ namespace osu.Game.Skinning.Editor }; } - private class SceneButton : OsuButton + public class SceneButton : OsuButton { public SceneButton() { diff --git a/osu.Game/Tests/Visual/EditorTestScene.cs b/osu.Game/Tests/Visual/EditorTestScene.cs index 24015590e2..51221cb8fe 100644 --- a/osu.Game/Tests/Visual/EditorTestScene.cs +++ b/osu.Game/Tests/Visual/EditorTestScene.cs @@ -7,10 +7,13 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.IO.Stores; using osu.Framework.Platform; +using osu.Framework.Screens; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Database; using osu.Game.Online.API; +using osu.Game.Overlays; +using osu.Game.Overlays.Dialog; using osu.Game.Rulesets; using osu.Game.Rulesets.Edit; using osu.Game.Screens.Edit; @@ -93,6 +96,10 @@ namespace osu.Game.Tests.Visual protected class TestEditor : Editor { + [Resolved(canBeNull: true)] + [CanBeNull] + private DialogOverlay dialogOverlay { get; set; } + public new void Undo() => base.Undo(); public new void Redo() => base.Redo(); @@ -111,6 +118,18 @@ namespace osu.Game.Tests.Visual public new bool HasUnsavedChanges => base.HasUnsavedChanges; + public override bool OnExiting(IScreen next) + { + // For testing purposes allow the screen to exit without saving on second attempt. + if (!ExitConfirmed && dialogOverlay?.CurrentDialog is PromptForSaveDialog saveDialog) + { + saveDialog.PerformAction(); + return true; + } + + return base.OnExiting(next); + } + public TestEditor(EditorLoader loader = null) : base(loader) { diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 5e194e2aca..1c1deaae8e 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -31,7 +31,7 @@ - + all runtime; build; native; contentfiles; analyzers; buildtransitive