mirror of
https://github.com/osukey/osukey.git
synced 2025-08-05 15:44:04 +09:00
Merge branch 'master' into stop-counting-to-score-after-fail
This commit is contained in:
119
osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs
Normal file
119
osu.Game.Tests/Visual/Gameplay/TestSceneBarHitErrorMeter.cs
Normal file
@ -0,0 +1,119 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using osu.Game.Rulesets.Judgements;
|
||||
using osu.Framework.MathUtils;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Catch.Scoring;
|
||||
using osu.Game.Rulesets.Mania.Scoring;
|
||||
using osu.Game.Rulesets.Osu.Scoring;
|
||||
using osu.Game.Rulesets.Scoring;
|
||||
using osu.Game.Rulesets.Taiko.Scoring;
|
||||
using osu.Game.Screens.Play.HUD.HitErrorMeters;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public class TestSceneBarHitErrorMeter : OsuTestScene
|
||||
{
|
||||
public override IReadOnlyList<Type> RequiredTypes => new[]
|
||||
{
|
||||
typeof(HitErrorMeter),
|
||||
};
|
||||
|
||||
private HitErrorMeter meter;
|
||||
private HitErrorMeter meter2;
|
||||
private HitWindows hitWindows;
|
||||
|
||||
public TestSceneBarHitErrorMeter()
|
||||
{
|
||||
recreateDisplay(new OsuHitWindows(), 5);
|
||||
|
||||
AddRepeatStep("New random judgement", () => newJudgement(), 40);
|
||||
|
||||
AddRepeatStep("New max negative", () => newJudgement(-hitWindows.WindowFor(HitResult.Meh)), 20);
|
||||
AddRepeatStep("New max positive", () => newJudgement(hitWindows.WindowFor(HitResult.Meh)), 20);
|
||||
AddStep("New fixed judgement (50ms)", () => newJudgement(50));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOsu()
|
||||
{
|
||||
AddStep("OD 1", () => recreateDisplay(new OsuHitWindows(), 1));
|
||||
AddStep("OD 10", () => recreateDisplay(new OsuHitWindows(), 10));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTaiko()
|
||||
{
|
||||
AddStep("OD 1", () => recreateDisplay(new TaikoHitWindows(), 1));
|
||||
AddStep("OD 10", () => recreateDisplay(new TaikoHitWindows(), 10));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMania()
|
||||
{
|
||||
AddStep("OD 1", () => recreateDisplay(new ManiaHitWindows(), 1));
|
||||
AddStep("OD 10", () => recreateDisplay(new ManiaHitWindows(), 10));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCatch()
|
||||
{
|
||||
AddStep("OD 1", () => recreateDisplay(new CatchHitWindows(), 1));
|
||||
AddStep("OD 10", () => recreateDisplay(new CatchHitWindows(), 10));
|
||||
}
|
||||
|
||||
private void recreateDisplay(HitWindows hitWindows, float overallDifficulty)
|
||||
{
|
||||
this.hitWindows = hitWindows;
|
||||
|
||||
hitWindows?.SetDifficulty(overallDifficulty);
|
||||
|
||||
Clear();
|
||||
|
||||
Add(new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Direction = FillDirection.Vertical,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Children = new[]
|
||||
{
|
||||
new SpriteText { Text = $@"Great: {hitWindows?.WindowFor(HitResult.Great)}" },
|
||||
new SpriteText { Text = $@"Good: {hitWindows?.WindowFor(HitResult.Good)}" },
|
||||
new SpriteText { Text = $@"Meh: {hitWindows?.WindowFor(HitResult.Meh)}" },
|
||||
}
|
||||
});
|
||||
|
||||
Add(meter = new BarHitErrorMeter(hitWindows, true)
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
});
|
||||
|
||||
Add(meter2 = new BarHitErrorMeter(hitWindows, false)
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
});
|
||||
}
|
||||
|
||||
private void newJudgement(double offset = 0)
|
||||
{
|
||||
var judgement = new JudgementResult(new HitObject(), new Judgement())
|
||||
{
|
||||
TimeOffset = offset == 0 ? RNG.Next(-150, 150) : offset,
|
||||
Type = HitResult.Perfect,
|
||||
};
|
||||
|
||||
meter.OnNewJudgement(judgement);
|
||||
meter2.OnNewJudgement(judgement);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,305 @@
|
||||
// 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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.MathUtils;
|
||||
using osu.Framework.Timing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Rulesets.Difficulty;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Drawables;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osu.Game.Rulesets.UI.Scrolling;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
public class TestSceneDrawableScrollingRuleset : OsuTestScene
|
||||
{
|
||||
/// <summary>
|
||||
/// The amount of time visible by the "view window" of the playfield.
|
||||
/// All hitobjects added through <see cref="createBeatmap"/> are spaced apart by this value, such that for a beat length of 1000,
|
||||
/// there will be at most 2 hitobjects visible in the "view window".
|
||||
/// </summary>
|
||||
private const double time_range = 1000;
|
||||
|
||||
private readonly ManualClock testClock = new ManualClock();
|
||||
private TestDrawableScrollingRuleset drawableRuleset;
|
||||
|
||||
[SetUp]
|
||||
public void Setup() => Schedule(() => testClock.CurrentTime = 0);
|
||||
|
||||
[Test]
|
||||
public void TestRelativeBeatLengthScaleSingleTimingPoint()
|
||||
{
|
||||
var beatmap = createBeatmap(new TimingControlPoint { BeatLength = time_range / 2 });
|
||||
|
||||
createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
|
||||
|
||||
assertPosition(0, 0f);
|
||||
|
||||
// The single timing point is 1x speed relative to itself, such that the hitobject occurring time_range milliseconds later should appear
|
||||
// at the bottom of the view window regardless of the timing point's beat length
|
||||
assertPosition(1, 1f);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRelativeBeatLengthScaleTimingPointBeyondEndDoesNotBecomeDominant()
|
||||
{
|
||||
var beatmap = createBeatmap(
|
||||
new TimingControlPoint { BeatLength = time_range / 2 },
|
||||
new TimingControlPoint { Time = 12000, BeatLength = time_range },
|
||||
new TimingControlPoint { Time = 100000, BeatLength = time_range });
|
||||
|
||||
createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
|
||||
|
||||
assertPosition(0, 0f);
|
||||
assertPosition(1, 1f);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestRelativeBeatLengthScaleFromSecondTimingPoint()
|
||||
{
|
||||
var beatmap = createBeatmap(
|
||||
new TimingControlPoint { BeatLength = time_range },
|
||||
new TimingControlPoint { Time = 3 * time_range, BeatLength = time_range / 2 });
|
||||
|
||||
createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true);
|
||||
|
||||
// The first timing point should have a relative velocity of 2
|
||||
assertPosition(0, 0f);
|
||||
assertPosition(1, 0.5f);
|
||||
assertPosition(2, 1f);
|
||||
|
||||
// Move to the second timing point
|
||||
setTime(3 * time_range);
|
||||
assertPosition(3, 0f);
|
||||
|
||||
// As above, this is the timing point that is 1x speed relative to itself, so the hitobject occurring time_range milliseconds later should be at the bottom of the view window
|
||||
assertPosition(4, 1f);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNonRelativeScale()
|
||||
{
|
||||
var beatmap = createBeatmap(
|
||||
new TimingControlPoint { BeatLength = time_range },
|
||||
new TimingControlPoint { Time = 3 * time_range, BeatLength = time_range / 2 });
|
||||
|
||||
createTest(beatmap);
|
||||
|
||||
assertPosition(0, 0f);
|
||||
assertPosition(1, 1);
|
||||
|
||||
// Move to the second timing point
|
||||
setTime(3 * time_range);
|
||||
assertPosition(3, 0f);
|
||||
|
||||
// For a beat length of 500, the view window of this timing point is elongated 2x (1000 / 500), such that the second hitobject is two TimeRanges away (offscreen)
|
||||
// To bring it on-screen, half TimeRange is added to the current time, bringing the second half of the view window into view, and the hitobject should appear at the bottom
|
||||
setTime(3 * time_range + time_range / 2);
|
||||
assertPosition(4, 1f);
|
||||
}
|
||||
|
||||
private void assertPosition(int index, float relativeY) => AddAssert($"hitobject {index} at {relativeY}",
|
||||
() => Precision.AlmostEquals(drawableRuleset.Playfield.AllHitObjects.ElementAt(index).DrawPosition.Y, drawableRuleset.Playfield.HitObjectContainer.DrawHeight * relativeY));
|
||||
|
||||
private void setTime(double time)
|
||||
{
|
||||
AddStep($"set time = {time}", () => testClock.CurrentTime = time);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Creates an <see cref="IBeatmap"/>, containing 10 hitobjects and user-provided timing points.
|
||||
/// The hitobjects are spaced <see cref="time_range"/> milliseconds apart.
|
||||
/// </summary>
|
||||
/// <param name="timingControlPoints">The timing points to add to the beatmap.</param>
|
||||
/// <returns>The <see cref="IBeatmap"/>.</returns>
|
||||
private IBeatmap createBeatmap(params TimingControlPoint[] timingControlPoints)
|
||||
{
|
||||
var beatmap = new Beatmap<HitObject> { BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo } };
|
||||
|
||||
beatmap.ControlPointInfo.TimingPoints.AddRange(timingControlPoints);
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
beatmap.HitObjects.Add(new HitObject { StartTime = i * time_range });
|
||||
|
||||
return beatmap;
|
||||
}
|
||||
|
||||
private void createTest(IBeatmap beatmap, Action<TestDrawableScrollingRuleset> overrideAction = null) => AddStep("create test", () =>
|
||||
{
|
||||
var ruleset = new TestScrollingRuleset();
|
||||
|
||||
drawableRuleset = (TestDrawableScrollingRuleset)ruleset.CreateDrawableRulesetWith(CreateWorkingBeatmap(beatmap), Array.Empty<Mod>());
|
||||
drawableRuleset.FrameStablePlayback = false;
|
||||
|
||||
overrideAction?.Invoke(drawableRuleset);
|
||||
|
||||
Child = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Height = 0.75f,
|
||||
Width = 400,
|
||||
Masking = true,
|
||||
Clock = new FramedClock(testClock),
|
||||
Child = drawableRuleset
|
||||
};
|
||||
});
|
||||
|
||||
#region Ruleset
|
||||
|
||||
private class TestScrollingRuleset : Ruleset
|
||||
{
|
||||
public TestScrollingRuleset(RulesetInfo rulesetInfo = null)
|
||||
: base(rulesetInfo)
|
||||
{
|
||||
}
|
||||
|
||||
public override IEnumerable<Mod> GetModsFor(ModType type) => throw new NotImplementedException();
|
||||
|
||||
public override DrawableRuleset CreateDrawableRulesetWith(IWorkingBeatmap beatmap, IReadOnlyList<Mod> mods) => new TestDrawableScrollingRuleset(this, beatmap, mods);
|
||||
|
||||
public override IBeatmapConverter CreateBeatmapConverter(IBeatmap beatmap) => new TestBeatmapConverter(beatmap);
|
||||
|
||||
public override DifficultyCalculator CreateDifficultyCalculator(WorkingBeatmap beatmap) => throw new NotImplementedException();
|
||||
|
||||
public override string Description { get; } = string.Empty;
|
||||
|
||||
public override string ShortName { get; } = string.Empty;
|
||||
}
|
||||
|
||||
private class TestDrawableScrollingRuleset : DrawableScrollingRuleset<TestHitObject>
|
||||
{
|
||||
public bool RelativeScaleBeatLengthsOverride { get; set; }
|
||||
|
||||
protected override bool RelativeScaleBeatLengths => RelativeScaleBeatLengthsOverride;
|
||||
|
||||
protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping;
|
||||
|
||||
public TestDrawableScrollingRuleset(Ruleset ruleset, IWorkingBeatmap beatmap, IReadOnlyList<Mod> mods)
|
||||
: base(ruleset, beatmap, mods)
|
||||
{
|
||||
TimeRange.Value = time_range;
|
||||
}
|
||||
|
||||
public override DrawableHitObject<TestHitObject> CreateDrawableRepresentation(TestHitObject h) => new DrawableTestHitObject(h);
|
||||
|
||||
protected override PassThroughInputManager CreateInputManager() => new PassThroughInputManager();
|
||||
|
||||
protected override Playfield CreatePlayfield() => new TestPlayfield();
|
||||
}
|
||||
|
||||
private class TestPlayfield : ScrollingPlayfield
|
||||
{
|
||||
public TestPlayfield()
|
||||
{
|
||||
AddInternal(new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Alpha = 0.2f,
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Top = 150 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 2,
|
||||
Colour = Color4.Green
|
||||
},
|
||||
HitObjectContainer
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private class TestBeatmapConverter : BeatmapConverter<TestHitObject>
|
||||
{
|
||||
public TestBeatmapConverter(IBeatmap beatmap)
|
||||
: base(beatmap)
|
||||
{
|
||||
}
|
||||
|
||||
protected override IEnumerable<Type> ValidConversionTypes => new[] { typeof(HitObject) };
|
||||
|
||||
protected override IEnumerable<TestHitObject> ConvertHitObject(HitObject original, IBeatmap beatmap)
|
||||
{
|
||||
yield return new TestHitObject
|
||||
{
|
||||
StartTime = original.StartTime,
|
||||
EndTime = (original as IHasEndTime)?.EndTime ?? (original.StartTime + 100)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region HitObject
|
||||
|
||||
private class TestHitObject : HitObject, IHasEndTime
|
||||
{
|
||||
public double EndTime { get; set; }
|
||||
|
||||
public double Duration => EndTime - StartTime;
|
||||
}
|
||||
|
||||
private class DrawableTestHitObject : DrawableHitObject<TestHitObject>
|
||||
{
|
||||
public DrawableTestHitObject(TestHitObject hitObject)
|
||||
: base(hitObject)
|
||||
{
|
||||
Anchor = Anchor.TopCentre;
|
||||
Origin = Anchor.TopCentre;
|
||||
|
||||
Size = new Vector2(100, 25);
|
||||
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.LightPink
|
||||
},
|
||||
new Box
|
||||
{
|
||||
Origin = Anchor.CentreLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 2,
|
||||
Colour = Color4.Red
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
@ -3,12 +3,13 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Screens.Play;
|
||||
using osuTK;
|
||||
@ -29,57 +30,118 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuGameBase game)
|
||||
{
|
||||
Child = globalActionContainer = new GlobalActionContainer(game)
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
pauseOverlay = new PauseOverlay
|
||||
{
|
||||
OnResume = () => Logger.Log(@"Resume"),
|
||||
OnRetry = () => Logger.Log(@"Retry"),
|
||||
OnQuit = () => Logger.Log(@"Quit"),
|
||||
},
|
||||
failOverlay = new FailOverlay
|
||||
Child = globalActionContainer = new GlobalActionContainer(game);
|
||||
}
|
||||
|
||||
{
|
||||
OnRetry = () => Logger.Log(@"Retry"),
|
||||
OnQuit = () => Logger.Log(@"Quit"),
|
||||
}
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
globalActionContainer.Children = new Drawable[]
|
||||
{
|
||||
pauseOverlay = new PauseOverlay
|
||||
{
|
||||
OnResume = () => Logger.Log(@"Resume"),
|
||||
OnRetry = () => Logger.Log(@"Retry"),
|
||||
OnQuit = () => Logger.Log(@"Quit"),
|
||||
},
|
||||
failOverlay = new FailOverlay
|
||||
|
||||
{
|
||||
OnRetry = () => Logger.Log(@"Retry"),
|
||||
OnQuit = () => Logger.Log(@"Quit"),
|
||||
}
|
||||
};
|
||||
|
||||
InputManager.MoveMouseTo(Vector2.Zero);
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestAdjustRetryCount()
|
||||
{
|
||||
showOverlay();
|
||||
|
||||
var retryCount = 0;
|
||||
|
||||
AddStep("Add retry", () =>
|
||||
AddRepeatStep("Add retry", () =>
|
||||
{
|
||||
retryCount++;
|
||||
pauseOverlay.Retries = failOverlay.Retries = retryCount;
|
||||
});
|
||||
}, 10);
|
||||
}
|
||||
|
||||
AddToggleStep("Toggle pause overlay", t => pauseOverlay.ToggleVisibility());
|
||||
AddToggleStep("Toggle fail overlay", t => failOverlay.ToggleVisibility());
|
||||
/// <summary>
|
||||
/// Tests that pressing enter after an overlay shows doesn't trigger an event because a selection hasn't occurred.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestEnterWithoutSelection()
|
||||
{
|
||||
showOverlay();
|
||||
|
||||
testHideResets();
|
||||
AddStep("Press select", () => press(GlobalAction.Select));
|
||||
AddAssert("Overlay still open", () => pauseOverlay.State.Value == Visibility.Visible);
|
||||
}
|
||||
|
||||
testEnterWithoutSelection();
|
||||
testKeyUpFromInitial();
|
||||
testKeyDownFromInitial();
|
||||
testKeyUpWrapping();
|
||||
testKeyDownWrapping();
|
||||
/// <summary>
|
||||
/// Tests that pressing the up arrow from the initial state selects the last button.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestKeyUpFromInitial()
|
||||
{
|
||||
showOverlay();
|
||||
|
||||
testMouseSelectionAfterKeySelection();
|
||||
testKeySelectionAfterMouseSelection();
|
||||
AddStep("Up arrow", () => press(Key.Up));
|
||||
AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected.Value);
|
||||
}
|
||||
|
||||
testMouseDeselectionResets();
|
||||
/// <summary>
|
||||
/// Tests that pressing the down arrow from the initial state selects the first button.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestKeyDownFromInitial()
|
||||
{
|
||||
showOverlay();
|
||||
|
||||
testClickSelection();
|
||||
testEnterKeySelection();
|
||||
AddStep("Down arrow", () => press(Key.Down));
|
||||
AddAssert("First button selected", () => getButton(0).Selected.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that pressing the up arrow repeatedly causes the selected button to wrap correctly.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestKeyUpWrapping()
|
||||
{
|
||||
AddStep("Show overlay", () => failOverlay.Show());
|
||||
|
||||
AddStep("Up arrow", () => press(Key.Up));
|
||||
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
|
||||
AddStep("Up arrow", () => press(Key.Up));
|
||||
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
|
||||
AddStep("Up arrow", () => press(Key.Up));
|
||||
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that pressing the down arrow repeatedly causes the selected button to wrap correctly.
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestKeyDownWrapping()
|
||||
{
|
||||
AddStep("Show overlay", () => failOverlay.Show());
|
||||
|
||||
AddStep("Down arrow", () => press(Key.Down));
|
||||
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
|
||||
AddStep("Down arrow", () => press(Key.Down));
|
||||
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
|
||||
AddStep("Down arrow", () => press(Key.Down));
|
||||
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Test that hiding the overlay after hovering a button will reset the overlay to the initial state with no buttons selected.
|
||||
/// </summary>
|
||||
private void testHideResets()
|
||||
[Test]
|
||||
public void TestHideResets()
|
||||
{
|
||||
AddStep("Show overlay", () => failOverlay.Show());
|
||||
|
||||
@ -90,141 +152,78 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that pressing enter after an overlay shows doesn't trigger an event because a selection hasn't occurred.
|
||||
/// Tests that entering menu with cursor initially on button doesn't selects it immediately.
|
||||
/// This is to allow for stable keyboard navigation.
|
||||
/// </summary>
|
||||
private void testEnterWithoutSelection()
|
||||
[Test]
|
||||
public void TestInitialButtonHover()
|
||||
{
|
||||
AddStep("Show overlay", () => pauseOverlay.Show());
|
||||
showOverlay();
|
||||
|
||||
AddStep("Press select", () => press(GlobalAction.Select));
|
||||
AddAssert("Overlay still open", () => pauseOverlay.State.Value == Visibility.Visible);
|
||||
AddStep("Hover first button", () => InputManager.MoveMouseTo(getButton(0)));
|
||||
|
||||
AddStep("Hide overlay", () => pauseOverlay.Hide());
|
||||
}
|
||||
showOverlay();
|
||||
|
||||
/// <summary>
|
||||
/// Tests that pressing the up arrow from the initial state selects the last button.
|
||||
/// </summary>
|
||||
private void testKeyUpFromInitial()
|
||||
{
|
||||
AddStep("Show overlay", () => pauseOverlay.Show());
|
||||
AddAssert("First button not selected", () => !getButton(0).Selected.Value);
|
||||
|
||||
AddStep("Up arrow", () => press(Key.Up));
|
||||
AddAssert("Last button selected", () => pauseOverlay.Buttons.Last().Selected.Value);
|
||||
AddStep("Move slightly", () => InputManager.MoveMouseTo(InputManager.CurrentState.Mouse.Position + new Vector2(1)));
|
||||
|
||||
AddStep("Hide overlay", () => pauseOverlay.Hide());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that pressing the down arrow from the initial state selects the first button.
|
||||
/// </summary>
|
||||
private void testKeyDownFromInitial()
|
||||
{
|
||||
AddStep("Show overlay", () => pauseOverlay.Show());
|
||||
|
||||
AddStep("Down arrow", () => press(Key.Down));
|
||||
AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected.Value);
|
||||
|
||||
AddStep("Hide overlay", () => pauseOverlay.Hide());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that pressing the up arrow repeatedly causes the selected button to wrap correctly.
|
||||
/// </summary>
|
||||
private void testKeyUpWrapping()
|
||||
{
|
||||
AddStep("Show overlay", () => failOverlay.Show());
|
||||
|
||||
AddStep("Up arrow", () => press(Key.Up));
|
||||
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
|
||||
AddStep("Up arrow", () => press(Key.Up));
|
||||
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
|
||||
AddStep("Up arrow", () => press(Key.Up));
|
||||
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
|
||||
|
||||
AddStep("Hide overlay", () => failOverlay.Hide());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that pressing the down arrow repeatedly causes the selected button to wrap correctly.
|
||||
/// </summary>
|
||||
private void testKeyDownWrapping()
|
||||
{
|
||||
AddStep("Show overlay", () => failOverlay.Show());
|
||||
|
||||
AddStep("Down arrow", () => press(Key.Down));
|
||||
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
|
||||
AddStep("Down arrow", () => press(Key.Down));
|
||||
AddAssert("Last button selected", () => failOverlay.Buttons.Last().Selected.Value);
|
||||
AddStep("Down arrow", () => press(Key.Down));
|
||||
AddAssert("First button selected", () => failOverlay.Buttons.First().Selected.Value);
|
||||
|
||||
AddStep("Hide overlay", () => failOverlay.Hide());
|
||||
AddAssert("First button selected", () => getButton(0).Selected.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that hovering a button that was previously selected with the keyboard correctly selects the new button and deselects the previous button.
|
||||
/// </summary>
|
||||
private void testMouseSelectionAfterKeySelection()
|
||||
[Test]
|
||||
public void TestMouseSelectionAfterKeySelection()
|
||||
{
|
||||
AddStep("Show overlay", () => pauseOverlay.Show());
|
||||
|
||||
var secondButton = pauseOverlay.Buttons.Skip(1).First();
|
||||
showOverlay();
|
||||
|
||||
AddStep("Down arrow", () => press(Key.Down));
|
||||
AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton));
|
||||
AddAssert("First button not selected", () => !pauseOverlay.Buttons.First().Selected.Value);
|
||||
AddAssert("Second button selected", () => secondButton.Selected.Value);
|
||||
|
||||
AddStep("Hide overlay", () => pauseOverlay.Hide());
|
||||
AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
|
||||
AddAssert("First button not selected", () => !getButton(0).Selected.Value);
|
||||
AddAssert("Second button selected", () => getButton(1).Selected.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that pressing a key after selecting a button with a hover event correctly selects a new button and deselects the previous button.
|
||||
/// </summary>
|
||||
private void testKeySelectionAfterMouseSelection()
|
||||
[Test]
|
||||
public void TestKeySelectionAfterMouseSelection()
|
||||
{
|
||||
AddStep("Show overlay", () =>
|
||||
{
|
||||
pauseOverlay.Show();
|
||||
InputManager.MoveMouseTo(Vector2.Zero);
|
||||
});
|
||||
|
||||
var secondButton = pauseOverlay.Buttons.Skip(1).First();
|
||||
|
||||
AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton));
|
||||
AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
|
||||
AddStep("Up arrow", () => press(Key.Up));
|
||||
AddAssert("Second button not selected", () => !secondButton.Selected.Value);
|
||||
AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected.Value);
|
||||
|
||||
AddStep("Hide overlay", () => pauseOverlay.Hide());
|
||||
AddAssert("Second button not selected", () => !getButton(1).Selected.Value);
|
||||
AddAssert("First button selected", () => getButton(0).Selected.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that deselecting with the mouse by losing hover will reset the overlay to the initial state.
|
||||
/// </summary>
|
||||
private void testMouseDeselectionResets()
|
||||
[Test]
|
||||
public void TestMouseDeselectionResets()
|
||||
{
|
||||
AddStep("Show overlay", () => pauseOverlay.Show());
|
||||
showOverlay();
|
||||
|
||||
var secondButton = pauseOverlay.Buttons.Skip(1).First();
|
||||
|
||||
AddStep("Hover second button", () => InputManager.MoveMouseTo(secondButton));
|
||||
AddStep("Hover second button", () => InputManager.MoveMouseTo(getButton(1)));
|
||||
AddStep("Unhover second button", () => InputManager.MoveMouseTo(Vector2.Zero));
|
||||
AddStep("Down arrow", () => press(Key.Down));
|
||||
AddAssert("First button selected", () => pauseOverlay.Buttons.First().Selected.Value); // Initial state condition
|
||||
|
||||
AddStep("Hide overlay", () => pauseOverlay.Hide());
|
||||
AddAssert("First button selected", () => getButton(0).Selected.Value); // Initial state condition
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Tests that clicking on a button correctly causes a click event for that button.
|
||||
/// </summary>
|
||||
private void testClickSelection()
|
||||
[Test]
|
||||
public void TestClickSelection()
|
||||
{
|
||||
AddStep("Show overlay", () => pauseOverlay.Show());
|
||||
|
||||
var retryButton = pauseOverlay.Buttons.Skip(1).First();
|
||||
showOverlay();
|
||||
|
||||
bool triggered = false;
|
||||
AddStep("Click retry button", () =>
|
||||
@ -232,7 +231,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
var lastAction = pauseOverlay.OnRetry;
|
||||
pauseOverlay.OnRetry = () => triggered = true;
|
||||
|
||||
retryButton.Click();
|
||||
getButton(1).Click();
|
||||
pauseOverlay.OnRetry = lastAction;
|
||||
});
|
||||
|
||||
@ -243,9 +242,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
/// <summary>
|
||||
/// Tests that pressing the enter key with a button selected correctly causes a click event for that button.
|
||||
/// </summary>
|
||||
private void testEnterKeySelection()
|
||||
[Test]
|
||||
public void TestEnterKeySelection()
|
||||
{
|
||||
AddStep("Show overlay", () => pauseOverlay.Show());
|
||||
showOverlay();
|
||||
|
||||
AddStep("Select second button", () =>
|
||||
{
|
||||
@ -275,6 +275,10 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddAssert("Overlay is closed", () => pauseOverlay.State.Value == Visibility.Hidden);
|
||||
}
|
||||
|
||||
private void showOverlay() => AddStep("Show overlay", () => pauseOverlay.Show());
|
||||
|
||||
private DialogButton getButton(int index) => pauseOverlay.Buttons.Skip(index).First();
|
||||
|
||||
private void press(Key key)
|
||||
{
|
||||
InputManager.PressKey(key);
|
||||
|
@ -17,6 +17,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
private bool exitAction;
|
||||
|
||||
protected override double TimePerAction => 100; // required for the early exit test, since hold-to-confirm delay is 200ms
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
|
@ -200,10 +200,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void UpdateState(ArmedState state)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class TestDrawableHitObject : DrawableHitObject<HitObject>
|
||||
@ -216,10 +212,6 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
AddInternal(new Box { Size = new Vector2(75) });
|
||||
}
|
||||
|
||||
protected override void UpdateState(ArmedState state)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,11 +5,14 @@ using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Skinning;
|
||||
@ -27,7 +30,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
AddStep("setup layout larger source", () =>
|
||||
{
|
||||
Child = new LocalSkinOverrideContainer(new SizedSource(50))
|
||||
Child = new SkinProvidingContainer(new SizedSource(50))
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = fill = new FillFlowContainer<ExposedSkinnableDrawable>
|
||||
@ -59,7 +62,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
AddStep("setup layout larger source", () =>
|
||||
{
|
||||
Child = new LocalSkinOverrideContainer(new SizedSource(30))
|
||||
Child = new SkinProvidingContainer(new SizedSource(30))
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = fill = new FillFlowContainer<ExposedSkinnableDrawable>
|
||||
@ -95,7 +98,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
Child = new SkinSourceContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new LocalSkinOverrideContainer(secondarySource)
|
||||
Child = new SkinProvidingContainer(secondarySource)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)
|
||||
@ -120,7 +123,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
Child = new SkinSourceContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = target = new LocalSkinOverrideContainer(secondarySource)
|
||||
Child = target = new SkinProvidingContainer(secondarySource)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
}
|
||||
@ -132,12 +135,55 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
AddAssert("skinchanged only called once", () => consumer.SkinChangedCount == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSwitchOff()
|
||||
{
|
||||
SkinConsumer consumer = null;
|
||||
SwitchableSkinProvidingContainer target = null;
|
||||
|
||||
AddStep("setup layout", () =>
|
||||
{
|
||||
Child = new SkinSourceContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = target = new SwitchableSkinProvidingContainer(new SecondarySource())
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
AddStep("add permissive", () => target.Add(consumer = new SkinConsumer("test", name => new NamedBox("Default Implementation"), source => true)));
|
||||
AddAssert("consumer using override source", () => consumer.Drawable is SecondarySourceBox);
|
||||
AddStep("disable", () => target.Disable());
|
||||
AddAssert("consumer using base source", () => consumer.Drawable is BaseSourceBox);
|
||||
}
|
||||
|
||||
private class SwitchableSkinProvidingContainer : SkinProvidingContainer
|
||||
{
|
||||
private bool allow = true;
|
||||
|
||||
protected override bool AllowDrawableLookup(ISkinComponent component) => allow;
|
||||
|
||||
public void Disable()
|
||||
{
|
||||
allow = false;
|
||||
TriggerSourceChanged();
|
||||
}
|
||||
|
||||
public SwitchableSkinProvidingContainer(ISkin skin)
|
||||
: base(skin)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
private class ExposedSkinnableDrawable : SkinnableDrawable
|
||||
{
|
||||
public new Drawable Drawable => base.Drawable;
|
||||
|
||||
public ExposedSkinnableDrawable(string name, Func<string, Drawable> defaultImplementation, Func<ISkinSource, bool> allowFallback = null, ConfineMode confineMode = ConfineMode.ScaleDownToFit)
|
||||
: base(name, defaultImplementation, allowFallback, confineMode)
|
||||
public ExposedSkinnableDrawable(string name, Func<ISkinComponent, Drawable> defaultImplementation, Func<ISkinSource, bool> allowFallback = null,
|
||||
ConfineMode confineMode = ConfineMode.ScaleDownToFit)
|
||||
: base(new TestSkinComponent(name), defaultImplementation, allowFallback, confineMode)
|
||||
{
|
||||
}
|
||||
}
|
||||
@ -205,8 +251,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
public new Drawable Drawable => base.Drawable;
|
||||
public int SkinChangedCount { get; private set; }
|
||||
|
||||
public SkinConsumer(string name, Func<string, Drawable> defaultImplementation, Func<ISkinSource, bool> allowFallback = null)
|
||||
: base(name, defaultImplementation, allowFallback)
|
||||
public SkinConsumer(string name, Func<ISkinComponent, Drawable> defaultImplementation, Func<ISkinSource, bool> allowFallback = null)
|
||||
: base(new TestSkinComponent(name), defaultImplementation, allowFallback)
|
||||
{
|
||||
}
|
||||
|
||||
@ -242,8 +288,8 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
this.size = size;
|
||||
}
|
||||
|
||||
public Drawable GetDrawableComponent(string componentName) =>
|
||||
componentName == "available"
|
||||
public Drawable GetDrawableComponent(ISkinComponent componentName) =>
|
||||
componentName.LookupName == "available"
|
||||
? new DrawWidthBox
|
||||
{
|
||||
Colour = Color4.Yellow,
|
||||
@ -253,31 +299,48 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
public Texture GetTexture(string componentName) => throw new NotImplementedException();
|
||||
|
||||
public SampleChannel GetSample(string sampleName) => throw new NotImplementedException();
|
||||
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
|
||||
|
||||
public TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
|
||||
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private class SecondarySource : ISkin
|
||||
{
|
||||
public Drawable GetDrawableComponent(string componentName) => new SecondarySourceBox();
|
||||
public Drawable GetDrawableComponent(ISkinComponent componentName) => new SecondarySourceBox();
|
||||
|
||||
public Texture GetTexture(string componentName) => throw new NotImplementedException();
|
||||
|
||||
public SampleChannel GetSample(string sampleName) => throw new NotImplementedException();
|
||||
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
|
||||
|
||||
public TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
|
||||
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotImplementedException();
|
||||
}
|
||||
|
||||
private class SkinSourceContainer : Container, ISkin
|
||||
[Cached(typeof(ISkinSource))]
|
||||
private class SkinSourceContainer : Container, ISkinSource
|
||||
{
|
||||
public Drawable GetDrawableComponent(string componentName) => new BaseSourceBox();
|
||||
public Drawable GetDrawableComponent(ISkinComponent componentName) => new BaseSourceBox();
|
||||
|
||||
public Texture GetTexture(string componentName) => throw new NotImplementedException();
|
||||
|
||||
public SampleChannel GetSample(string sampleName) => throw new NotImplementedException();
|
||||
public SampleChannel GetSample(ISampleInfo sampleInfo) => throw new NotImplementedException();
|
||||
|
||||
public TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration => throw new NotImplementedException();
|
||||
public IBindable<TValue> GetConfig<TLookup, TValue>(TLookup lookup) => throw new NotImplementedException();
|
||||
|
||||
public event Action SourceChanged;
|
||||
}
|
||||
|
||||
private class TestSkinComponent : ISkinComponent
|
||||
{
|
||||
private readonly string name;
|
||||
|
||||
public TestSkinComponent(string name)
|
||||
{
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public string ComponentGroup => string.Empty;
|
||||
|
||||
public string LookupName => name;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,32 +21,38 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
private readonly Container<DrawableStoryboard> storyboardContainer;
|
||||
private DrawableStoryboard storyboard;
|
||||
|
||||
[Cached]
|
||||
private MusicController musicController = new MusicController();
|
||||
|
||||
public TestSceneStoryboard()
|
||||
{
|
||||
Clock = new FramedClock();
|
||||
|
||||
Add(new Container
|
||||
AddRange(new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
musicController,
|
||||
new Container
|
||||
{
|
||||
new Box
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black,
|
||||
},
|
||||
storyboardContainer = new Container<DrawableStoryboard>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Color4.Black,
|
||||
},
|
||||
storyboardContainer = new Container<DrawableStoryboard>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
Add(new MusicController
|
||||
{
|
||||
Origin = Anchor.TopRight,
|
||||
Anchor = Anchor.TopRight,
|
||||
State = { Value = Visibility.Visible },
|
||||
new NowPlayingOverlay
|
||||
{
|
||||
Origin = Anchor.TopRight,
|
||||
Anchor = Anchor.TopRight,
|
||||
State = { Value = Visibility.Visible },
|
||||
}
|
||||
});
|
||||
|
||||
AddStep("Restart", restart);
|
||||
|
Reference in New Issue
Block a user