Better tests, add documentation

This commit is contained in:
David Zhao
2019-03-26 17:18:35 +09:00
parent be9ac39f54
commit 3fe52be77f
5 changed files with 121 additions and 94 deletions

View File

@ -7,16 +7,13 @@ using osu.Framework.Graphics;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Screens; using osu.Game.Screens;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osuTK;
namespace osu.Game.Tests.Visual.Gameplay namespace osu.Game.Tests.Visual.Gameplay
{ {
public class TestCasePlayerLoader : ManualInputManagerTestCase public class TestCasePlayerLoader : ManualInputManagerTestCase
{ {
private PlayerLoader loader; private PlayerLoader loader;
private readonly OsuScreenStack stack; private readonly OsuScreenStack stack;
public TestCasePlayerLoader() public TestCasePlayerLoader()
@ -29,8 +26,6 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
Beatmap.Value = new DummyWorkingBeatmap(game); Beatmap.Value = new DummyWorkingBeatmap(game);
AddStep("Reset logo position", () => logo = new OsuLogo { Position = new Vector2(0, 0) });
AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => new Player AddStep("load dummy beatmap", () => stack.Push(loader = new PlayerLoader(() => new Player
{ {
AllowPause = false, AllowPause = false,
@ -58,6 +53,8 @@ namespace osu.Game.Tests.Visual.Gameplay
AllowLeadIn = false, AllowLeadIn = false,
AllowResults = false, AllowResults = false,
})); }));
Scheduler.AddDelayed(() => slow.Ready = true, 5000);
}); });
AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen()); AddUntilStep("wait for no longer current", () => !loader.IsCurrentScreen());

View File

@ -11,7 +11,7 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Testing; using osu.Framework.Graphics.UserInterface;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
@ -31,7 +31,11 @@ namespace osu.Game.Tests.Visual
typeof(PlayerLoader), typeof(PlayerLoader),
typeof(Player), typeof(Player),
typeof(Facade), typeof(Facade),
typeof(FacadeContainer) typeof(FacadeContainer),
typeof(ButtonSystem),
typeof(ButtonSystemState),
typeof(Menu),
typeof(MainMenu)
}; };
[Cached] [Cached]
@ -68,15 +72,10 @@ namespace osu.Game.Tests.Visual
AllowPause = false, AllowPause = false,
AllowLeadIn = false, AllowLeadIn = false,
AllowResults = false, AllowResults = false,
Ready = false
}))); })));
} }
[Test]
public void MainMenuTest()
{
AddStep("Add new Main Menu", () => LoadScreen(new MainMenu()));
}
private class TestFacadeContainer : FacadeContainer private class TestFacadeContainer : FacadeContainer
{ {
protected override Facade CreateFacade() => new Facade protected override Facade CreateFacade() => new Facade
@ -119,16 +118,23 @@ namespace osu.Game.Tests.Visual
base.LogoArriving(logo, resuming); base.LogoArriving(logo, resuming);
logo.FadeIn(350); logo.FadeIn(350);
logo.ScaleTo(new Vector2(0.15f), 350, Easing.In); logo.ScaleTo(new Vector2(0.15f), 350, Easing.In);
facadeContainer.SetLogo(logo); facadeContainer.SetLogo(logo, 0.3f, 1000, Easing.InOutQuint);
facadeContainer.Tracking = true;
moveLogoFacade(); moveLogoFacade();
} }
protected override void LogoExiting(OsuLogo logo)
{
base.LogoExiting(logo);
facadeContainer.Tracking = false;
}
private void moveLogoFacade() private void moveLogoFacade()
{ {
Random random = new Random(); Random random = new Random();
if (facadeFlowComponent.Transforms.Count == 0) if (facadeFlowComponent.Transforms.Count == 0)
{ {
facadeFlowComponent.Delay(500).MoveTo(new Vector2(random.Next(0, 800), random.Next(0, 600)), 300); facadeFlowComponent.Delay(500).MoveTo(new Vector2(random.Next(0, (int)DrawWidth), random.Next(0, (int)DrawHeight)), 300);
} }
if (randomPositions) if (randomPositions)
@ -159,11 +165,13 @@ namespace osu.Game.Tests.Visual
private class TestPlayer : Player private class TestPlayer : Player
{ {
public bool Ready;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
// Never finish loading // Never finish loading
while (true) while (!Ready)
Thread.Sleep(1); Thread.Sleep(1);
} }
} }

View File

@ -10,80 +10,98 @@ using osuTK;
namespace osu.Game.Graphics.Containers namespace osu.Game.Graphics.Containers
{ {
/// <summary>
/// A container that creates a <see cref="Facade"/> to be used by its children.
/// This container also updates the position and size of the Facade, and contains logic for tracking an <see cref="OsuLogo"/> on the Facade's position.
/// </summary>
public class FacadeContainer : Container public class FacadeContainer : Container
{ {
protected virtual Facade CreateFacade() => new Facade();
public Facade Facade => facade;
/// <summary>
/// Whether or not the logo assigned to this FacadeContainer should be tracking the position its facade.
/// </summary>
public bool Tracking;
[Cached] [Cached]
private Facade facade; private Facade facade;
private OsuLogo logo; private OsuLogo logo;
private float facadeScale;
private bool tracking; private Vector2 startPosition;
private Easing easing;
protected virtual Facade CreateFacade() => new Facade(); private double startTime;
private double duration;
public FacadeContainer() public FacadeContainer()
{ {
facade = CreateFacade(); facade = CreateFacade();
} }
private Vector2 logoTrackingPosition => logo.Parent.ToLocalSpace(facade.ScreenSpaceDrawQuad.Centre); /// <summary>
/// Set the logo that should track the Facade's position, as well as how it should transform to its initial position.
public void SetLogo(OsuLogo logo, double transformDelay = 0) /// </summary>
/// <param name="logo"> The instance of the logo to be used for tracking. </param>
/// <param name="facadeScale"> The scale of the facade. </param>
/// <param name="duration"> The duration of the initial transform. Default is instant.</param>
/// <param name="easing"> The easing type of the initial transform. </param>
public void SetLogo(OsuLogo logo, float facadeScale, double duration = 0, Easing easing = Easing.None)
{ {
if (logo != null) if (logo != null)
{ {
facade.Size = new Vector2(logo.SizeForFlow * 0.3f);
this.logo = logo; this.logo = logo;
Scheduler.AddDelayed(() =>
{
tracking = true;
}, transformDelay);
} }
this.facadeScale = facadeScale;
this.duration = duration;
this.easing = easing;
} }
private double startTime; private Vector2 logoTrackingPosition => logo.Parent.ToLocalSpace(facade.ScreenSpaceDrawQuad.Centre);
private double duration = 1000;
private Vector2 startPosition;
private Easing easing = Easing.InOutExpo;
protected override void UpdateAfterChildren() protected override void UpdateAfterChildren()
{ {
base.UpdateAfterChildren(); base.UpdateAfterChildren();
if (logo == null || !tracking) if (logo == null || !Tracking)
return; return;
facade.Size = new Vector2(logo.SizeForFlow * 0.3f); facade.Size = new Vector2(logo.SizeForFlow * facadeScale);
if (facade.IsLoaded && logo.Position != logoTrackingPosition) if (facade.IsLoaded && logo.Position != logoTrackingPosition)
{ {
if (logo.RelativePositionAxes != Axes.None) // Required for the correct position of the logo to be set with respect to logoTrackingPosition
{ logo.RelativePositionAxes = Axes.None;
logo.RelativePositionAxes = Axes.None;
logo.Position = logo.Parent.ToLocalSpace(logo.Position);
}
// If this is our first update since tracking has started, initialize our starting values for interpolation
if (startTime == 0) if (startTime == 0)
{ {
startTime = Time.Current; startTime = Time.Current;
startPosition = logo.Position;
} }
var endTime = startTime + duration; var endTime = startTime + duration;
var remainingDuration = endTime - Time.Current; var remainingDuration = endTime - Time.Current;
if (remainingDuration <= 0) // If our transform should be instant, our position should already be at logoTrackingPosition, thus set the blend to 0.
{ // If we are already past when the transform should be finished playing, set the blend to 0 so that the logo is always at the position of the facade.
remainingDuration = 0; var blend = duration > 0 && remainingDuration > 0
} ? (float)Interpolation.ApplyEasing(easing, remainingDuration / duration)
: 0;
float currentTime = (float)Interpolation.ApplyEasing(easing, remainingDuration / duration); // Interpolate the position of the logo, where blend 0 is the position of the Facade, and blend 1 is where the logo was when it first began interpolating.
logo.Position = Vector2.Lerp(logoTrackingPosition, startPosition, currentTime); logo.Position = Vector2.Lerp(logoTrackingPosition, startPosition, blend);
} }
} }
} }
} }
/// <summary>
/// A placeholder container that serves as a dummy object to denote another object's location and size.
/// </summary>
public class Facade : Container public class Facade : Container
{ {
} }

View File

@ -9,6 +9,7 @@ using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
@ -16,6 +17,7 @@ using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Input; using osu.Game.Input;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osu.Game.Online.API; using osu.Game.Online.API;
@ -53,15 +55,19 @@ namespace osu.Game.Screens.Menu
if (this.logo != null) if (this.logo != null)
{ {
this.logo.Action = onOsuLogo; this.logo.Action = onOsuLogo;
facadeContainer.SetLogo(logo, 0.5f);
// osuLogo.SizeForFlow relies on loading to be complete. // osuLogo.SizeForFlow relies on loading to be complete.
buttonArea.Flow.Position = new Vector2(WEDGE_WIDTH * 2 - (BUTTON_WIDTH + this.logo.SizeForFlow / 4), 0); buttonArea.Flow.Position = new Vector2(WEDGE_WIDTH * 2 - (BUTTON_WIDTH + this.logo.SizeForFlow / 4), 0);
updateLogoState(); updateLogoState();
} }
else
{
facadeContainer.Tracking = false;
}
} }
private readonly Drawable iconFacade;
private readonly ButtonArea buttonArea; private readonly ButtonArea buttonArea;
private readonly Button backButton; private readonly Button backButton;
@ -71,26 +77,29 @@ namespace osu.Game.Screens.Menu
private SampleChannel sampleBack; private SampleChannel sampleBack;
private readonly FacadeContainer facadeContainer;
public ButtonSystem() public ButtonSystem()
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
Child = buttonArea = new ButtonArea(); Child = facadeContainer = new FacadeContainer
{
RelativeSizeAxes = Axes.Both,
Child = buttonArea = new ButtonArea()
};
buttonArea.AddRange(new[] buttonArea.AddRange(new Container[]
{ {
new Button(@"settings", string.Empty, FontAwesome.fa_gear, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O), new Button(@"settings", string.Empty, FontAwesome.fa_gear, new Color4(85, 85, 85, 255), () => OnSettings?.Invoke(), -WEDGE_WIDTH, Key.O),
backButton = new Button(@"back", @"button-back-select", FontAwesome.fa_osu_left_o, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH) backButton = new Button(@"back", @"button-back-select", FontAwesome.fa_osu_left_o, new Color4(51, 58, 94, 255), () => State = ButtonSystemState.TopLevel, -WEDGE_WIDTH)
{ {
VisibleState = ButtonSystemState.Play, VisibleState = ButtonSystemState.Play,
}, },
iconFacade = new Container //need a container to make the osu! icon flow properly. facadeContainer.Facade
{
Size = new Vector2(0, ButtonArea.BUTTON_AREA_HEIGHT)
}
}); });
buttonArea.Flow.CentreTarget = iconFacade; buttonArea.Flow.CentreTarget = facadeContainer.Facade;
} }
[Resolved(CanBeNull = true)] [Resolved(CanBeNull = true)]
@ -120,6 +129,15 @@ namespace osu.Game.Screens.Menu
buttonArea.AddRange(buttonsPlay); buttonArea.AddRange(buttonsPlay);
buttonArea.AddRange(buttonsTopLevel); buttonArea.AddRange(buttonsTopLevel);
buttonArea.ForEach(b =>
{
if (b is Button)
{
b.Origin = Anchor.CentreLeft;
b.Anchor = Anchor.CentreLeft;
}
});
isIdle.ValueChanged += idle => updateIdleState(idle.NewValue); isIdle.ValueChanged += idle => updateIdleState(idle.NewValue);
if (idleTracker != null) isIdle.BindTo(idleTracker.IsIdle); if (idleTracker != null) isIdle.BindTo(idleTracker.IsIdle);
@ -247,7 +265,7 @@ namespace osu.Game.Screens.Menu
logoDelayedAction?.Cancel(); logoDelayedAction?.Cancel();
logoDelayedAction = Scheduler.AddDelayed(() => logoDelayedAction = Scheduler.AddDelayed(() =>
{ {
logoTracking = false; facadeContainer.Tracking = false;
game?.Toolbar.Hide(); game?.Toolbar.Hide();
@ -266,19 +284,16 @@ namespace osu.Game.Screens.Menu
break; break;
case ButtonSystemState.Initial: case ButtonSystemState.Initial:
logo.ClearTransforms(targetMember: nameof(Position)); logo.ClearTransforms(targetMember: nameof(Position));
logo.RelativePositionAxes = Axes.None;
bool impact = logo.Scale.X > 0.6f; bool impact = logo.Scale.X > 0.6f;
if (lastState == ButtonSystemState.Initial) if (lastState == ButtonSystemState.Initial)
logo.ScaleTo(0.5f, 200, Easing.In); logo.ScaleTo(0.5f, 200, Easing.In);
logo.MoveTo(logoTrackingPosition, lastState == ButtonSystemState.EnteringMode ? 0 : 200, Easing.In);
logoDelayedAction?.Cancel(); logoDelayedAction?.Cancel();
logoDelayedAction = Scheduler.AddDelayed(() => logoDelayedAction = Scheduler.AddDelayed(() =>
{ {
logoTracking = true; facadeContainer.Tracking = true;
if (impact) if (impact)
logo.Impact(); logo.Impact();
@ -288,35 +303,17 @@ namespace osu.Game.Screens.Menu
break; break;
default: default:
logo.ClearTransforms(targetMember: nameof(Position)); logo.ClearTransforms(targetMember: nameof(Position));
logo.RelativePositionAxes = Axes.None; facadeContainer.Tracking = true;
logoTracking = true;
logo.ScaleTo(0.5f, 200, Easing.OutQuint); logo.ScaleTo(0.5f, 200, Easing.OutQuint);
break; break;
} }
break; break;
case ButtonSystemState.EnteringMode: case ButtonSystemState.EnteringMode:
logoTracking = true; facadeContainer.Tracking = true;
break; break;
} }
} }
private Vector2 logoTrackingPosition => logo.Parent.ToLocalSpace(iconFacade.ScreenSpaceDrawQuad.Centre);
private bool logoTracking;
protected override void Update()
{
base.Update();
if (logo != null)
{
if (logoTracking && logo.RelativePositionAxes == Axes.None && iconFacade.IsLoaded)
logo.Position = logoTrackingPosition;
iconFacade.Width = logo.SizeForFlow * 0.5f;
}
}
} }
public enum ButtonSystemState public enum ButtonSystemState

View File

@ -33,7 +33,7 @@ namespace osu.Game.Screens.Play
private Player player; private Player player;
private FacadeContainer content; private FacadeContainer facadeContainer;
protected virtual FacadeContainer CreateFacadeContainer() => new FacadeContainer(); protected virtual FacadeContainer CreateFacadeContainer() => new FacadeContainer();
@ -62,11 +62,11 @@ namespace osu.Game.Screens.Play
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
InternalChild = content = CreateFacadeContainer(); InternalChild = facadeContainer = CreateFacadeContainer();
content.Anchor = Anchor.Centre; facadeContainer.Anchor = Anchor.Centre;
content.Origin = Anchor.Centre; facadeContainer.Origin = Anchor.Centre;
content.RelativeSizeAxes = Axes.Both; facadeContainer.RelativeSizeAxes = Axes.Both;
content.Children = new Drawable[] facadeContainer.Children = new Drawable[]
{ {
info = new BeatmapMetadataDisplay(Beatmap.Value) info = new BeatmapMetadataDisplay(Beatmap.Value)
{ {
@ -122,21 +122,21 @@ namespace osu.Game.Screens.Play
private void contentIn() private void contentIn()
{ {
content.ScaleTo(1, 650, Easing.OutQuint); facadeContainer.ScaleTo(1, 650, Easing.OutQuint);
content.FadeInFromZero(400); facadeContainer.FadeInFromZero(400);
} }
private void contentOut() private void contentOut()
{ {
content.ScaleTo(0.7f, 300, Easing.InQuint); facadeContainer.ScaleTo(0.7f, 300, Easing.InQuint);
content.FadeOut(250); facadeContainer.FadeOut(250);
} }
public override void OnEntering(IScreen last) public override void OnEntering(IScreen last)
{ {
base.OnEntering(last); base.OnEntering(last);
content.ScaleTo(0.7f); facadeContainer.ScaleTo(0.7f);
Background?.FadeColour(Color4.White, 800, Easing.OutQuint); Background?.FadeColour(Color4.White, 800, Easing.OutQuint);
contentIn(); contentIn();
@ -155,7 +155,15 @@ namespace osu.Game.Screens.Play
logo.MoveTo(new Vector2(0.5f), duration, Easing.In); logo.MoveTo(new Vector2(0.5f), duration, Easing.In);
logo.FadeIn(350); logo.FadeIn(350);
content.SetLogo(logo, duration); facadeContainer.SetLogo(logo, 0.3f, 500, Easing.InOutQuint);
Scheduler.AddDelayed(() => facadeContainer.Tracking = true, duration);
}
protected override void LogoExiting(OsuLogo logo)
{
base.LogoExiting(logo);
facadeContainer.Tracking = false;
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -230,7 +238,7 @@ namespace osu.Game.Screens.Play
public override bool OnExiting(IScreen next) public override bool OnExiting(IScreen next)
{ {
content.ScaleTo(0.7f, 150, Easing.InQuint); facadeContainer.ScaleTo(0.7f, 150, Easing.InQuint);
this.FadeOut(150); this.FadeOut(150);
cancelLoad(); cancelLoad();
@ -305,7 +313,6 @@ namespace osu.Game.Screens.Play
private LoadingAnimation loading; private LoadingAnimation loading;
private Sprite backgroundSprite; private Sprite backgroundSprite;
private ModDisplay modDisplay; private ModDisplay modDisplay;
private FillFlowContainer fillFlowContainer;
public bool Loading public bool Loading
{ {
@ -340,7 +347,7 @@ namespace osu.Game.Screens.Play
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
Children = new Drawable[] Children = new Drawable[]
{ {
fillFlowContainer = new FillFlowContainer new FillFlowContainer
{ {
AutoSizeAxes = Axes.Both, AutoSizeAxes = Axes.Both,
Origin = Anchor.TopCentre, Origin = Anchor.TopCentre,