Merge pull request #17384 from peppy/hold-to-discard-changes

Add hold-to-confirm flow for discarding editor changes
This commit is contained in:
Dan Balasescu 2022-03-23 07:05:56 +09:00 committed by GitHub
commit d84865ff76
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 105 additions and 17 deletions

View File

@ -13,6 +13,7 @@ using osu.Framework.Testing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Overlays.Dialog;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch; using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Osu;
@ -23,6 +24,7 @@ using osu.Game.Screens.Edit.Setup;
using osu.Game.Storyboards; using osu.Game.Storyboards;
using osu.Game.Tests.Resources; using osu.Game.Tests.Resources;
using osuTK; using osuTK;
using osuTK.Input;
using SharpCompress.Archives; using SharpCompress.Archives;
using SharpCompress.Archives.Zip; using SharpCompress.Archives.Zip;
@ -63,13 +65,19 @@ namespace osu.Game.Tests.Visual.Editing
EditorBeatmap editorBeatmap = null; EditorBeatmap editorBeatmap = null;
AddStep("store editor beatmap", () => editorBeatmap = EditorBeatmap); AddStep("store editor beatmap", () => editorBeatmap = EditorBeatmap);
AddStep("exit without save", () =>
AddStep("exit without save", () => Editor.Exit());
AddStep("hold to confirm", () =>
{ {
Editor.Exit(); var confirmButton = DialogOverlay.CurrentDialog.ChildrenOfType<PopupDialogDangerousButton>().First();
DialogOverlay.CurrentDialog.PerformOkAction();
InputManager.MoveMouseTo(confirmButton);
InputManager.PressButton(MouseButton.Left);
}); });
AddUntilStep("wait for exit", () => !Editor.IsCurrentScreen()); 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); AddAssert("new beatmap not persisted", () => beatmapManager.QueryBeatmapSet(s => s.ID == editorBeatmap.BeatmapInfo.BeatmapSet.ID)?.Value.DeletePending == true);
} }

View File

@ -40,6 +40,10 @@ namespace osu.Game.Tests.Visual.UserInterface
{ {
Text = @"You're a fake!", Text = @"You're a fake!",
}, },
new PopupDialogDangerousButton
{
Text = @"Careful with this one..",
},
}; };
} }
} }

View File

@ -28,6 +28,14 @@ namespace osu.Game.Graphics.Containers
/// </summary> /// </summary>
protected virtual bool AllowMultipleFires => false; protected virtual bool AllowMultipleFires => false;
/// <summary>
/// Specify a custom activation delay, overriding the game-wide user setting.
/// </summary>
/// <remarks>
/// 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.
/// </remarks>
protected virtual double? HoldActivationDelay => null;
public Bindable<double> Progress = new BindableDouble(); public Bindable<double> Progress = new BindableDouble();
private Bindable<double> holdActivationDelay; private Bindable<double> holdActivationDelay;
@ -35,7 +43,9 @@ namespace osu.Game.Graphics.Containers
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuConfigManager config) private void load(OsuConfigManager config)
{ {
holdActivationDelay = config.GetBindable<double>(OsuSetting.UIHoldActivationDelay); holdActivationDelay = HoldActivationDelay != null
? new Bindable<double>(HoldActivationDelay.Value)
: config.GetBindable<double>(OsuSetting.UIHoldActivationDelay);
} }
protected void BeginConfirm() protected void BeginConfirm()

View File

@ -45,8 +45,9 @@ namespace osu.Game.Graphics.UserInterface
} }
} }
protected readonly Container ColourContainer;
private readonly Container backgroundContainer; private readonly Container backgroundContainer;
private readonly Container colourContainer;
private readonly Container glowContainer; private readonly Container glowContainer;
private readonly Box leftGlow; private readonly Box leftGlow;
private readonly Box centerGlow; private readonly Box centerGlow;
@ -113,7 +114,7 @@ namespace osu.Game.Graphics.UserInterface
Masking = true, Masking = true,
Children = new Drawable[] Children = new Drawable[]
{ {
colourContainer = new Container ColourContainer = new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Origin = Anchor.Centre, Origin = Anchor.Centre,
@ -182,7 +183,7 @@ namespace osu.Game.Graphics.UserInterface
{ {
buttonColour = value; buttonColour = value;
updateGlow(); updateGlow();
colourContainer.Colour = value; ColourContainer.Colour = value;
} }
} }
@ -230,11 +231,11 @@ namespace osu.Game.Graphics.UserInterface
Alpha = 0.05f Alpha = 0.05f
}; };
colourContainer.Add(flash); ColourContainer.Add(flash);
flash.FadeOutFromOne(100).Expire(); flash.FadeOutFromOne(100).Expire();
clickAnimating = true; clickAnimating = true;
colourContainer.ResizeWidthTo(colourContainer.Width * 1.05f, 100, Easing.OutQuint) ColourContainer.ResizeWidthTo(ColourContainer.Width * 1.05f, 100, Easing.OutQuint)
.OnComplete(_ => .OnComplete(_ =>
{ {
clickAnimating = false; clickAnimating = false;
@ -246,14 +247,14 @@ namespace osu.Game.Graphics.UserInterface
protected override bool OnMouseDown(MouseDownEvent e) 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); return base.OnMouseDown(e);
} }
protected override void OnMouseUp(MouseUpEvent e) protected override void OnMouseUp(MouseUpEvent e)
{ {
if (State == SelectionState.Selected) if (State == SelectionState.Selected)
colourContainer.ResizeWidthTo(hover_width, click_duration, Easing.In); ColourContainer.ResizeWidthTo(hover_width, click_duration, Easing.In);
base.OnMouseUp(e); base.OnMouseUp(e);
} }
@ -279,12 +280,12 @@ namespace osu.Game.Graphics.UserInterface
if (newState == SelectionState.Selected) if (newState == SelectionState.Selected)
{ {
spriteText.TransformSpacingTo(hoverSpacing, hover_duration, Easing.OutElastic); 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); glowContainer.FadeIn(hover_duration, Easing.OutQuint);
} }
else 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); spriteText.TransformSpacingTo(Vector2.Zero, hover_duration, Easing.OutElastic);
glowContainer.FadeOut(hover_duration, Easing.OutQuint); glowContainer.FadeOut(hover_duration, Easing.OutQuint);
} }

View File

@ -219,7 +219,12 @@ namespace osu.Game.Overlays.Dialog
/// <summary> /// <summary>
/// Programmatically clicks the first <see cref="PopupDialogOkButton"/>. /// Programmatically clicks the first <see cref="PopupDialogOkButton"/>.
/// </summary> /// </summary>
public void PerformOkAction() => Buttons.OfType<PopupDialogOkButton>().First().TriggerClick(); public void PerformOkAction() => PerformAction<PopupDialogOkButton>();
/// <summary>
/// Programmatically clicks the first button of the provided type.
/// </summary>
public void PerformAction<T>() where T : PopupDialogButton => Buttons.OfType<T>().First().TriggerClick();
protected override bool OnKeyDown(KeyDownEvent e) protected override bool OnKeyDown(KeyDownEvent e)
{ {

View File

@ -0,0 +1,59 @@
// 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 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();
}
}
}
}

View File

@ -28,6 +28,7 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Dialog;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit;
@ -598,7 +599,7 @@ namespace osu.Game.Screens.Edit
// if the dialog is already displayed, confirm exit with no save. // if the dialog is already displayed, confirm exit with no save.
if (dialogOverlay.CurrentDialog is PromptForSaveDialog saveDialog) if (dialogOverlay.CurrentDialog is PromptForSaveDialog saveDialog)
{ {
saveDialog.PerformOkAction(); saveDialog.PerformAction<PopupDialogDangerousButton>();
return true; return true;
} }

View File

@ -17,12 +17,12 @@ namespace osu.Game.Screens.Edit
Buttons = new PopupDialogButton[] Buttons = new PopupDialogButton[]
{ {
new PopupDialogCancelButton new PopupDialogOkButton
{ {
Text = @"Save my masterpiece!", Text = @"Save my masterpiece!",
Action = saveAndExit Action = saveAndExit
}, },
new PopupDialogOkButton new PopupDialogDangerousButton
{ {
Text = @"Forget all changes", Text = @"Forget all changes",
Action = exit Action = exit