Share code with HoldToConfirm implementations elsewhere

This commit is contained in:
Dean Herbert 2018-05-22 01:44:06 +09:00
parent f9c162dee9
commit ebda287e81
6 changed files with 135 additions and 112 deletions

@ -1 +1 @@
Subproject commit fac688633b8fcf34ae5d0514c26b03e217161eb4 Subproject commit b4d5c766f5698540a7b1bbbae7290ac7dafc2813

View File

@ -3,49 +3,55 @@
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD;
using OpenTK;
using OpenTK.Input;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
{ {
[Description("'Hold to Quit' UI element")] [Description("'Hold to Quit' UI element")]
public class TestCaseQuitButton : OsuTestCase public class TestCaseQuitButton : ManualInputManagerTestCase
{ {
private readonly QuitButton quitButton;
private Drawable innerButton => quitButton.Children.Single(child => child is CircularContainer);
private bool exitAction; private bool exitAction;
public TestCaseQuitButton() [BackgroundDependencyLoader]
private void load()
{ {
QuitButton quitButton;
Add(quitButton = new QuitButton Add(quitButton = new QuitButton
{ {
Origin = Anchor.BottomRight, Origin = Anchor.BottomRight,
Anchor = Anchor.BottomRight, Anchor = Anchor.BottomRight,
}); Action = () => exitAction = true
quitButton.ExitAction = () => exitAction = true;
var text = quitButton.Children.OfType<SpriteText>().Single();
AddStep("Trigger text fade in/out", () =>
{
exitAction = false;
innerButton.TriggerOnMouseDown();
innerButton.TriggerOnMouseUp();
}); });
var text = quitButton.Children.OfType<SpriteText>().First();
// initial display
AddUntilStep(() => text.IsPresent && !exitAction, "Text visible"); AddUntilStep(() => text.IsPresent && !exitAction, "Text visible");
AddUntilStep(() => !text.IsPresent && !exitAction, "Text is not visible"); AddUntilStep(() => !text.IsPresent && !exitAction, "Text is not visible");
AddStep("Trigger text fade in", () => InputManager.MoveMouseTo(quitButton));
AddUntilStep(() => text.IsPresent && !exitAction, "Text visible");
AddStep("Trigger text fade out", () => InputManager.MoveMouseTo(Vector2.One));
AddUntilStep(() => !text.IsPresent && !exitAction, "Text is not visible");
AddStep("Trigger exit action", () => AddStep("Trigger exit action", () =>
{ {
exitAction = false; exitAction = false;
innerButton.TriggerOnMouseDown(); InputManager.MoveMouseTo(quitButton);
InputManager.ButtonDown(MouseButton.Left);
}); });
AddUntilStep(() => exitAction, $"{nameof(quitButton.ExitAction)} was triggered"); AddStep("Early release", () => InputManager.ButtonUp(MouseButton.Left));
AddAssert("action not triggered", () => !exitAction);
AddStep("Trigger exit action", () => InputManager.ButtonDown(MouseButton.Left));
AddUntilStep(() => exitAction, $"{nameof(quitButton.Action)} was triggered");
} }
} }
} }

View File

@ -0,0 +1,52 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
namespace osu.Game.Graphics.Containers
{
public abstract class HoldToCofirmContainer : Container
{
public Action Action;
private const int activate_delay = 400;
private const int fadeout_delay = 200;
private bool fired;
private bool confirming;
/// <summary>
/// Whether the overlay should be allowed to return from a fired state.
/// </summary>
protected virtual bool AllowMultipleFires => false;
public Bindable<double> Progress = new BindableDouble();
protected void BeginConfirm()
{
if (confirming || !AllowMultipleFires && fired) return;
confirming = true;
this.TransformBindableTo(Progress, 1, activate_delay * (1 - Progress.Value), Easing.Out).OnComplete(_ => Confirm());
}
protected virtual void Confirm()
{
Action?.Invoke();
fired = true;
}
protected void AbortConfirm()
{
if (!AllowMultipleFires && fired) return;
confirming = false;
this.TransformBindableTo(Progress, 0, fadeout_delay, Easing.Out);
}
}
}

View File

@ -1,11 +1,10 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.Containers;
using OpenTK.Graphics; using OpenTK.Graphics;
namespace osu.Game.Overlays namespace osu.Game.Overlays
@ -14,22 +13,10 @@ namespace osu.Game.Overlays
/// An overlay which will display a black screen that dims over a period before confirming an exit action. /// An overlay which will display a black screen that dims over a period before confirming an exit action.
/// Action is BYO (derived class will need to call <see cref="BeginConfirm"/> and <see cref="AbortConfirm"/> from a user event). /// Action is BYO (derived class will need to call <see cref="BeginConfirm"/> and <see cref="AbortConfirm"/> from a user event).
/// </summary> /// </summary>
public abstract class HoldToConfirmOverlay : Container public abstract class HoldToConfirmOverlay : HoldToCofirmContainer
{ {
public Action Action;
private Box overlay; private Box overlay;
private const int activate_delay = 400;
private const int fadeout_delay = 200;
private bool fired;
/// <summary>
/// Whether the overlay should be allowed to return from a fired state.
/// </summary>
protected virtual bool AllowMultipleFires => false;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
@ -45,22 +32,8 @@ namespace osu.Game.Overlays
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
} }
}; };
}
protected void BeginConfirm() Progress.ValueChanged += v => overlay.Alpha = (float)v;
{
if (!AllowMultipleFires && fired) return;
overlay.FadeIn(activate_delay * (1 - overlay.Alpha), Easing.Out).OnComplete(_ =>
{
Action?.Invoke();
fired = true;
});
}
protected void AbortConfirm()
{
if (!AllowMultipleFires && fired) return;
overlay.FadeOut(fadeout_delay, Easing.Out);
} }
} }
} }

View File

@ -10,6 +10,7 @@ using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using OpenTK; using OpenTK;
@ -21,10 +22,9 @@ namespace osu.Game.Screens.Play.HUD
private readonly Button button; private readonly Button button;
public Action ExitAction public Action Action
{ {
get => button.ExitAction; set => button.Action = value;
set => button.ExitAction = value;
} }
private readonly OsuSpriteText text; private readonly OsuSpriteText text;
@ -73,88 +73,80 @@ namespace osu.Game.Screens.Play.HUD
float adjust = Vector2.Distance(GetContainingInputManager().CurrentState.Mouse.NativeState.Position, button.ScreenSpaceDrawQuad.Centre) / 200; float adjust = Vector2.Distance(GetContainingInputManager().CurrentState.Mouse.NativeState.Position, button.ScreenSpaceDrawQuad.Centre) / 200;
double elapsed = MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000); double elapsed = MathHelper.Clamp(Clock.ElapsedFrameTime, 0, 1000);
bool stayVisible = text.Alpha > 0 || button.Progress > 0 || IsHovered; bool stayVisible = text.Alpha > 0 || button.Progress.Value > 0 || IsHovered;
Alpha = stayVisible ? 1 : Interpolation.ValueAt(elapsed, Alpha, MathHelper.Clamp(1 - adjust, 0.04f, 1), 0, 200, Easing.OutQuint); Alpha = stayVisible ? 1 : Interpolation.ValueAt(elapsed, Alpha, MathHelper.Clamp(1 - adjust, 0.04f, 1), 0, 200, Easing.OutQuint);
} }
private class Button : CircularContainer private class Button : HoldToCofirmContainer
{ {
private SpriteIcon icon; private SpriteIcon icon;
private CircularProgress progress; private CircularProgress progress;
private Circle innerCircle; private Circle innerCircle;
private bool triggered;
public Action ExitAction { get; set; }
public double Progress => progress.Current.Value;
private const int fade_duration = 200;
private const int progress_duration = 600;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
Masking = true;
Size = new Vector2(60); Size = new Vector2(60);
AddRange(new Drawable[]
Child = new CircularContainer
{ {
new Box Masking = true,
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{ {
RelativeSizeAxes = Axes.Both, new Box
Colour = colours.Gray1, {
Alpha = 0.5f, RelativeSizeAxes = Axes.Both,
}, Colour = colours.Gray1,
progress = new CircularProgress Alpha = 0.5f,
{ },
RelativeSizeAxes = Axes.Both, progress = new CircularProgress
InnerRadius = 1 {
}, RelativeSizeAxes = Axes.Both,
innerCircle = new Circle InnerRadius = 1
{ },
Anchor = Anchor.Centre, innerCircle = new Circle
Origin = Anchor.Centre, {
RelativeSizeAxes = Axes.Both, Anchor = Anchor.Centre,
Colour = colours.Gray1, Origin = Anchor.Centre,
Size = new Vector2(0.9f), RelativeSizeAxes = Axes.Both,
}, Colour = colours.Gray1,
icon = new SpriteIcon Size = new Vector2(0.9f),
{ },
Shadow = false, icon = new SpriteIcon
Anchor = Anchor.Centre, {
Origin = Anchor.Centre, Shadow = false,
Size = new Vector2(15), Anchor = Anchor.Centre,
Icon = FontAwesome.fa_close Origin = Anchor.Centre,
}, Size = new Vector2(15),
}); Icon = FontAwesome.fa_close
},
}
};
Progress.BindTo(progress.Current);
Progress.ValueChanged += v => icon.Scale = new Vector2(1 + (float)v * 0.4f);
}
protected override void Confirm()
{
base.Confirm();
innerCircle.ScaleTo(0, 100).Then().FadeOut().ScaleTo(1).FadeIn(500);
} }
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{ {
if (state.Mouse.Buttons.Count > 1 || triggered) if (state.Mouse.Buttons.Count == 1)
return true; BeginConfirm();
return true;
icon.ScaleTo(1.4f, progress_duration);
progress.FillTo(1, progress_duration, Easing.OutSine).OnComplete(_ =>
{
innerCircle.ScaleTo(0, 100).Then().FadeOut().ScaleTo(1).FadeIn(500);
triggered = true;
ExitAction();
});
return base.OnMouseDown(state, args);
} }
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
{ {
if (state.Mouse.Buttons.Count > 0 || triggered) if (state.Mouse.Buttons.Count == 0)
return true; AbortConfirm();
return true;
icon.ScaleTo(1, 800, Easing.OutElastic);
progress.FillTo(0, progress_duration, Easing.OutQuint).OnComplete(cp => progress.Current.SetDefault());
return base.OnMouseUp(state, args);
} }
} }
} }

View File

@ -219,7 +219,7 @@ namespace osu.Game.Screens.Play
} }
}; };
hudOverlay.HoldToQuit.ExitAction = Exit; hudOverlay.HoldToQuit.Action = Exit;
if (ShowStoryboard) if (ShowStoryboard)
initializeStoryboard(false); initializeStoryboard(false);