mirror of
https://github.com/ppy/osu.git
synced 2024-11-11 07:07:45 +08:00
Merge branch 'master' into limit-barline-generation-sanity
This commit is contained in:
commit
508f1f488c
@ -35,6 +35,8 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
private void load(CatchInputManager catchInputManager, OsuColour colours)
|
private void load(CatchInputManager catchInputManager, OsuColour colours)
|
||||||
{
|
{
|
||||||
const float width = 0.15f;
|
const float width = 0.15f;
|
||||||
|
// Ratio between normal move area height and total input height
|
||||||
|
const float normal_area_height_ratio = 0.45f;
|
||||||
|
|
||||||
keyBindingContainer = catchInputManager.KeyBindingContainer;
|
keyBindingContainer = catchInputManager.KeyBindingContainer;
|
||||||
|
|
||||||
@ -54,18 +56,18 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
Width = width,
|
Width = width,
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
leftDashBox = new InputArea(TouchCatchAction.DashLeft, trackedActionSources)
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Width = 0.5f,
|
|
||||||
},
|
|
||||||
leftBox = new InputArea(TouchCatchAction.MoveLeft, trackedActionSources)
|
leftBox = new InputArea(TouchCatchAction.MoveLeft, trackedActionSources)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = 0.5f,
|
Height = normal_area_height_ratio,
|
||||||
Colour = colours.Gray9,
|
Colour = colours.Gray9,
|
||||||
Anchor = Anchor.TopRight,
|
Anchor = Anchor.BottomRight,
|
||||||
Origin = Anchor.TopRight,
|
Origin = Anchor.BottomRight,
|
||||||
|
},
|
||||||
|
leftDashBox = new InputArea(TouchCatchAction.DashLeft, trackedActionSources)
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Height = 1 - normal_area_height_ratio,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -80,15 +82,15 @@ namespace osu.Game.Rulesets.Catch.UI
|
|||||||
rightBox = new InputArea(TouchCatchAction.MoveRight, trackedActionSources)
|
rightBox = new InputArea(TouchCatchAction.MoveRight, trackedActionSources)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = 0.5f,
|
Height = normal_area_height_ratio,
|
||||||
Colour = colours.Gray9,
|
Colour = colours.Gray9,
|
||||||
|
Anchor = Anchor.BottomRight,
|
||||||
|
Origin = Anchor.BottomRight,
|
||||||
},
|
},
|
||||||
rightDashBox = new InputArea(TouchCatchAction.DashRight, trackedActionSources)
|
rightDashBox = new InputArea(TouchCatchAction.DashRight, trackedActionSources)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = 0.5f,
|
Height = 1 - normal_area_height_ratio,
|
||||||
Anchor = Anchor.TopRight,
|
|
||||||
Origin = Anchor.TopRight,
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -23,6 +23,15 @@ using osu.Game.Tests.Visual;
|
|||||||
|
|
||||||
namespace osu.Game.Rulesets.Mania.Tests
|
namespace osu.Game.Rulesets.Mania.Tests
|
||||||
{
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// Diagrams in this class are represented as:
|
||||||
|
/// - : time
|
||||||
|
/// O : note
|
||||||
|
/// [ ] : hold note
|
||||||
|
///
|
||||||
|
/// x : button press
|
||||||
|
/// o : button release
|
||||||
|
/// </summary>
|
||||||
public class TestSceneHoldNoteInput : RateAdjustedBeatmapTestScene
|
public class TestSceneHoldNoteInput : RateAdjustedBeatmapTestScene
|
||||||
{
|
{
|
||||||
private const double time_before_head = 250;
|
private const double time_before_head = 250;
|
||||||
@ -223,6 +232,149 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
assertTailJudgement(HitResult.Meh);
|
assertTailJudgement(HitResult.Meh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// -----[ ]-O-------------
|
||||||
|
/// xo o
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestPressAndReleaseJustBeforeTailWithNearbyNoteAndCloseByHead()
|
||||||
|
{
|
||||||
|
Note note;
|
||||||
|
|
||||||
|
const int duration = 50;
|
||||||
|
|
||||||
|
var beatmap = new Beatmap<ManiaHitObject>
|
||||||
|
{
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
// hold note is very short, to make the head still in range
|
||||||
|
new HoldNote
|
||||||
|
{
|
||||||
|
StartTime = time_head,
|
||||||
|
Duration = duration,
|
||||||
|
Column = 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Next note within tail lenience
|
||||||
|
note = new Note
|
||||||
|
{
|
||||||
|
StartTime = time_head + duration + 10
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
BeatmapInfo =
|
||||||
|
{
|
||||||
|
Difficulty = new BeatmapDifficulty { SliderTickRate = 4 },
|
||||||
|
Ruleset = new ManiaRuleset().RulesetInfo
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
performTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(time_head + duration, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_head + duration + 10),
|
||||||
|
}, beatmap);
|
||||||
|
|
||||||
|
assertHeadJudgement(HitResult.Good);
|
||||||
|
assertTailJudgement(HitResult.Perfect);
|
||||||
|
|
||||||
|
assertHitObjectJudgement(note, HitResult.Miss);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// -----[ ]--O--
|
||||||
|
/// xo o
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestPressAndReleaseJustBeforeTailWithNearbyNote()
|
||||||
|
{
|
||||||
|
Note note;
|
||||||
|
|
||||||
|
var beatmap = new Beatmap<ManiaHitObject>
|
||||||
|
{
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HoldNote
|
||||||
|
{
|
||||||
|
StartTime = time_head,
|
||||||
|
Duration = time_tail - time_head,
|
||||||
|
Column = 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Next note within tail lenience
|
||||||
|
note = new Note
|
||||||
|
{
|
||||||
|
StartTime = time_tail + 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
BeatmapInfo =
|
||||||
|
{
|
||||||
|
Difficulty = new BeatmapDifficulty { SliderTickRate = 4 },
|
||||||
|
Ruleset = new ManiaRuleset().RulesetInfo
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
performTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(time_tail - 10, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_tail),
|
||||||
|
}, beatmap);
|
||||||
|
|
||||||
|
assertHeadJudgement(HitResult.Miss);
|
||||||
|
assertTickJudgement(HitResult.LargeTickMiss);
|
||||||
|
assertTailJudgement(HitResult.Miss);
|
||||||
|
|
||||||
|
assertHitObjectJudgement(note, HitResult.Good);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// -----[ ]--O--
|
||||||
|
/// xo o
|
||||||
|
/// </summary>
|
||||||
|
[Test]
|
||||||
|
public void TestPressAndReleaseJustAfterTailWithNearbyNote()
|
||||||
|
{
|
||||||
|
Note note;
|
||||||
|
|
||||||
|
var beatmap = new Beatmap<ManiaHitObject>
|
||||||
|
{
|
||||||
|
HitObjects =
|
||||||
|
{
|
||||||
|
new HoldNote
|
||||||
|
{
|
||||||
|
StartTime = time_head,
|
||||||
|
Duration = time_tail - time_head,
|
||||||
|
Column = 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Next note within tail lenience
|
||||||
|
note = new Note
|
||||||
|
{
|
||||||
|
StartTime = time_tail + 50
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
BeatmapInfo =
|
||||||
|
{
|
||||||
|
Difficulty = new BeatmapDifficulty { SliderTickRate = 4 },
|
||||||
|
Ruleset = new ManiaRuleset().RulesetInfo
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
performTest(new List<ReplayFrame>
|
||||||
|
{
|
||||||
|
new ManiaReplayFrame(time_tail + 10, ManiaAction.Key1),
|
||||||
|
new ManiaReplayFrame(time_tail + 20),
|
||||||
|
}, beatmap);
|
||||||
|
|
||||||
|
assertHeadJudgement(HitResult.Miss);
|
||||||
|
assertTickJudgement(HitResult.LargeTickMiss);
|
||||||
|
assertTailJudgement(HitResult.Miss);
|
||||||
|
|
||||||
|
assertHitObjectJudgement(note, HitResult.Great);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// -----[ ]-----
|
/// -----[ ]-----
|
||||||
/// xo o
|
/// xo o
|
||||||
@ -351,20 +503,23 @@ namespace osu.Game.Rulesets.Mania.Tests
|
|||||||
.All(j => j.Type.IsHit()));
|
.All(j => j.Type.IsHit()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assertHitObjectJudgement(HitObject hitObject, HitResult result)
|
||||||
|
=> AddAssert($"object judged as {result}", () => judgementResults.First(j => j.HitObject == hitObject).Type, () => Is.EqualTo(result));
|
||||||
|
|
||||||
private void assertHeadJudgement(HitResult result)
|
private void assertHeadJudgement(HitResult result)
|
||||||
=> AddAssert($"head judged as {result}", () => judgementResults.First(j => j.HitObject is Note).Type == result);
|
=> AddAssert($"head judged as {result}", () => judgementResults.First(j => j.HitObject is Note).Type, () => Is.EqualTo(result));
|
||||||
|
|
||||||
private void assertTailJudgement(HitResult result)
|
private void assertTailJudgement(HitResult result)
|
||||||
=> AddAssert($"tail judged as {result}", () => judgementResults.Single(j => j.HitObject is TailNote).Type == result);
|
=> AddAssert($"tail judged as {result}", () => judgementResults.Single(j => j.HitObject is TailNote).Type, () => Is.EqualTo(result));
|
||||||
|
|
||||||
private void assertNoteJudgement(HitResult result)
|
private void assertNoteJudgement(HitResult result)
|
||||||
=> AddAssert($"hold note judged as {result}", () => judgementResults.Single(j => j.HitObject is HoldNote).Type == result);
|
=> AddAssert($"hold note judged as {result}", () => judgementResults.Single(j => j.HitObject is HoldNote).Type, () => Is.EqualTo(result));
|
||||||
|
|
||||||
private void assertTickJudgement(HitResult result)
|
private void assertTickJudgement(HitResult result)
|
||||||
=> AddAssert($"any tick judged as {result}", () => judgementResults.Where(j => j.HitObject is HoldNoteTick).Any(j => j.Type == result));
|
=> AddAssert($"any tick judged as {result}", () => judgementResults.Where(j => j.HitObject is HoldNoteTick).Select(j => j.Type), () => Does.Contain(result));
|
||||||
|
|
||||||
private void assertLastTickJudgement(HitResult result)
|
private void assertLastTickJudgement(HitResult result)
|
||||||
=> AddAssert($"last tick judged as {result}", () => judgementResults.Last(j => j.HitObject is HoldNoteTick).Type == result);
|
=> AddAssert($"last tick judged as {result}", () => judgementResults.Last(j => j.HitObject is HoldNoteTick).Type, () => Is.EqualTo(result));
|
||||||
|
|
||||||
private ScoreAccessibleReplayPlayer currentPlayer;
|
private ScoreAccessibleReplayPlayer currentPlayer;
|
||||||
|
|
||||||
|
@ -262,14 +262,24 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
tick.MissForcefully();
|
tick.MissForcefully();
|
||||||
}
|
}
|
||||||
|
|
||||||
ApplyResult(r => r.Type = Tail.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult);
|
if (Tail.IsHit)
|
||||||
endHold();
|
ApplyResult(r => r.Type = r.Judgement.MaxResult);
|
||||||
|
else
|
||||||
|
MissForcefully();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (Tail.Judged && !Tail.IsHit)
|
if (Tail.Judged && !Tail.IsHit)
|
||||||
HoldBrokenTime = Time.Current;
|
HoldBrokenTime = Time.Current;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public override void MissForcefully()
|
||||||
|
{
|
||||||
|
base.MissForcefully();
|
||||||
|
|
||||||
|
// Important that this is always called when a result is applied.
|
||||||
|
endHold();
|
||||||
|
}
|
||||||
|
|
||||||
public bool OnPressed(KeyBindingPressEvent<ManiaAction> e)
|
public bool OnPressed(KeyBindingPressEvent<ManiaAction> e)
|
||||||
{
|
{
|
||||||
if (AllJudged)
|
if (AllJudged)
|
||||||
|
@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
|
|||||||
/// <summary>
|
/// <summary>
|
||||||
/// Causes this <see cref="DrawableManiaHitObject"/> to get missed, disregarding all conditions in implementations of <see cref="DrawableHitObject.CheckForResult"/>.
|
/// Causes this <see cref="DrawableManiaHitObject"/> to get missed, disregarding all conditions in implementations of <see cref="DrawableHitObject.CheckForResult"/>.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
public void MissForcefully() => ApplyResult(r => r.Type = r.Judgement.MinResult);
|
public virtual void MissForcefully() => ApplyResult(r => r.Type = r.Judgement.MinResult);
|
||||||
}
|
}
|
||||||
|
|
||||||
public abstract class DrawableManiaHitObject<TObject> : DrawableManiaHitObject
|
public abstract class DrawableManiaHitObject<TObject> : DrawableManiaHitObject
|
||||||
|
@ -38,6 +38,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
},
|
},
|
||||||
new Container
|
new Container
|
||||||
{
|
{
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Height = 0.82f,
|
Height = 0.82f,
|
||||||
Masking = true,
|
Masking = true,
|
||||||
@ -54,6 +56,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Height = ArgonNotePiece.CORNER_RADIUS * 2,
|
Height = ArgonNotePiece.CORNER_RADIUS * 2,
|
||||||
|
Anchor = Anchor.BottomLeft,
|
||||||
|
Origin = Anchor.BottomLeft,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -26,8 +26,10 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
|||||||
};
|
};
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void DrumrollTest()
|
public void TestDrumroll([Values] bool withKiai)
|
||||||
{
|
{
|
||||||
|
AddStep("set up beatmap", () => setUpBeatmap(withKiai));
|
||||||
|
|
||||||
AddStep("Drum roll", () => SetContents(_ =>
|
AddStep("Drum roll", () => SetContents(_ =>
|
||||||
{
|
{
|
||||||
var hoc = new ScrollingHitObjectContainer();
|
var hoc = new ScrollingHitObjectContainer();
|
||||||
@ -73,5 +75,22 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
|||||||
|
|
||||||
return drumroll;
|
return drumroll;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setUpBeatmap(bool withKiai)
|
||||||
|
{
|
||||||
|
var controlPointInfo = new ControlPointInfo();
|
||||||
|
|
||||||
|
controlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
|
||||||
|
if (withKiai)
|
||||||
|
controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true });
|
||||||
|
|
||||||
|
Beatmap.Value = CreateWorkingBeatmap(new Beatmap
|
||||||
|
{
|
||||||
|
ControlPointInfo = controlPointInfo
|
||||||
|
});
|
||||||
|
|
||||||
|
Beatmap.Value.Track.Start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
// 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.Beatmaps.ControlPoints;
|
|
||||||
using osu.Game.Beatmaps;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public class TestSceneDrawableDrumRollKiai : TestSceneDrawableDrumRoll
|
|
||||||
{
|
|
||||||
[SetUp]
|
|
||||||
public void SetUp() => Schedule(() =>
|
|
||||||
{
|
|
||||||
var controlPointInfo = new ControlPointInfo();
|
|
||||||
|
|
||||||
controlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
|
|
||||||
controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true });
|
|
||||||
|
|
||||||
Beatmap.Value = CreateWorkingBeatmap(new Beatmap
|
|
||||||
{
|
|
||||||
ControlPointInfo = controlPointInfo
|
|
||||||
});
|
|
||||||
|
|
||||||
// track needs to be playing for BeatSyncedContainer to work.
|
|
||||||
Beatmap.Value.Track.Start();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,19 +4,51 @@
|
|||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Game.Beatmaps;
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
using osu.Game.Beatmaps.ControlPoints;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
using osu.Game.Rulesets.Taiko.Objects.Drawables;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
|
using osu.Game.Tests.Gameplay;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
||||||
{
|
{
|
||||||
[TestFixture]
|
[TestFixture]
|
||||||
public class TestSceneDrawableHit : TaikoSkinnableTestScene
|
public class TestSceneDrawableHit : TaikoSkinnableTestScene
|
||||||
{
|
{
|
||||||
|
[Cached]
|
||||||
|
private GameplayState gameplayState = TestGameplayState.Create(new TaikoRuleset());
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestHits()
|
public void TestHits([Values] bool withKiai)
|
||||||
|
{
|
||||||
|
AddStep("Create beatmap", () => setUpBeatmap(withKiai));
|
||||||
|
addHitSteps();
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHitAnimationSlow()
|
||||||
|
{
|
||||||
|
AddStep("Create beatmap", () => setUpBeatmap(false));
|
||||||
|
|
||||||
|
AddStep("Set 50 combo", () => gameplayState.ScoreProcessor.Combo.Value = 50);
|
||||||
|
addHitSteps();
|
||||||
|
AddStep("Reset combo", () => gameplayState.ScoreProcessor.Combo.Value = 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHitAnimationFast()
|
||||||
|
{
|
||||||
|
AddStep("Create beatmap", () => setUpBeatmap(false));
|
||||||
|
|
||||||
|
AddStep("Set 150 combo", () => gameplayState.ScoreProcessor.Combo.Value = 150);
|
||||||
|
addHitSteps();
|
||||||
|
AddStep("Reset combo", () => gameplayState.ScoreProcessor.Combo.Value = 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addHitSteps()
|
||||||
{
|
{
|
||||||
AddStep("Centre hit", () => SetContents(_ => new DrawableHit(createHitAtCurrentTime())
|
AddStep("Centre hit", () => SetContents(_ => new DrawableHit(createHitAtCurrentTime())
|
||||||
{
|
{
|
||||||
@ -56,5 +88,22 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
|||||||
|
|
||||||
return hit;
|
return hit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void setUpBeatmap(bool withKiai)
|
||||||
|
{
|
||||||
|
var controlPointInfo = new ControlPointInfo();
|
||||||
|
|
||||||
|
controlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
|
||||||
|
|
||||||
|
if (withKiai)
|
||||||
|
controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true });
|
||||||
|
|
||||||
|
Beatmap.Value = CreateWorkingBeatmap(new Beatmap
|
||||||
|
{
|
||||||
|
ControlPointInfo = controlPointInfo
|
||||||
|
});
|
||||||
|
|
||||||
|
Beatmap.Value.Track.Start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,30 +0,0 @@
|
|||||||
// 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.Beatmaps;
|
|
||||||
using osu.Game.Beatmaps.ControlPoints;
|
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Tests.Skinning
|
|
||||||
{
|
|
||||||
[TestFixture]
|
|
||||||
public class TestSceneDrawableHitKiai : TestSceneDrawableHit
|
|
||||||
{
|
|
||||||
[SetUp]
|
|
||||||
public void SetUp() => Schedule(() =>
|
|
||||||
{
|
|
||||||
var controlPointInfo = new ControlPointInfo();
|
|
||||||
|
|
||||||
controlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 });
|
|
||||||
controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true });
|
|
||||||
|
|
||||||
Beatmap.Value = CreateWorkingBeatmap(new Beatmap
|
|
||||||
{
|
|
||||||
ControlPointInfo = controlPointInfo
|
|
||||||
});
|
|
||||||
|
|
||||||
// track needs to be playing for BeatSyncedContainer to work.
|
|
||||||
Beatmap.Value.Track.Start();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
@ -6,10 +6,10 @@
|
|||||||
using System;
|
using System;
|
||||||
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Difficulty.Skills;
|
using osu.Game.Rulesets.Difficulty.Skills;
|
||||||
using osu.Game.Rulesets.Difficulty.Utils;
|
|
||||||
using osu.Game.Rulesets.Mods;
|
using osu.Game.Rulesets.Mods;
|
||||||
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
|
using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
namespace osu.Game.Rulesets.Taiko.Difficulty.Skills
|
||||||
{
|
{
|
||||||
|
@ -2,13 +2,16 @@
|
|||||||
// See the LICENCE file in the repository root for full licence text.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Animations;
|
using osu.Framework.Graphics.Animations;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Graphics.Primitives;
|
using osu.Framework.Graphics.Primitives;
|
||||||
|
using osu.Game.Beatmaps;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Rulesets.Objects.Drawables;
|
using osu.Game.Rulesets.Objects.Drawables;
|
||||||
using osu.Game.Rulesets.Taiko.Objects;
|
using osu.Game.Rulesets.Taiko.Objects;
|
||||||
|
using osu.Game.Screens.Play;
|
||||||
using osu.Game.Skinning;
|
using osu.Game.Skinning;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osuTK.Graphics;
|
using osuTK.Graphics;
|
||||||
@ -18,6 +21,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
public class LegacyCirclePiece : CompositeDrawable, IHasAccentColour
|
public class LegacyCirclePiece : CompositeDrawable, IHasAccentColour
|
||||||
{
|
{
|
||||||
private Drawable backgroundLayer = null!;
|
private Drawable backgroundLayer = null!;
|
||||||
|
private Drawable? foregroundLayer;
|
||||||
|
|
||||||
|
private Bindable<int> currentCombo { get; } = new BindableInt();
|
||||||
|
|
||||||
|
private int animationFrame;
|
||||||
|
private double beatLength;
|
||||||
|
|
||||||
// required for editor blueprints (not sure why these circle pieces are zero size).
|
// required for editor blueprints (not sure why these circle pieces are zero size).
|
||||||
public override Quad ScreenSpaceDrawQuad => backgroundLayer.ScreenSpaceDrawQuad;
|
public override Quad ScreenSpaceDrawQuad => backgroundLayer.ScreenSpaceDrawQuad;
|
||||||
@ -27,6 +36,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
RelativeSizeAxes = Axes.Both;
|
RelativeSizeAxes = Axes.Both;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private GameplayState? gameplayState { get; set; }
|
||||||
|
|
||||||
|
[Resolved(canBeNull: true)]
|
||||||
|
private IBeatSyncProvider? beatSyncProvider { get; set; }
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(ISkinSource skin, DrawableHitObject drawableHitObject)
|
private void load(ISkinSource skin, DrawableHitObject drawableHitObject)
|
||||||
{
|
{
|
||||||
@ -45,7 +60,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
// backgroundLayer is guaranteed to exist due to the pre-check in TaikoLegacySkinTransformer.
|
// backgroundLayer is guaranteed to exist due to the pre-check in TaikoLegacySkinTransformer.
|
||||||
AddInternal(backgroundLayer = new LegacyKiaiFlashingDrawable(() => getDrawableFor("circle")));
|
AddInternal(backgroundLayer = new LegacyKiaiFlashingDrawable(() => getDrawableFor("circle")));
|
||||||
|
|
||||||
var foregroundLayer = getDrawableFor("circleoverlay");
|
foregroundLayer = getDrawableFor("circleoverlay");
|
||||||
if (foregroundLayer != null)
|
if (foregroundLayer != null)
|
||||||
AddInternal(foregroundLayer);
|
AddInternal(foregroundLayer);
|
||||||
|
|
||||||
@ -58,6 +73,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
c.Anchor = Anchor.Centre;
|
c.Anchor = Anchor.Centre;
|
||||||
c.Origin = Anchor.Centre;
|
c.Origin = Anchor.Centre;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (gameplayState != null)
|
||||||
|
currentCombo.BindTo(gameplayState.ScoreProcessor.Combo);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
@ -74,6 +92,37 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy
|
|||||||
// This ensures they are scaled relative to each other but also match the expected DrawableHit size.
|
// This ensures they are scaled relative to each other but also match the expected DrawableHit size.
|
||||||
foreach (var c in InternalChildren)
|
foreach (var c in InternalChildren)
|
||||||
c.Scale = new Vector2(DrawHeight / 128);
|
c.Scale = new Vector2(DrawHeight / 128);
|
||||||
|
|
||||||
|
if (foregroundLayer is IFramedAnimation animatableForegroundLayer)
|
||||||
|
animateForegroundLayer(animatableForegroundLayer);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void animateForegroundLayer(IFramedAnimation animatableForegroundLayer)
|
||||||
|
{
|
||||||
|
int multiplier;
|
||||||
|
|
||||||
|
if (currentCombo.Value >= 150)
|
||||||
|
{
|
||||||
|
multiplier = 2;
|
||||||
|
}
|
||||||
|
else if (currentCombo.Value >= 50)
|
||||||
|
{
|
||||||
|
multiplier = 1;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
animatableForegroundLayer.GotoFrame(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (beatSyncProvider?.ControlPoints != null)
|
||||||
|
{
|
||||||
|
beatLength = beatSyncProvider.ControlPoints.TimingPointAt(Time.Current).BeatLength;
|
||||||
|
|
||||||
|
animationFrame = Time.Current % ((beatLength * 2) / multiplier) >= beatLength / multiplier ? 0 : 1;
|
||||||
|
|
||||||
|
animatableForegroundLayer.GotoFrame(animationFrame);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Color4 accentColour;
|
private Color4 accentColour;
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
using System;
|
using System;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Game.Rulesets.Difficulty.Utils;
|
using osu.Game.Utils;
|
||||||
|
|
||||||
namespace osu.Game.Tests.NonVisual
|
namespace osu.Game.Tests.NonVisual
|
||||||
{
|
{
|
||||||
|
@ -3,6 +3,7 @@
|
|||||||
|
|
||||||
#nullable disable
|
#nullable disable
|
||||||
|
|
||||||
|
using System;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
@ -32,6 +33,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
AddStep("values from 1-10", () => graph.Values = Enumerable.Range(1, 10).Select(i => (float)i));
|
AddStep("values from 1-10", () => graph.Values = Enumerable.Range(1, 10).Select(i => (float)i));
|
||||||
AddStep("values from 1-100", () => graph.Values = Enumerable.Range(1, 100).Select(i => (float)i));
|
AddStep("values from 1-100", () => graph.Values = Enumerable.Range(1, 100).Select(i => (float)i));
|
||||||
AddStep("reversed values from 1-10", () => graph.Values = Enumerable.Range(1, 10).Reverse().Select(i => (float)i));
|
AddStep("reversed values from 1-10", () => graph.Values = Enumerable.Range(1, 10).Reverse().Select(i => (float)i));
|
||||||
|
AddStep("empty values", () => graph.Values = Array.Empty<float>());
|
||||||
AddStep("Bottom to top", () => graph.Direction = BarDirection.BottomToTop);
|
AddStep("Bottom to top", () => graph.Direction = BarDirection.BottomToTop);
|
||||||
AddStep("Top to bottom", () => graph.Direction = BarDirection.TopToBottom);
|
AddStep("Top to bottom", () => graph.Direction = BarDirection.TopToBottom);
|
||||||
AddStep("Left to right", () => graph.Direction = BarDirection.LeftToRight);
|
AddStep("Left to right", () => graph.Direction = BarDirection.LeftToRight);
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
// 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.
|
// See the LICENCE file in the repository root for full licence text.
|
||||||
|
|
||||||
#nullable disable
|
using System.Linq;
|
||||||
|
|
||||||
using NUnit.Framework;
|
using NUnit.Framework;
|
||||||
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.Containers;
|
||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Graphics;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
using osu.Game.Online.API.Requests.Responses;
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Profile.Header.Components;
|
using osu.Game.Overlays.Profile.Header.Components;
|
||||||
@ -23,33 +24,14 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
[Cached]
|
[Cached]
|
||||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink);
|
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink);
|
||||||
|
|
||||||
public TestSceneRankGraph()
|
private RankGraph graph = null!;
|
||||||
|
|
||||||
|
private const int history_length = 89;
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
{
|
{
|
||||||
RankGraph graph;
|
AddStep("create graph", () => Child = new Container
|
||||||
|
|
||||||
int[] data = new int[89];
|
|
||||||
int[] dataWithZeros = new int[89];
|
|
||||||
int[] smallData = new int[89];
|
|
||||||
int[] edgyData = new int[89];
|
|
||||||
|
|
||||||
for (int i = 0; i < 89; i++)
|
|
||||||
data[i] = dataWithZeros[i] = (i + 1) * 1000;
|
|
||||||
|
|
||||||
for (int i = 20; i < 60; i++)
|
|
||||||
dataWithZeros[i] = 0;
|
|
||||||
|
|
||||||
for (int i = 79; i < 89; i++)
|
|
||||||
smallData[i] = 100000 - i * 1000;
|
|
||||||
|
|
||||||
bool edge = true;
|
|
||||||
|
|
||||||
for (int i = 0; i < 20; i++)
|
|
||||||
{
|
|
||||||
edgyData[i] = 100000 + (edge ? 1000 : -1000) * (i + 1);
|
|
||||||
edge = !edge;
|
|
||||||
}
|
|
||||||
|
|
||||||
Add(new Container
|
|
||||||
{
|
{
|
||||||
Anchor = Anchor.Centre,
|
Anchor = Anchor.Centre,
|
||||||
Origin = Anchor.Centre,
|
Origin = Anchor.Centre,
|
||||||
@ -67,34 +49,70 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestNullUser()
|
||||||
|
{
|
||||||
AddStep("null user", () => graph.Statistics.Value = null);
|
AddStep("null user", () => graph.Statistics.Value = null);
|
||||||
|
AddAssert("line graph hidden", () => this.ChildrenOfType<LineGraph>().All(graph => graph.Alpha == 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRankOnly()
|
||||||
|
{
|
||||||
AddStep("rank only", () =>
|
AddStep("rank only", () =>
|
||||||
{
|
{
|
||||||
graph.Statistics.Value = new UserStatistics
|
graph.Statistics.Value = new UserStatistics
|
||||||
{
|
{
|
||||||
|
IsRanked = true,
|
||||||
GlobalRank = 123456,
|
GlobalRank = 123456,
|
||||||
PP = 12345,
|
PP = 12345,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
AddAssert("line graph hidden", () => this.ChildrenOfType<LineGraph>().All(graph => graph.Alpha == 0));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestWithRankHistory()
|
||||||
|
{
|
||||||
|
int[] data = new int[history_length];
|
||||||
|
|
||||||
|
for (int i = 0; i < history_length; i++)
|
||||||
|
data[i] = (i + 1) * 1000;
|
||||||
|
|
||||||
AddStep("with rank history", () =>
|
AddStep("with rank history", () =>
|
||||||
{
|
{
|
||||||
graph.Statistics.Value = new UserStatistics
|
graph.Statistics.Value = new UserStatistics
|
||||||
{
|
{
|
||||||
|
IsRanked = true,
|
||||||
GlobalRank = 89000,
|
GlobalRank = 89000,
|
||||||
PP = 12345,
|
PP = 12345,
|
||||||
RankHistory = new APIRankHistory
|
RankHistory = new APIRankHistory
|
||||||
{
|
{
|
||||||
Data = data,
|
Data = data
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
AddAssert("line graph shown", () => this.ChildrenOfType<LineGraph>().All(graph => graph.Alpha == 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestRanksWithZeroValues()
|
||||||
|
{
|
||||||
|
int[] dataWithZeros = new int[history_length];
|
||||||
|
|
||||||
|
for (int i = 0; i < history_length; i++)
|
||||||
|
{
|
||||||
|
if (i < 20 || i >= 60)
|
||||||
|
dataWithZeros[i] = (i + 1) * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
AddStep("with zero values", () =>
|
AddStep("with zero values", () =>
|
||||||
{
|
{
|
||||||
graph.Statistics.Value = new UserStatistics
|
graph.Statistics.Value = new UserStatistics
|
||||||
{
|
{
|
||||||
|
IsRanked = true,
|
||||||
GlobalRank = 89000,
|
GlobalRank = 89000,
|
||||||
PP = 12345,
|
PP = 12345,
|
||||||
RankHistory = new APIRankHistory
|
RankHistory = new APIRankHistory
|
||||||
@ -103,11 +121,22 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
AddAssert("line graph shown", () => this.ChildrenOfType<LineGraph>().All(graph => graph.Alpha == 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSmallAmountOfData()
|
||||||
|
{
|
||||||
|
int[] smallData = new int[history_length];
|
||||||
|
|
||||||
|
for (int i = history_length - 10; i < history_length; i++)
|
||||||
|
smallData[i] = 100000 - i * 1000;
|
||||||
|
|
||||||
AddStep("small amount of data", () =>
|
AddStep("small amount of data", () =>
|
||||||
{
|
{
|
||||||
graph.Statistics.Value = new UserStatistics
|
graph.Statistics.Value = new UserStatistics
|
||||||
{
|
{
|
||||||
|
IsRanked = true,
|
||||||
GlobalRank = 12000,
|
GlobalRank = 12000,
|
||||||
PP = 12345,
|
PP = 12345,
|
||||||
RankHistory = new APIRankHistory
|
RankHistory = new APIRankHistory
|
||||||
@ -116,11 +145,27 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
AddAssert("line graph shown", () => this.ChildrenOfType<LineGraph>().All(graph => graph.Alpha == 1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHistoryWithEdges()
|
||||||
|
{
|
||||||
|
int[] edgyData = new int[89];
|
||||||
|
|
||||||
|
bool edge = true;
|
||||||
|
|
||||||
|
for (int i = 0; i < 20; i++)
|
||||||
|
{
|
||||||
|
edgyData[i] = 100000 + (edge ? 1000 : -1000) * (i + 1);
|
||||||
|
edge = !edge;
|
||||||
|
}
|
||||||
|
|
||||||
AddStep("graph with edges", () =>
|
AddStep("graph with edges", () =>
|
||||||
{
|
{
|
||||||
graph.Statistics.Value = new UserStatistics
|
graph.Statistics.Value = new UserStatistics
|
||||||
{
|
{
|
||||||
|
IsRanked = true,
|
||||||
GlobalRank = 12000,
|
GlobalRank = 12000,
|
||||||
PP = 12345,
|
PP = 12345,
|
||||||
RankHistory = new APIRankHistory
|
RankHistory = new APIRankHistory
|
||||||
@ -129,6 +174,7 @@ namespace osu.Game.Tests.Visual.Online
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
AddAssert("line graph shown", () => this.ChildrenOfType<LineGraph>().All(graph => graph.Alpha == 1));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -11,6 +11,7 @@ using osu.Framework.Graphics;
|
|||||||
using osu.Framework.Input.Handlers.Tablet;
|
using osu.Framework.Input.Handlers.Tablet;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Framework.Utils;
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using osu.Game.Overlays.Settings;
|
using osu.Game.Overlays.Settings;
|
||||||
using osu.Game.Overlays.Settings.Sections.Input;
|
using osu.Game.Overlays.Settings.Sections.Input;
|
||||||
@ -36,12 +37,16 @@ namespace osu.Game.Tests.Visual.Settings
|
|||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
settings = new TabletSettings(tabletHandler)
|
new OsuScrollContainer(Direction.Vertical)
|
||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.None,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Width = SettingsPanel.PANEL_WIDTH,
|
Child = settings = new TabletSettings(tabletHandler)
|
||||||
Anchor = Anchor.TopCentre,
|
{
|
||||||
Origin = Anchor.TopCentre,
|
RelativeSizeAxes = Axes.None,
|
||||||
|
Width = SettingsPanel.PANEL_WIDTH,
|
||||||
|
Anchor = Anchor.TopCentre,
|
||||||
|
Origin = Anchor.TopCentre,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
178
osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs
Normal file
178
osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs
Normal file
@ -0,0 +1,178 @@
|
|||||||
|
// 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.Framework.Graphics;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.UserInterface
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneHistoryTextBox : OsuManualInputManagerTestScene
|
||||||
|
{
|
||||||
|
private const string temp = "Temp message";
|
||||||
|
|
||||||
|
private int messageCounter;
|
||||||
|
|
||||||
|
private HistoryTextBox box = null!;
|
||||||
|
private OsuSpriteText text = null!;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp()
|
||||||
|
{
|
||||||
|
Schedule(() =>
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
box = new HistoryTextBox(5)
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Width = 0.99f,
|
||||||
|
},
|
||||||
|
text = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Width = 0.99f,
|
||||||
|
Y = -box.Height,
|
||||||
|
Font = OsuFont.Default.With(size: 20),
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
box.OnCommit += (_, _) =>
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(box.Text))
|
||||||
|
return;
|
||||||
|
|
||||||
|
text.Text = $"{nameof(box.OnCommit)}: {box.Text}";
|
||||||
|
box.Text = string.Empty;
|
||||||
|
box.TakeFocus();
|
||||||
|
text.FadeOutFromOne(1000, Easing.InQuint);
|
||||||
|
};
|
||||||
|
|
||||||
|
messageCounter = 0;
|
||||||
|
|
||||||
|
box.TakeFocus();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestEmptyHistory()
|
||||||
|
{
|
||||||
|
AddStep("Set text", () => box.Text = temp);
|
||||||
|
|
||||||
|
AddStep("Move down", () => InputManager.Key(Key.Down));
|
||||||
|
AddAssert("Text is unchanged", () => box.Text == temp);
|
||||||
|
|
||||||
|
AddStep("Move up", () => InputManager.Key(Key.Up));
|
||||||
|
AddAssert("Text is unchanged", () => box.Text == temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestPartialHistory()
|
||||||
|
{
|
||||||
|
addMessages(3);
|
||||||
|
AddStep("Set text", () => box.Text = temp);
|
||||||
|
|
||||||
|
AddStep("Move down", () => InputManager.Key(Key.Down));
|
||||||
|
AddAssert("Text is unchanged", () => box.Text == temp);
|
||||||
|
|
||||||
|
AddRepeatStep("Move up", () => InputManager.Key(Key.Up), 3);
|
||||||
|
AddAssert("Same as 1st message", () => box.Text == "Message 1");
|
||||||
|
|
||||||
|
AddStep("Move up", () => InputManager.Key(Key.Up));
|
||||||
|
AddAssert("Same as 1st message", () => box.Text == "Message 1");
|
||||||
|
|
||||||
|
AddStep("Move down", () => InputManager.Key(Key.Down));
|
||||||
|
AddAssert("Same as 2nd message", () => box.Text == "Message 2");
|
||||||
|
|
||||||
|
AddRepeatStep("Move down", () => InputManager.Key(Key.Down), 2);
|
||||||
|
AddAssert("Temporary message restored", () => box.Text == temp);
|
||||||
|
|
||||||
|
AddStep("Move down", () => InputManager.Key(Key.Down));
|
||||||
|
AddAssert("Text is unchanged", () => box.Text == temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestFullHistory()
|
||||||
|
{
|
||||||
|
addMessages(7);
|
||||||
|
AddStep("Set text", () => box.Text = temp);
|
||||||
|
|
||||||
|
AddStep("Move down", () => InputManager.Key(Key.Down));
|
||||||
|
AddAssert("Text is unchanged", () => box.Text == temp);
|
||||||
|
|
||||||
|
AddRepeatStep("Move up", () => InputManager.Key(Key.Up), 5);
|
||||||
|
AddAssert("Same as 3rd message", () => box.Text == "Message 3");
|
||||||
|
|
||||||
|
AddStep("Move up", () => InputManager.Key(Key.Up));
|
||||||
|
AddAssert("Same as 3rd message", () => box.Text == "Message 3");
|
||||||
|
|
||||||
|
AddRepeatStep("Move down", () => InputManager.Key(Key.Down), 4);
|
||||||
|
AddAssert("Same as 7th message", () => box.Text == "Message 7");
|
||||||
|
|
||||||
|
AddStep("Move down", () => InputManager.Key(Key.Down));
|
||||||
|
AddAssert("Temporary message restored", () => box.Text == temp);
|
||||||
|
|
||||||
|
AddStep("Move down", () => InputManager.Key(Key.Down));
|
||||||
|
AddAssert("Text is unchanged", () => box.Text == temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestChangedHistory()
|
||||||
|
{
|
||||||
|
addMessages(2);
|
||||||
|
AddStep("Set text", () => box.Text = temp);
|
||||||
|
AddStep("Move up", () => InputManager.Key(Key.Up));
|
||||||
|
|
||||||
|
AddStep("Change text", () => box.Text = "New message");
|
||||||
|
AddStep("Move down", () => InputManager.Key(Key.Down));
|
||||||
|
AddStep("Move up", () => InputManager.Key(Key.Up));
|
||||||
|
AddAssert("Changes lost", () => box.Text == "Message 2");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestInputOnEdge()
|
||||||
|
{
|
||||||
|
addMessages(2);
|
||||||
|
AddStep("Set text", () => box.Text = temp);
|
||||||
|
|
||||||
|
AddStep("Move down", () => InputManager.Key(Key.Down));
|
||||||
|
AddAssert("Text unchanged", () => box.Text == temp);
|
||||||
|
|
||||||
|
AddRepeatStep("Move up", () => InputManager.Key(Key.Up), 2);
|
||||||
|
AddAssert("Same as 1st message", () => box.Text == "Message 1");
|
||||||
|
|
||||||
|
AddStep("Move up", () => InputManager.Key(Key.Up));
|
||||||
|
AddAssert("Text unchanged", () => box.Text == "Message 1");
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestResetIndex()
|
||||||
|
{
|
||||||
|
addMessages(2);
|
||||||
|
|
||||||
|
AddRepeatStep("Move Up", () => InputManager.Key(Key.Up), 2);
|
||||||
|
AddAssert("Same as 1st message", () => box.Text == "Message 1");
|
||||||
|
|
||||||
|
AddStep("Change text", () => box.Text = "New message");
|
||||||
|
AddStep("Move Up", () => InputManager.Key(Key.Up));
|
||||||
|
AddAssert("Same as previous message", () => box.Text == "Message 2");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addMessages(int count)
|
||||||
|
{
|
||||||
|
AddRepeatStep("Add messages", () =>
|
||||||
|
{
|
||||||
|
box.Text = $"Message {++messageCounter}";
|
||||||
|
InputManager.Key(Key.Enter);
|
||||||
|
}, count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -136,7 +136,9 @@ namespace osu.Game.Beatmaps.Drawables
|
|||||||
private static readonly string[] always_bundled_beatmaps =
|
private static readonly string[] always_bundled_beatmaps =
|
||||||
{
|
{
|
||||||
// This thing is 40mb, I'm not sure we want it here...
|
// This thing is 40mb, I'm not sure we want it here...
|
||||||
@"1388906 Raphlesia & BilliumMoto - My Love.osz"
|
@"1388906 Raphlesia & BilliumMoto - My Love.osz",
|
||||||
|
// Winner of Triangles mapping competition: https://osu.ppy.sh/home/news/2022-10-06-results-triangles
|
||||||
|
@"1841885 cYsmix - triangles.osz",
|
||||||
};
|
};
|
||||||
|
|
||||||
private static readonly string[] bundled_osu =
|
private static readonly string[] bundled_osu =
|
||||||
|
@ -147,7 +147,11 @@ namespace osu.Game.Beatmaps.Formats
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected string CleanFilename(string path) => path.Trim('"').ToStandardisedPath();
|
protected string CleanFilename(string path) => path
|
||||||
|
// User error which is supported by stable (https://github.com/ppy/osu/issues/21204)
|
||||||
|
.Replace(@"\\", @"\")
|
||||||
|
.Trim('"')
|
||||||
|
.ToStandardisedPath();
|
||||||
|
|
||||||
public enum Section
|
public enum Section
|
||||||
{
|
{
|
||||||
|
@ -109,15 +109,11 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[Flags]
|
|
||||||
public enum BarDirection
|
public enum BarDirection
|
||||||
{
|
{
|
||||||
LeftToRight = 1,
|
LeftToRight,
|
||||||
RightToLeft = 1 << 1,
|
RightToLeft,
|
||||||
TopToBottom = 1 << 2,
|
TopToBottom,
|
||||||
BottomToTop = 1 << 3,
|
BottomToTop
|
||||||
|
|
||||||
Vertical = TopToBottom | BottomToTop,
|
|
||||||
Horizontal = LeftToRight | RightToLeft,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,15 +5,23 @@
|
|||||||
|
|
||||||
using osuTK;
|
using osuTK;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Containers;
|
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using osu.Framework.Extensions.EnumExtensions;
|
using osu.Framework.Graphics.Rendering;
|
||||||
|
using osu.Framework.Graphics.Shaders;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Framework.Graphics.Primitives;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using System;
|
||||||
|
|
||||||
namespace osu.Game.Graphics.UserInterface
|
namespace osu.Game.Graphics.UserInterface
|
||||||
{
|
{
|
||||||
public class BarGraph : FillFlowContainer<Bar>
|
public class BarGraph : Drawable
|
||||||
{
|
{
|
||||||
|
private const int resize_duration = 250;
|
||||||
|
private const Easing easing = Easing.InOutCubic;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Manually sets the max value, if null <see cref="Enumerable.Max(IEnumerable{float})"/> is instead used
|
/// Manually sets the max value, if null <see cref="Enumerable.Max(IEnumerable{float})"/> is instead used
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -21,22 +29,21 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
private BarDirection direction = BarDirection.BottomToTop;
|
private BarDirection direction = BarDirection.BottomToTop;
|
||||||
|
|
||||||
public new BarDirection Direction
|
public BarDirection Direction
|
||||||
{
|
{
|
||||||
get => direction;
|
get => direction;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
direction = value;
|
if (direction == value)
|
||||||
base.Direction = direction.HasFlagFast(BarDirection.Horizontal) ? FillDirection.Vertical : FillDirection.Horizontal;
|
return;
|
||||||
|
|
||||||
foreach (var bar in Children)
|
direction = value;
|
||||||
{
|
Invalidate(Invalidation.DrawNode);
|
||||||
bar.Size = direction.HasFlagFast(BarDirection.Horizontal) ? new Vector2(1, 1.0f / Children.Count) : new Vector2(1.0f / Children.Count, 1);
|
|
||||||
bar.Direction = direction;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private readonly BarsInfo bars = new BarsInfo();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// A list of floats that defines the length of each <see cref="Bar"/>
|
/// A list of floats that defines the length of each <see cref="Bar"/>
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -44,37 +51,199 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
{
|
{
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
List<Bar> bars = Children.ToList();
|
if (!value.Any())
|
||||||
|
|
||||||
foreach (var bar in value.Select((length, index) => new { Value = length, Bar = bars.Count > index ? bars[index] : null }))
|
|
||||||
{
|
{
|
||||||
float length = MaxValue ?? value.Max();
|
bars.Clear();
|
||||||
if (length != 0)
|
Invalidate(Invalidation.DrawNode);
|
||||||
length = bar.Value / length;
|
return;
|
||||||
|
|
||||||
float size = value.Count();
|
|
||||||
if (size != 0)
|
|
||||||
size = 1.0f / size;
|
|
||||||
|
|
||||||
if (bar.Bar != null)
|
|
||||||
{
|
|
||||||
bar.Bar.Length = length;
|
|
||||||
bar.Bar.Size = direction.HasFlagFast(BarDirection.Horizontal) ? new Vector2(1, size) : new Vector2(size, 1);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
Add(new Bar
|
|
||||||
{
|
|
||||||
RelativeSizeAxes = Axes.Both,
|
|
||||||
Size = direction.HasFlagFast(BarDirection.Horizontal) ? new Vector2(1, size) : new Vector2(size, 1),
|
|
||||||
Length = length,
|
|
||||||
Direction = Direction,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//I'm using ToList() here because Where() returns an Enumerable which can change it's elements afterwards
|
float maxLength = MaxValue ?? value.Max();
|
||||||
RemoveRange(Children.Where((_, index) => index >= value.Count()).ToList(), true);
|
|
||||||
|
bars.SetLengths(value.Select(v => maxLength == 0 ? 0 : Math.Max(0f, v / maxLength)).ToArray());
|
||||||
|
|
||||||
|
animationStartTime = Clock.CurrentTime;
|
||||||
|
animationComplete = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private double animationStartTime;
|
||||||
|
private bool animationComplete;
|
||||||
|
|
||||||
|
private IShader shader = null!;
|
||||||
|
private Texture texture = null!;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(IRenderer renderer, ShaderManager shaders)
|
||||||
|
{
|
||||||
|
texture = renderer.WhitePixel;
|
||||||
|
shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (!bars.Any)
|
||||||
|
return;
|
||||||
|
|
||||||
|
double currentTime = Clock.CurrentTime;
|
||||||
|
|
||||||
|
if (currentTime < animationStartTime + resize_duration)
|
||||||
|
{
|
||||||
|
bars.Animate(animationStartTime, currentTime);
|
||||||
|
Invalidate(Invalidation.DrawNode);
|
||||||
|
}
|
||||||
|
else if (!animationComplete)
|
||||||
|
{
|
||||||
|
bars.FinishAnimation();
|
||||||
|
Invalidate(Invalidation.DrawNode);
|
||||||
|
|
||||||
|
animationComplete = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override DrawNode CreateDrawNode() => new BarGraphDrawNode(this);
|
||||||
|
|
||||||
|
private class BarGraphDrawNode : DrawNode
|
||||||
|
{
|
||||||
|
public new BarGraph Source => (BarGraph)base.Source;
|
||||||
|
|
||||||
|
public BarGraphDrawNode(BarGraph source)
|
||||||
|
: base(source)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
private IShader shader = null!;
|
||||||
|
private Texture texture = null!;
|
||||||
|
private Vector2 drawSize;
|
||||||
|
private BarDirection direction;
|
||||||
|
private float barBreadth;
|
||||||
|
|
||||||
|
private readonly List<float> lengths = new List<float>();
|
||||||
|
|
||||||
|
public override void ApplyState()
|
||||||
|
{
|
||||||
|
base.ApplyState();
|
||||||
|
|
||||||
|
shader = Source.shader;
|
||||||
|
texture = Source.texture;
|
||||||
|
drawSize = Source.DrawSize;
|
||||||
|
direction = Source.direction;
|
||||||
|
barBreadth = Source.bars.Breadth;
|
||||||
|
|
||||||
|
lengths.Clear();
|
||||||
|
lengths.AddRange(Source.bars.InstantaneousLengths);
|
||||||
|
}
|
||||||
|
|
||||||
|
public override void Draw(IRenderer renderer)
|
||||||
|
{
|
||||||
|
base.Draw(renderer);
|
||||||
|
|
||||||
|
shader.Bind();
|
||||||
|
|
||||||
|
for (int i = 0; i < lengths.Count; i++)
|
||||||
|
{
|
||||||
|
float barHeight = drawSize.Y * ((direction == BarDirection.TopToBottom || direction == BarDirection.BottomToTop) ? lengths[i] : barBreadth);
|
||||||
|
float barWidth = drawSize.X * ((direction == BarDirection.LeftToRight || direction == BarDirection.RightToLeft) ? lengths[i] : barBreadth);
|
||||||
|
|
||||||
|
Vector2 topLeft;
|
||||||
|
|
||||||
|
switch (direction)
|
||||||
|
{
|
||||||
|
default:
|
||||||
|
case BarDirection.LeftToRight:
|
||||||
|
topLeft = new Vector2(0, i * barHeight);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BarDirection.RightToLeft:
|
||||||
|
topLeft = new Vector2(drawSize.X - barWidth, i * barHeight);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BarDirection.TopToBottom:
|
||||||
|
topLeft = new Vector2(i * barWidth, 0);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case BarDirection.BottomToTop:
|
||||||
|
topLeft = new Vector2(i * barWidth, drawSize.Y - barHeight);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
renderer.DrawQuad(
|
||||||
|
texture,
|
||||||
|
new Quad(
|
||||||
|
Vector2Extensions.Transform(topLeft, DrawInfo.Matrix),
|
||||||
|
Vector2Extensions.Transform(topLeft + new Vector2(barWidth, 0), DrawInfo.Matrix),
|
||||||
|
Vector2Extensions.Transform(topLeft + new Vector2(0, barHeight), DrawInfo.Matrix),
|
||||||
|
Vector2Extensions.Transform(topLeft + new Vector2(barWidth, barHeight), DrawInfo.Matrix)
|
||||||
|
),
|
||||||
|
DrawColourInfo.Colour);
|
||||||
|
}
|
||||||
|
|
||||||
|
shader.Unbind();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class BarsInfo
|
||||||
|
{
|
||||||
|
public bool Any => Count > 0;
|
||||||
|
|
||||||
|
public int Count { get; private set; }
|
||||||
|
|
||||||
|
public float Breadth { get; private set; }
|
||||||
|
|
||||||
|
public List<float> InstantaneousLengths { get; } = new List<float>();
|
||||||
|
|
||||||
|
private readonly List<float> initialLengths = new List<float>();
|
||||||
|
private readonly List<float> finalLengths = new List<float>();
|
||||||
|
|
||||||
|
public void Clear() => SetLengths(Array.Empty<float>());
|
||||||
|
|
||||||
|
public void SetLengths(float[] newLengths)
|
||||||
|
{
|
||||||
|
int newCount = newLengths.Length;
|
||||||
|
|
||||||
|
for (int i = 0; i < newCount; i++)
|
||||||
|
{
|
||||||
|
// If we have an old bar at this index - change it's length
|
||||||
|
if (i < Count)
|
||||||
|
{
|
||||||
|
initialLengths[i] = finalLengths[i];
|
||||||
|
finalLengths[i] = newLengths[i];
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If exceeded old bars count - add new one
|
||||||
|
initialLengths.Add(0);
|
||||||
|
finalLengths.Add(newLengths[i]);
|
||||||
|
InstantaneousLengths.Add(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove excessive bars
|
||||||
|
if (Count > newCount)
|
||||||
|
{
|
||||||
|
int barsToRemove = Count - newCount;
|
||||||
|
|
||||||
|
initialLengths.RemoveRange(newCount, barsToRemove);
|
||||||
|
finalLengths.RemoveRange(newCount, barsToRemove);
|
||||||
|
InstantaneousLengths.RemoveRange(newCount, barsToRemove);
|
||||||
|
}
|
||||||
|
|
||||||
|
Count = newCount;
|
||||||
|
Breadth = Count == 0 ? 0 : (1f / Count);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void Animate(double animationStartTime, double currentTime)
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Count; i++)
|
||||||
|
InstantaneousLengths[i] = Interpolation.ValueAt(currentTime, initialLengths[i], finalLengths[i], animationStartTime, animationStartTime + resize_duration, easing);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void FinishAnimation()
|
||||||
|
{
|
||||||
|
for (int i = 0; i < Count; i++)
|
||||||
|
InstantaneousLengths[i] = finalLengths[i];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
90
osu.Game/Graphics/UserInterface/HistoryTextBox.cs
Normal file
90
osu.Game/Graphics/UserInterface/HistoryTextBox.cs
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
// 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.Input.Events;
|
||||||
|
using osu.Game.Utils;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Graphics.UserInterface
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="FocusedTextBox"/> which additionally retains a history of text committed, up to a limit
|
||||||
|
/// (100 by default, specified in constructor).
|
||||||
|
/// The history of committed text can be navigated using up/down arrows.
|
||||||
|
/// This resembles the operation of command-line terminals.
|
||||||
|
/// </summary>
|
||||||
|
public class HistoryTextBox : FocusedTextBox
|
||||||
|
{
|
||||||
|
private readonly LimitedCapacityQueue<string> messageHistory;
|
||||||
|
|
||||||
|
public int HistoryCount => messageHistory.Count;
|
||||||
|
|
||||||
|
private int selectedIndex;
|
||||||
|
|
||||||
|
private string originalMessage = string.Empty;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Creates a new <see cref="HistoryTextBox"/>.
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="capacity">
|
||||||
|
/// The maximum number of committed lines to keep in history.
|
||||||
|
/// When exceeded, the oldest lines in history will be dropped to make space for new ones.
|
||||||
|
/// </param>
|
||||||
|
public HistoryTextBox(int capacity = 100)
|
||||||
|
{
|
||||||
|
messageHistory = new LimitedCapacityQueue<string>(capacity);
|
||||||
|
|
||||||
|
Current.ValueChanged += text =>
|
||||||
|
{
|
||||||
|
if (selectedIndex != HistoryCount && text.NewValue != messageHistory[selectedIndex])
|
||||||
|
{
|
||||||
|
selectedIndex = HistoryCount;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
|
{
|
||||||
|
switch (e.Key)
|
||||||
|
{
|
||||||
|
case Key.Up:
|
||||||
|
if (selectedIndex == 0)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (selectedIndex == HistoryCount)
|
||||||
|
originalMessage = Text;
|
||||||
|
|
||||||
|
Text = messageHistory[--selectedIndex];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case Key.Down:
|
||||||
|
if (selectedIndex == HistoryCount)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
if (selectedIndex == HistoryCount - 1)
|
||||||
|
{
|
||||||
|
selectedIndex = HistoryCount;
|
||||||
|
Text = originalMessage;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
Text = messageHistory[++selectedIndex];
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return base.OnKeyDown(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void Commit()
|
||||||
|
{
|
||||||
|
if (!string.IsNullOrEmpty(Text))
|
||||||
|
messageHistory.Enqueue(Text);
|
||||||
|
|
||||||
|
selectedIndex = HistoryCount;
|
||||||
|
|
||||||
|
base.Commit();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -120,17 +120,20 @@ namespace osu.Game.Online.Chat
|
|||||||
AddInternal(drawableChannel);
|
AddInternal(drawableChannel);
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ChatTextBox : FocusedTextBox
|
public class ChatTextBox : HistoryTextBox
|
||||||
{
|
{
|
||||||
protected override bool OnKeyDown(KeyDownEvent e)
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
{
|
{
|
||||||
// Chat text boxes are generally used in places where they retain focus, but shouldn't block interaction with other
|
// Chat text boxes are generally used in places where they retain focus, but shouldn't block interaction with other
|
||||||
// elements on the same screen.
|
// elements on the same screen.
|
||||||
switch (e.Key)
|
if (!HoldFocus)
|
||||||
{
|
{
|
||||||
case Key.Up:
|
switch (e.Key)
|
||||||
case Key.Down:
|
{
|
||||||
return false;
|
case Key.Up:
|
||||||
|
case Key.Down:
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return base.OnKeyDown(e);
|
return base.OnKeyDown(e);
|
||||||
|
@ -7,7 +7,7 @@ using osu.Game.Resources.Localisation.Web;
|
|||||||
|
|
||||||
namespace osu.Game.Overlays.Chat
|
namespace osu.Game.Overlays.Chat
|
||||||
{
|
{
|
||||||
public class ChatTextBox : FocusedTextBox
|
public class ChatTextBox : HistoryTextBox
|
||||||
{
|
{
|
||||||
public readonly BindableBool ShowSearch = new BindableBool();
|
public readonly BindableBool ShowSearch = new BindableBool();
|
||||||
|
|
||||||
|
@ -59,7 +59,11 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
|
|||||||
// the dropdown. BASS does not give us a simple mechanism to select
|
// the dropdown. BASS does not give us a simple mechanism to select
|
||||||
// specific audio devices in such a case anyways. Such
|
// specific audio devices in such a case anyways. Such
|
||||||
// functionality would require involved OS-specific code.
|
// functionality would require involved OS-specific code.
|
||||||
dropdown.Items = deviceItems.Distinct().ToList();
|
dropdown.Items = deviceItems
|
||||||
|
// Dropdown doesn't like null items. Somehow we are seeing some arrive here (see https://github.com/ppy/osu/issues/21271)
|
||||||
|
.Where(i => i != null)
|
||||||
|
.Distinct()
|
||||||
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void Dispose(bool isDisposing)
|
protected override void Dispose(bool isDisposing)
|
||||||
|
@ -45,9 +45,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input
|
|||||||
private GameHost host { get; set; }
|
private GameHost host { get; set; }
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Based on ultrawide monitor configurations.
|
/// Based on ultrawide monitor configurations, plus a bit of lenience for users which are intentionally aiming for higher horizontal velocity.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
private const float largest_feasible_aspect_ratio = 21f / 9;
|
private const float largest_feasible_aspect_ratio = 23f / 9;
|
||||||
|
|
||||||
private readonly BindableNumber<float> aspectRatio = new BindableFloat(1)
|
private readonly BindableNumber<float> aspectRatio = new BindableFloat(1)
|
||||||
{
|
{
|
||||||
|
@ -296,6 +296,13 @@ namespace osu.Game.Scoring
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
case HitResult.LargeBonus:
|
||||||
|
case HitResult.SmallBonus:
|
||||||
|
if (MaximumStatistics.TryGetValue(r.result, out int count) && count > 0)
|
||||||
|
yield return new HitResultDisplayStatistic(r.result, value, null, r.displayName);
|
||||||
|
|
||||||
|
break;
|
||||||
|
|
||||||
case HitResult.SmallTickMiss:
|
case HitResult.SmallTickMiss:
|
||||||
case HitResult.LargeTickMiss:
|
case HitResult.LargeTickMiss:
|
||||||
break;
|
break;
|
||||||
|
@ -304,6 +304,16 @@ namespace osu.Game.Screens.Select
|
|||||||
modSelectOverlayRegistration = OverlayManager?.RegisterBlockingOverlay(ModSelect);
|
modSelectOverlayRegistration = OverlayManager?.RegisterBlockingOverlay(ModSelect);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected override bool OnScroll(ScrollEvent e)
|
||||||
|
{
|
||||||
|
// Match stable behaviour of only alt-scroll adjusting volume.
|
||||||
|
// Supporting scroll adjust without a modifier key just feels bad, since there are so many scrollable elements on the screen.
|
||||||
|
if (!e.CurrentState.Keyboard.AltPressed)
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return base.OnScroll(e);
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates the buttons to be displayed in the footer.
|
/// Creates the buttons to be displayed in the footer.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
@ -7,7 +7,7 @@ using System;
|
|||||||
using System.Collections;
|
using System.Collections;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
|
||||||
namespace osu.Game.Rulesets.Difficulty.Utils
|
namespace osu.Game.Utils
|
||||||
{
|
{
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// An indexed queue with limited capacity.
|
/// An indexed queue with limited capacity.
|
Loading…
Reference in New Issue
Block a user