1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-11 15:27:20 +08:00

Merge branch 'master' into fix-editor-save-failure

This commit is contained in:
Bartłomiej Dach 2023-06-28 19:56:58 +02:00
commit e1600b3d72
No known key found for this signature in database
46 changed files with 610 additions and 378 deletions

View File

@ -49,6 +49,9 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep("Create tests", () => AddStep("Create tests", () =>
{ {
InputTrigger triggerLeft;
InputTrigger triggerRight;
Children = new Drawable[] Children = new Drawable[]
{ {
osuInputManager = new OsuInputManager(new OsuRuleset().RulesetInfo) osuInputManager = new OsuInputManager(new OsuRuleset().RulesetInfo)
@ -59,29 +62,39 @@ namespace osu.Game.Rulesets.Osu.Tests
Origin = Anchor.Centre, Origin = Anchor.Centre,
Children = new Drawable[] Children = new Drawable[]
{ {
leftKeyCounter = new DefaultKeyCounter(new TestActionKeyCounterTrigger(OsuAction.LeftButton))
{
Anchor = Anchor.Centre,
Origin = Anchor.CentreRight,
Depth = float.MinValue,
X = -100,
},
rightKeyCounter = new DefaultKeyCounter(new TestActionKeyCounterTrigger(OsuAction.RightButton))
{
Anchor = Anchor.Centre,
Origin = Anchor.CentreLeft,
Depth = float.MinValue,
X = 100,
},
new OsuCursorContainer new OsuCursorContainer
{ {
Depth = float.MinValue, Depth = float.MinValue,
},
triggerLeft = new TestActionKeyCounterTrigger(OsuAction.LeftButton)
{
Depth = float.MinValue
},
triggerRight = new TestActionKeyCounterTrigger(OsuAction.RightButton)
{
Depth = float.MinValue
} }
}, },
} },
}, },
new TouchVisualiser(), new TouchVisualiser(),
}; };
mainContent.AddRange(new[]
{
leftKeyCounter = new DefaultKeyCounter(triggerLeft)
{
Anchor = Anchor.Centre,
Origin = Anchor.CentreRight,
X = -100,
},
rightKeyCounter = new DefaultKeyCounter(triggerRight)
{
Anchor = Anchor.Centre,
Origin = Anchor.CentreLeft,
X = 100,
},
});
}); });
} }

View File

@ -33,7 +33,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
Artist = "The Artist", Artist = "The Artist",
ArtistUnicode = "check unicode too", ArtistUnicode = "check unicode too",
Title = "Title goes here", Title = "Title goes here",
TitleUnicode = "Title goes here", TitleUnicode = "TitleUnicode goes here",
Author = { Username = "The Author" }, Author = { Username = "The Author" },
Source = "unit tests", Source = "unit tests",
Tags = "look for tags too", Tags = "look for tags too",
@ -169,6 +169,9 @@ namespace osu.Game.Tests.NonVisual.Filtering
[TestCase("\"tags to\"", true)] [TestCase("\"tags to\"", true)]
[TestCase("\"version\"", false)] [TestCase("\"version\"", false)]
[TestCase("\"an auteur\"", true)] [TestCase("\"an auteur\"", true)]
[TestCase("\"Artist\"!", true)]
[TestCase("\"The Artist\"!", false)]
[TestCase("\"the artist\"!", false)]
[TestCase("\"\\\"", true)] // nasty case, covers properly escaping user input in underlying regex. [TestCase("\"\\\"", true)] // nasty case, covers properly escaping user input in underlying regex.
public void TestCriteriaMatchingExactTerms(string terms, bool filtered) public void TestCriteriaMatchingExactTerms(string terms, bool filtered)
{ {
@ -204,6 +207,27 @@ namespace osu.Game.Tests.NonVisual.Filtering
Assert.AreEqual(filtered, carouselItem.Filtered.Value); Assert.AreEqual(filtered, carouselItem.Filtered.Value);
} }
[Test]
[TestCase("", false)]
[TestCase("Goes", false)]
[TestCase("GOES", false)]
[TestCase("goes", false)]
[TestCase("title goes", false)]
[TestCase("title goes AND then something else", true)]
[TestCase("titleunicode", false)]
[TestCase("unknown", true)]
public void TestCriteriaMatchingTitle(string titleName, bool filtered)
{
var exampleBeatmapInfo = getExampleBeatmap();
var criteria = new FilterCriteria
{
Title = new FilterCriteria.OptionalTextFilter { SearchTerm = titleName }
};
var carouselItem = new CarouselBeatmap(exampleBeatmapInfo);
carouselItem.Filter(criteria);
Assert.AreEqual(filtered, carouselItem.Filtered.Value);
}
[Test] [Test]
[TestCase("", false)] [TestCase("", false)]
[TestCase("The", false)] [TestCase("The", false)]
@ -213,6 +237,9 @@ namespace osu.Game.Tests.NonVisual.Filtering
[TestCase("the artist AND then something else", true)] [TestCase("the artist AND then something else", true)]
[TestCase("unicode too", false)] [TestCase("unicode too", false)]
[TestCase("unknown", true)] [TestCase("unknown", true)]
[TestCase("\"Artist\"!", true)]
[TestCase("\"The Artist\"!", false)]
[TestCase("\"the artist\"!", false)]
public void TestCriteriaMatchingArtist(string artistName, bool filtered) public void TestCriteriaMatchingArtist(string artistName, bool filtered)
{ {
var exampleBeatmapInfo = getExampleBeatmap(); var exampleBeatmapInfo = getExampleBeatmap();

View File

@ -26,26 +26,58 @@ namespace osu.Game.Tests.NonVisual.Filtering
[Test] [Test]
public void TestApplyQueriesBareWordsWithExactMatch() public void TestApplyQueriesBareWordsWithExactMatch()
{ {
const string query = "looking for \"a beatmap\" like \"this\""; const string query = "looking for \"a beatmap\"! like \"this\"";
var filterCriteria = new FilterCriteria(); var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query); FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual("looking for \"a beatmap\" like \"this\"", filterCriteria.SearchText); Assert.AreEqual("looking for \"a beatmap\"! like \"this\"", filterCriteria.SearchText);
Assert.AreEqual(5, filterCriteria.SearchTerms.Length); Assert.AreEqual(5, filterCriteria.SearchTerms.Length);
Assert.That(filterCriteria.SearchTerms[0].SearchTerm, Is.EqualTo("a beatmap")); Assert.That(filterCriteria.SearchTerms[0].SearchTerm, Is.EqualTo("a beatmap"));
Assert.That(filterCriteria.SearchTerms[0].Exact, Is.True); Assert.That(filterCriteria.SearchTerms[0].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.FullPhrase));
Assert.That(filterCriteria.SearchTerms[1].SearchTerm, Is.EqualTo("this")); Assert.That(filterCriteria.SearchTerms[1].SearchTerm, Is.EqualTo("this"));
Assert.That(filterCriteria.SearchTerms[1].Exact, Is.True); Assert.That(filterCriteria.SearchTerms[1].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.IsolatedPhrase));
Assert.That(filterCriteria.SearchTerms[2].SearchTerm, Is.EqualTo("looking")); Assert.That(filterCriteria.SearchTerms[2].SearchTerm, Is.EqualTo("looking"));
Assert.That(filterCriteria.SearchTerms[2].Exact, Is.False); Assert.That(filterCriteria.SearchTerms[2].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.Substring));
Assert.That(filterCriteria.SearchTerms[3].SearchTerm, Is.EqualTo("for")); Assert.That(filterCriteria.SearchTerms[3].SearchTerm, Is.EqualTo("for"));
Assert.That(filterCriteria.SearchTerms[3].Exact, Is.False); Assert.That(filterCriteria.SearchTerms[3].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.Substring));
Assert.That(filterCriteria.SearchTerms[4].SearchTerm, Is.EqualTo("like")); Assert.That(filterCriteria.SearchTerms[4].SearchTerm, Is.EqualTo("like"));
Assert.That(filterCriteria.SearchTerms[4].Exact, Is.False); Assert.That(filterCriteria.SearchTerms[4].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.Substring));
}
[Test]
public void TestApplyFullPhraseQueryWithExclamationPointInTerm()
{
const string query = "looking for \"circles!\"!";
var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual("looking for \"circles!\"!", filterCriteria.SearchText);
Assert.AreEqual(3, filterCriteria.SearchTerms.Length);
Assert.That(filterCriteria.SearchTerms[0].SearchTerm, Is.EqualTo("circles!"));
Assert.That(filterCriteria.SearchTerms[0].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.FullPhrase));
Assert.That(filterCriteria.SearchTerms[1].SearchTerm, Is.EqualTo("looking"));
Assert.That(filterCriteria.SearchTerms[1].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.Substring));
Assert.That(filterCriteria.SearchTerms[2].SearchTerm, Is.EqualTo("for"));
Assert.That(filterCriteria.SearchTerms[2].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.Substring));
}
[Test]
public void TestApplyBrokenFullPhraseQuery()
{
const string query = "\"!";
var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual("\"!", filterCriteria.SearchText);
Assert.AreEqual(1, filterCriteria.SearchTerms.Length);
Assert.That(filterCriteria.SearchTerms[0].SearchTerm, Is.EqualTo("!"));
Assert.That(filterCriteria.SearchTerms[0].MatchMode, Is.EqualTo(FilterCriteria.MatchMode.IsolatedPhrase));
} }
/* /*
@ -251,6 +283,18 @@ namespace osu.Game.Tests.NonVisual.Filtering
Assert.AreEqual("my_fav", filterCriteria.Creator.SearchTerm); Assert.AreEqual("my_fav", filterCriteria.Creator.SearchTerm);
} }
[Test]
public void TestApplyTitleQueries()
{
const string query = "find me songs with title=\"a certain title\" please";
var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.AreEqual("find me songs with please", filterCriteria.SearchText.Trim());
Assert.AreEqual(5, filterCriteria.SearchTerms.Length);
Assert.AreEqual("a certain title", filterCriteria.Title.SearchTerm);
Assert.That(filterCriteria.Title.MatchMode, Is.EqualTo(FilterCriteria.MatchMode.IsolatedPhrase));
}
[Test] [Test]
public void TestApplyArtistQueries() public void TestApplyArtistQueries()
{ {
@ -260,7 +304,7 @@ namespace osu.Game.Tests.NonVisual.Filtering
Assert.AreEqual("find me songs by please", filterCriteria.SearchText.Trim()); Assert.AreEqual("find me songs by please", filterCriteria.SearchText.Trim());
Assert.AreEqual(5, filterCriteria.SearchTerms.Length); Assert.AreEqual(5, filterCriteria.SearchTerms.Length);
Assert.AreEqual("singer", filterCriteria.Artist.SearchTerm); Assert.AreEqual("singer", filterCriteria.Artist.SearchTerm);
Assert.That(filterCriteria.Artist.Exact, Is.False); Assert.That(filterCriteria.Artist.MatchMode, Is.EqualTo(FilterCriteria.MatchMode.Substring));
} }
[Test] [Test]
@ -272,7 +316,19 @@ namespace osu.Game.Tests.NonVisual.Filtering
Assert.AreEqual("really like yes", filterCriteria.SearchText.Trim()); Assert.AreEqual("really like yes", filterCriteria.SearchText.Trim());
Assert.AreEqual(3, filterCriteria.SearchTerms.Length); Assert.AreEqual(3, filterCriteria.SearchTerms.Length);
Assert.AreEqual("name with space", filterCriteria.Artist.SearchTerm); Assert.AreEqual("name with space", filterCriteria.Artist.SearchTerm);
Assert.That(filterCriteria.Artist.Exact, Is.True); Assert.That(filterCriteria.Artist.MatchMode, Is.EqualTo(FilterCriteria.MatchMode.IsolatedPhrase));
}
[Test]
public void TestApplyArtistQueriesWithSpacesFullPhrase()
{
const string query = "artist=\"The Only One\"!";
var filterCriteria = new FilterCriteria();
FilterQueryParser.ApplyQueries(filterCriteria, query);
Assert.That(filterCriteria.SearchText.Trim(), Is.Empty);
Assert.AreEqual(0, filterCriteria.SearchTerms.Length);
Assert.AreEqual("The Only One", filterCriteria.Artist.SearchTerm);
Assert.That(filterCriteria.Artist.MatchMode, Is.EqualTo(FilterCriteria.MatchMode.FullPhrase));
} }
[Test] [Test]

View File

@ -51,6 +51,8 @@ namespace osu.Game.Tests.Skins
"Archives/modified-default-20230117.osk", "Archives/modified-default-20230117.osk",
// Covers player avatar and flag. // Covers player avatar and flag.
"Archives/modified-argon-20230305.osk", "Archives/modified-argon-20230305.osk",
// Covers key counters
"Archives/modified-argon-pro-20230618.osk"
}; };
/// <summary> /// <summary>

View File

@ -101,6 +101,38 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("objects reverted to original position", () => addedObjects[0].StartTime == 100); AddAssert("objects reverted to original position", () => addedObjects[0].StartTime == 100);
} }
[Test]
public void TestRotateHotkeys()
{
HitCircle[] addedObjects = null;
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[]
{
new HitCircle { StartTime = 100 },
new HitCircle { StartTime = 200, Position = new Vector2(100) },
new HitCircle { StartTime = 300, Position = new Vector2(200) },
new HitCircle { StartTime = 400, Position = new Vector2(300) },
}));
AddStep("select objects", () => EditorBeatmap.SelectedHitObjects.AddRange(addedObjects));
AddStep("rotate clockwise", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.Key(Key.Period);
InputManager.ReleaseKey(Key.ControlLeft);
});
AddAssert("objects rotated clockwise", () => addedObjects[0].Position == new Vector2(300, 0));
AddStep("rotate counterclockwise", () =>
{
InputManager.PressKey(Key.ControlLeft);
InputManager.Key(Key.Comma);
InputManager.ReleaseKey(Key.ControlLeft);
});
AddAssert("objects reverted to original position", () => addedObjects[0].Position == new Vector2(0));
}
[Test] [Test]
public void TestBasicSelect() public void TestBasicSelect()
{ {

View File

@ -72,9 +72,13 @@ namespace osu.Game.Tests.Visual.Editing
AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog != null); AddUntilStep("wait for dialog", () => DialogOverlay.CurrentDialog != null);
AddStep("confirm", () => InputManager.Key(Key.Number1)); AddStep("confirm", () => InputManager.Key(Key.Number1));
AddAssert($"difficulty {i} is deleted", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.Select(b => b.ID), () => Does.Not.Contain(deletedDifficultyID)); AddAssert($"difficulty {i} is unattached from set",
AddAssert("count decreased by one", () => Beatmap.Value.BeatmapSetInfo.Beatmaps.Count, () => Is.EqualTo(countBeforeDeletion - 1)); () => Beatmap.Value.BeatmapSetInfo.Beatmaps.Select(b => b.ID), () => Does.Not.Contain(deletedDifficultyID));
AddAssert("beatmap set difficulty count decreased by one",
() => Beatmap.Value.BeatmapSetInfo.Beatmaps.Count, () => Is.EqualTo(countBeforeDeletion - 1));
AddAssert("set hash changed", () => Beatmap.Value.BeatmapSetInfo.Hash, () => Is.Not.EqualTo(beatmapSetHashBefore)); AddAssert("set hash changed", () => Beatmap.Value.BeatmapSetInfo.Hash, () => Is.Not.EqualTo(beatmapSetHashBefore));
AddAssert($"difficulty {i} is deleted from realm",
() => Realm.Run(r => r.Find<BeatmapInfo>(deletedDifficultyID)), () => Is.Null);
} }
} }
} }

View File

@ -35,14 +35,14 @@ namespace osu.Game.Tests.Visual.Gameplay
var referenceBeatmap = CreateBeatmap(new OsuRuleset().RulesetInfo); var referenceBeatmap = CreateBeatmap(new OsuRuleset().RulesetInfo);
AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0); AddUntilStep("score above zero", () => Player.ScoreProcessor.TotalScore.Value > 0);
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Counters.Any(kc => kc.CountPresses.Value > 2)); AddUntilStep("key counter counted keys", () => Player.HUDOverlay.InputCountController.Triggers.Any(kc => kc.ActivationCount.Value > 2));
seekTo(referenceBeatmap.Breaks[0].StartTime); seekTo(referenceBeatmap.Breaks[0].StartTime);
AddAssert("keys not counting", () => !Player.HUDOverlay.KeyCounter.IsCounting.Value); AddAssert("keys not counting", () => !Player.HUDOverlay.InputCountController.IsCounting.Value);
AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType<BreakInfo>().Single().AccuracyDisplay.Current.Value == 1); AddAssert("overlay displays 100% accuracy", () => Player.BreakOverlay.ChildrenOfType<BreakInfo>().Single().AccuracyDisplay.Current.Value == 1);
AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000)); AddStep("rewind", () => Player.GameplayClockContainer.Seek(-80000));
AddUntilStep("key counter reset", () => Player.HUDOverlay.KeyCounter.Counters.All(kc => kc.CountPresses.Value == 0)); AddUntilStep("key counter reset", () => Player.HUDOverlay.InputCountController.Triggers.All(kc => kc.ActivationCount.Value == 0));
seekTo(referenceBeatmap.HitObjects[^1].GetEndTime()); seekTo(referenceBeatmap.HitObjects[^1].GetEndTime());
AddUntilStep("results displayed", () => getResultsScreen()?.IsLoaded == true); AddUntilStep("results displayed", () => getResultsScreen()?.IsLoaded == true);

View File

@ -18,6 +18,7 @@ using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Skinning.Legacy; using osu.Game.Rulesets.Osu.Skinning.Legacy;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD;
using osu.Game.Skinning; using osu.Game.Skinning;
using osu.Game.Storyboards; using osu.Game.Storyboards;
@ -77,7 +78,8 @@ namespace osu.Game.Tests.Visual.Gameplay
(typeof(ScoreProcessor), actualComponentsContainer.Dependencies.Get<ScoreProcessor>()), (typeof(ScoreProcessor), actualComponentsContainer.Dependencies.Get<ScoreProcessor>()),
(typeof(HealthProcessor), actualComponentsContainer.Dependencies.Get<HealthProcessor>()), (typeof(HealthProcessor), actualComponentsContainer.Dependencies.Get<HealthProcessor>()),
(typeof(GameplayState), actualComponentsContainer.Dependencies.Get<GameplayState>()), (typeof(GameplayState), actualComponentsContainer.Dependencies.Get<GameplayState>()),
(typeof(IGameplayClock), actualComponentsContainer.Dependencies.Get<IGameplayClock>()) (typeof(IGameplayClock), actualComponentsContainer.Dependencies.Get<IGameplayClock>()),
(typeof(InputCountController), actualComponentsContainer.Dependencies.Get<InputCountController>())
}, },
}; };

View File

@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual.Gameplay
{ {
public partial class TestSceneClicksPerSecondCalculator : OsuTestScene public partial class TestSceneClicksPerSecondCalculator : OsuTestScene
{ {
private ClicksPerSecondCalculator calculator = null!; private ClicksPerSecondController controller = null!;
private TestGameplayClock manualGameplayClock = null!; private TestGameplayClock manualGameplayClock = null!;
@ -34,11 +34,11 @@ namespace osu.Game.Tests.Visual.Gameplay
CachedDependencies = new (Type, object)[] { (typeof(IGameplayClock), manualGameplayClock) }, CachedDependencies = new (Type, object)[] { (typeof(IGameplayClock), manualGameplayClock) },
Children = new Drawable[] Children = new Drawable[]
{ {
calculator = new ClicksPerSecondCalculator(), controller = new ClicksPerSecondController(),
new DependencyProvidingContainer new DependencyProvidingContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
CachedDependencies = new (Type, object)[] { (typeof(ClicksPerSecondCalculator), calculator) }, CachedDependencies = new (Type, object)[] { (typeof(ClicksPerSecondController), controller) },
Child = new ClicksPerSecondCounter Child = new ClicksPerSecondCounter
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
@ -81,7 +81,7 @@ namespace osu.Game.Tests.Visual.Gameplay
checkClicksPerSecondValue(6); checkClicksPerSecondValue(6);
} }
private void checkClicksPerSecondValue(int i) => AddAssert("clicks/s is correct", () => calculator.Value, () => Is.EqualTo(i)); private void checkClicksPerSecondValue(int i) => AddAssert("clicks/s is correct", () => controller.Value, () => Is.EqualTo(i));
private void seekClockImmediately(double time) => manualGameplayClock.CurrentTime = time; private void seekClockImmediately(double time) => manualGameplayClock.CurrentTime = time;
@ -94,7 +94,7 @@ namespace osu.Game.Tests.Visual.Gameplay
foreach (double timestamp in inputs) foreach (double timestamp in inputs)
{ {
seekClockImmediately(timestamp); seekClockImmediately(timestamp);
calculator.AddInputTimestamp(); controller.AddInputTimestamp();
} }
seekClockImmediately(baseTime); seekClockImmediately(baseTime);

View File

@ -7,8 +7,8 @@ using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Utils;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Framework.Utils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
@ -31,11 +31,11 @@ namespace osu.Game.Tests.Visual.Gameplay
AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning); AddUntilStep("wait for track to start running", () => Beatmap.Value.Track.IsRunning);
addSeekStep(3000); addSeekStep(3000);
AddAssert("all judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged)); AddAssert("all judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => h.Judged));
AddUntilStep("key counter counted keys", () => Player.HUDOverlay.KeyCounter.Counters.Select(kc => kc.CountPresses.Value).Sum() == 15); AddUntilStep("key counter counted keys", () => Player.HUDOverlay.InputCountController.Triggers.Select(kc => kc.ActivationCount.Value).Sum() == 15);
AddStep("clear results", () => Player.Results.Clear()); AddStep("clear results", () => Player.Results.Clear());
addSeekStep(0); addSeekStep(0);
AddAssert("none judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged)); AddAssert("none judged", () => Player.DrawableRuleset.Playfield.AllHitObjects.All(h => !h.Judged));
AddUntilStep("key counters reset", () => Player.HUDOverlay.KeyCounter.Counters.All(kc => kc.CountPresses.Value == 0)); AddUntilStep("key counters reset", () => Player.HUDOverlay.InputCountController.Triggers.All(kc => kc.ActivationCount.Value == 0));
AddAssert("no results triggered", () => Player.Results.Count == 0); AddAssert("no results triggered", () => Player.Results.Count == 0);
} }

View File

@ -44,8 +44,8 @@ namespace osu.Game.Tests.Visual.Gameplay
private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new FramedClock()); private readonly IGameplayClock gameplayClock = new GameplayClockContainer(new FramedClock());
// best way to check without exposing. // best way to check without exposing.
private Drawable hideTarget => hudOverlay.KeyCounter; private Drawable hideTarget => hudOverlay.ChildrenOfType<SkinComponentsContainer>().First();
private Drawable keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().Single(); private Drawable keyCounterFlow => hudOverlay.ChildrenOfType<KeyCounterDisplay>().First().ChildrenOfType<FillFlowContainer<KeyCounter>>().Single();
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddAssert("showhud is set", () => hudOverlay.ShowHud.Value); AddAssert("showhud is set", () => hudOverlay.ShowHud.Value);
AddAssert("hidetarget is visible", () => hideTarget.IsPresent); AddAssert("hidetarget is visible", () => hideTarget.Alpha, () => Is.GreaterThan(0));
AddAssert("key counter flow is visible", () => keyCounterFlow.IsPresent); AddAssert("key counter flow is visible", () => keyCounterFlow.IsPresent);
AddAssert("pause button is visible", () => hudOverlay.HoldToQuit.IsPresent); AddAssert("pause button is visible", () => hudOverlay.HoldToQuit.IsPresent);
} }
@ -95,7 +95,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent); AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0));
AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent); AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent);
// Key counter flow container should not be affected by this, only the key counter display will be hidden as checked above. // Key counter flow container should not be affected by this, only the key counter display will be hidden as checked above.
@ -109,13 +109,13 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("set hud to never show", () => localConfig.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never)); AddStep("set hud to never show", () => localConfig.SetValue(OsuSetting.HUDVisibilityMode, HUDVisibilityMode.Never));
AddUntilStep("wait for fade", () => !hideTarget.IsPresent); AddUntilStep("wait for fade", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0));
AddStep("trigger momentary show", () => InputManager.PressKey(Key.ControlLeft)); AddStep("trigger momentary show", () => InputManager.PressKey(Key.ControlLeft));
AddUntilStep("wait for visible", () => hideTarget.IsPresent); AddUntilStep("wait for visible", () => hideTarget.Alpha, () => Is.GreaterThan(0));
AddStep("stop trigering", () => InputManager.ReleaseKey(Key.ControlLeft)); AddStep("stop trigering", () => InputManager.ReleaseKey(Key.ControlLeft));
AddUntilStep("wait for fade", () => !hideTarget.IsPresent); AddUntilStep("wait for fade", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0));
} }
[Test] [Test]
@ -138,16 +138,18 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("hide key overlay", () => AddStep("hide key overlay", () =>
{ {
localConfig.SetValue(OsuSetting.KeyOverlay, false); localConfig.SetValue(OsuSetting.KeyOverlay, false);
hudOverlay.KeyCounter.AlwaysVisible.Value = false; var kcd = hudOverlay.ChildrenOfType<KeyCounterDisplay>().FirstOrDefault();
if (kcd != null)
kcd.AlwaysVisible.Value = false;
}); });
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent); AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0));
AddAssert("key counters hidden", () => !keyCounterFlow.IsPresent); AddUntilStep("key counters hidden", () => !keyCounterFlow.IsPresent);
AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true); AddStep("set showhud true", () => hudOverlay.ShowHud.Value = true);
AddUntilStep("hidetarget is visible", () => hideTarget.IsPresent); AddUntilStep("hidetarget is visible", () => hideTarget.Alpha, () => Is.GreaterThan(0));
AddAssert("key counters still hidden", () => !keyCounterFlow.IsPresent); AddUntilStep("key counters still hidden", () => !keyCounterFlow.IsPresent);
} }
[Test] [Test]
@ -169,7 +171,7 @@ namespace osu.Game.Tests.Visual.Gameplay
}); });
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent); AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0));
AddStep("attempt activate", () => AddStep("attempt activate", () =>
{ {
@ -209,7 +211,7 @@ namespace osu.Game.Tests.Visual.Gameplay
}); });
AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false); AddStep("set showhud false", () => hudOverlay.ShowHud.Value = false);
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent); AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0));
AddStep("attempt seek", () => AddStep("attempt seek", () =>
{ {
@ -234,7 +236,6 @@ namespace osu.Game.Tests.Visual.Gameplay
createNew(); createNew();
AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded);
AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().Single().Alpha == 0); AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().Single().Alpha == 0);
AddUntilStep("wait for hud load", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().All(c => c.ComponentsLoaded)); AddUntilStep("wait for hud load", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().All(c => c.ComponentsLoaded));
@ -253,7 +254,6 @@ namespace osu.Game.Tests.Visual.Gameplay
createNew(); createNew();
AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded);
AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().Single().Alpha == 0); AddUntilStep("wait for components to be hidden", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().Single().Alpha == 0);
AddStep("reload components", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().Single().Reload()); AddStep("reload components", () => hudOverlay.ChildrenOfType<SkinComponentsContainer>().Single().Reload());
@ -267,7 +267,7 @@ namespace osu.Game.Tests.Visual.Gameplay
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>()); hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
// Add any key just to display the key counter visually. // Add any key just to display the key counter visually.
hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space)); hudOverlay.InputCountController.Add(new KeyCounterKeyboardTrigger(Key.Space));
scoreProcessor.Combo.Value = 1; scoreProcessor.Combo.Value = 1;
@ -275,6 +275,9 @@ namespace osu.Game.Tests.Visual.Gameplay
Child = hudOverlay; Child = hudOverlay;
}); });
AddUntilStep("wait for hud load", () => hudOverlay.IsLoaded);
AddUntilStep("wait for components present", () => hudOverlay.ChildrenOfType<KeyCounterDisplay>().FirstOrDefault() != null);
} }
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)

View File

@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.Gameplay
public partial class TestSceneJudgementCounter : OsuTestScene public partial class TestSceneJudgementCounter : OsuTestScene
{ {
private ScoreProcessor scoreProcessor = null!; private ScoreProcessor scoreProcessor = null!;
private JudgementTally judgementTally = null!; private JudgementCountController judgementCountController = null!;
private TestJudgementCounterDisplay counterDisplay = null!; private TestJudgementCounterDisplay counterDisplay = null!;
private DependencyProvidingContainer content = null!; private DependencyProvidingContainer content = null!;
@ -47,11 +47,11 @@ namespace osu.Game.Tests.Visual.Gameplay
CachedDependencies = new (Type, object)[] { (typeof(ScoreProcessor), scoreProcessor), (typeof(Ruleset), ruleset) }, CachedDependencies = new (Type, object)[] { (typeof(ScoreProcessor), scoreProcessor), (typeof(Ruleset), ruleset) },
Children = new Drawable[] Children = new Drawable[]
{ {
judgementTally = new JudgementTally(), judgementCountController = new JudgementCountController(),
content = new DependencyProvidingContainer content = new DependencyProvidingContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
CachedDependencies = new (Type, object)[] { (typeof(JudgementTally), judgementTally) }, CachedDependencies = new (Type, object)[] { (typeof(JudgementCountController), judgementCountController) },
} }
}, },
}; };

View File

@ -3,7 +3,9 @@
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Utils; using osu.Framework.Utils;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD;
@ -15,63 +17,66 @@ namespace osu.Game.Tests.Visual.Gameplay
[TestFixture] [TestFixture]
public partial class TestSceneKeyCounter : OsuManualInputManagerTestScene public partial class TestSceneKeyCounter : OsuManualInputManagerTestScene
{ {
[Cached]
private readonly InputCountController controller;
public TestSceneKeyCounter() public TestSceneKeyCounter()
{ {
KeyCounterDisplay defaultDisplay = new DefaultKeyCounterDisplay Children = new Drawable[]
{ {
Origin = Anchor.Centre, controller = new InputCountController(),
Anchor = Anchor.Centre, new FillFlowContainer
Position = new Vector2(0, 72.7f) {
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(72.7f),
Children = new KeyCounterDisplay[]
{
new DefaultKeyCounterDisplay
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
},
new ArgonKeyCounterDisplay
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
}
}
}
}; };
KeyCounterDisplay argonDisplay = new ArgonKeyCounterDisplay var inputTriggers = new InputTrigger[]
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Position = new Vector2(0, -72.7f)
};
defaultDisplay.AddRange(new InputTrigger[]
{ {
new KeyCounterKeyboardTrigger(Key.X), new KeyCounterKeyboardTrigger(Key.X),
new KeyCounterKeyboardTrigger(Key.X), new KeyCounterKeyboardTrigger(Key.X),
new KeyCounterMouseTrigger(MouseButton.Left), new KeyCounterMouseTrigger(MouseButton.Left),
new KeyCounterMouseTrigger(MouseButton.Right), new KeyCounterMouseTrigger(MouseButton.Right),
}); };
argonDisplay.AddRange(new InputTrigger[] AddRange(inputTriggers);
{ controller.AddRange(inputTriggers);
new KeyCounterKeyboardTrigger(Key.X),
new KeyCounterKeyboardTrigger(Key.X),
new KeyCounterMouseTrigger(MouseButton.Left),
new KeyCounterMouseTrigger(MouseButton.Right),
});
var testCounter = (DefaultKeyCounter)defaultDisplay.Counters.First();
AddStep("Add random", () => AddStep("Add random", () =>
{ {
Key key = (Key)((int)Key.A + RNG.Next(26)); Key key = (Key)((int)Key.A + RNG.Next(26));
defaultDisplay.Add(new KeyCounterKeyboardTrigger(key)); var trigger = new KeyCounterKeyboardTrigger(key);
argonDisplay.Add(new KeyCounterKeyboardTrigger(key)); Add(trigger);
controller.Add(trigger);
}); });
Key testKey = ((KeyCounterKeyboardTrigger)defaultDisplay.Counters.First().Trigger).Key; InputTrigger testTrigger = controller.Triggers.First();
Key testKey = ((KeyCounterKeyboardTrigger)testTrigger).Key;
addPressKeyStep(); addPressKeyStep();
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 1); AddAssert($"Check {testKey} counter after keypress", () => testTrigger.ActivationCount.Value == 1);
addPressKeyStep(); addPressKeyStep();
AddAssert($"Check {testKey} counter after keypress", () => testCounter.CountPresses.Value == 2); AddAssert($"Check {testKey} counter after keypress", () => testTrigger.ActivationCount.Value == 2);
AddStep("Disable counting", () => AddStep("Disable counting", () => controller.IsCounting.Value = false);
{
argonDisplay.IsCounting.Value = false;
defaultDisplay.IsCounting.Value = false;
});
addPressKeyStep(); addPressKeyStep();
AddAssert($"Check {testKey} count has not changed", () => testCounter.CountPresses.Value == 2); AddAssert($"Check {testKey} count has not changed", () => testTrigger.ActivationCount.Value == 2);
Add(defaultDisplay);
Add(argonDisplay);
void addPressKeyStep() => AddStep($"Press {testKey} key", () => InputManager.Key(testKey)); void addPressKeyStep() => AddStep($"Press {testKey} key", () => InputManager.Key(testKey));
} }

View File

@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Gameplay
protected override void AddCheckSteps() protected override void AddCheckSteps()
{ {
AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0); AddUntilStep("score above zero", () => ((ScoreAccessibleReplayPlayer)Player).ScoreProcessor.TotalScore.Value > 0);
AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.KeyCounter.Counters.Any(kc => kc.CountPresses.Value > 0)); AddUntilStep("key counter counted keys", () => ((ScoreAccessibleReplayPlayer)Player).HUDOverlay.InputCountController.Triggers.Any(kc => kc.ActivationCount.Value > 0));
AddAssert("cannot fail", () => !((ScoreAccessibleReplayPlayer)Player).AllowFail); AddAssert("cannot fail", () => !((ScoreAccessibleReplayPlayer)Player).AllowFail);
} }

View File

@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Gameplay
}; };
// Add any key just to display the key counter visually. // Add any key just to display the key counter visually.
hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space)); hudOverlay.InputCountController.Add(new KeyCounterKeyboardTrigger(Key.Space));
scoreProcessor.Combo.Value = 1; scoreProcessor.Combo.Value = 1;
return new Container return new Container

View File

@ -19,6 +19,7 @@ using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD;
using osu.Game.Skinning;
using osu.Game.Tests.Gameplay; using osu.Game.Tests.Gameplay;
using osuTK.Input; using osuTK.Input;
@ -43,8 +44,8 @@ namespace osu.Game.Tests.Visual.Gameplay
private IEnumerable<HUDOverlay> hudOverlays => CreatedDrawables.OfType<HUDOverlay>(); private IEnumerable<HUDOverlay> hudOverlays => CreatedDrawables.OfType<HUDOverlay>();
// best way to check without exposing. // best way to check without exposing.
private Drawable hideTarget => hudOverlay.KeyCounter; private Drawable hideTarget => hudOverlay.ChildrenOfType<SkinComponentsContainer>().First();
private Drawable keyCounterFlow => hudOverlay.KeyCounter.ChildrenOfType<FillFlowContainer<KeyCounter>>().Single(); private Drawable keyCounterFlow => hudOverlay.ChildrenOfType<KeyCounterDisplay>().First().ChildrenOfType<FillFlowContainer<KeyCounter>>().Single();
[Test] [Test]
public void TestComboCounterIncrementing() public void TestComboCounterIncrementing()
@ -62,7 +63,6 @@ namespace osu.Game.Tests.Visual.Gameplay
float? initialAlpha = null; float? initialAlpha = null;
createNew(h => h.OnLoadComplete += _ => initialAlpha = hideTarget.Alpha); createNew(h => h.OnLoadComplete += _ => initialAlpha = hideTarget.Alpha);
AddUntilStep("wait for load", () => hudOverlay.IsAlive);
AddAssert("initial alpha was less than 1", () => initialAlpha < 1); AddAssert("initial alpha was less than 1", () => initialAlpha < 1);
} }
@ -73,7 +73,7 @@ namespace osu.Game.Tests.Visual.Gameplay
AddStep("set showhud false", () => hudOverlays.ForEach(h => h.ShowHud.Value = false)); AddStep("set showhud false", () => hudOverlays.ForEach(h => h.ShowHud.Value = false));
AddUntilStep("hidetarget is hidden", () => !hideTarget.IsPresent); AddUntilStep("hidetarget is hidden", () => hideTarget.Alpha, () => Is.LessThanOrEqualTo(0));
AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent); AddAssert("pause button is still visible", () => hudOverlay.HoldToQuit.IsPresent);
// Key counter flow container should not be affected by this, only the key counter display will be hidden as checked above. // Key counter flow container should not be affected by this, only the key counter display will be hidden as checked above.
@ -89,13 +89,16 @@ namespace osu.Game.Tests.Visual.Gameplay
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>()); hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
// Add any key just to display the key counter visually. // Add any key just to display the key counter visually.
hudOverlay.KeyCounter.Add(new KeyCounterKeyboardTrigger(Key.Space)); hudOverlay.InputCountController.Add(new KeyCounterKeyboardTrigger(Key.Space));
action?.Invoke(hudOverlay); action?.Invoke(hudOverlay);
return hudOverlay; return hudOverlay;
}); });
}); });
AddUntilStep("HUD overlay loaded", () => hudOverlay.IsAlive);
AddUntilStep("components container loaded",
() => hudOverlay.ChildrenOfType<SkinComponentsContainer>().Any(scc => scc.ComponentsLoaded));
} }
protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset(); protected override Ruleset CreateRulesetForSkinProvider() => new OsuRuleset();

View File

@ -339,6 +339,8 @@ namespace osu.Game.Beatmaps
DeleteFile(setInfo, beatmapInfo.File); DeleteFile(setInfo, beatmapInfo.File);
setInfo.Beatmaps.Remove(beatmapInfo); setInfo.Beatmaps.Remove(beatmapInfo);
r.Remove(beatmapInfo.Metadata);
r.Remove(beatmapInfo);
updateHashAndMarkDirty(setInfo); updateHashAndMarkDirty(setInfo);
workingBeatmapCache.Invalidate(setInfo); workingBeatmapCache.Invalidate(setInfo);

View File

@ -63,6 +63,31 @@ Please try changing your audio device to a working setting.");
/// </summary> /// </summary>
public static LocalisableString ScoreOverlayDisabled(LocalisableString arg0) => new TranslatableString(getKey(@"score_overlay_disabled"), @"The score overlay is currently disabled. You can toggle this by pressing {0}.", arg0); public static LocalisableString ScoreOverlayDisabled(LocalisableString arg0) => new TranslatableString(getKey(@"score_overlay_disabled"), @"The score overlay is currently disabled. You can toggle this by pressing {0}.", arg0);
/// <summary>
/// "The URL {0} has an unsupported or dangerous protocol and will not be opened."
/// </summary>
public static LocalisableString UnsupportedOrDangerousUrlProtocol(string url) => new TranslatableString(getKey(@"unsupported_or_dangerous_url_protocol"), @"The URL {0} has an unsupported or dangerous protocol and will not be opened.", url);
/// <summary>
/// "Subsequent messages have been logged. Click to view log files."
/// </summary>
public static LocalisableString SubsequentMessagesLogged => new TranslatableString(getKey(@"subsequent_messages_logged"), @"Subsequent messages have been logged. Click to view log files.");
/// <summary>
/// "Disabling tablet support due to error: &quot;{0}&quot;"
/// </summary>
public static LocalisableString TabletSupportDisabledDueToError(string message) => new TranslatableString(getKey(@"tablet_support_disabled_due_to_error"), @"Disabling tablet support due to error: ""{0}""", message);
/// <summary>
/// "Encountered tablet warning, your tablet may not function correctly. Click here for a list of all tablets supported."
/// </summary>
public static LocalisableString EncounteredTabletWarning => new TranslatableString(getKey(@"encountered_tablet_warning"), @"Encountered tablet warning, your tablet may not function correctly. Click here for a list of all tablets supported.");
/// <summary>
/// "This link type is not yet supported!"
/// </summary>
public static LocalisableString LinkTypeNotSupported => new TranslatableString(getKey(@"unsupported_link_type"), @"This link type is not yet supported!");
private static string getKey(string key) => $@"{prefix}:{key}"; private static string getKey(string key) => $@"{prefix}:{key}";
} }
} }

View File

@ -435,7 +435,7 @@ namespace osu.Game
case LinkAction.Spectate: case LinkAction.Spectate:
waitForReady(() => Notifications, _ => Notifications.Post(new SimpleNotification waitForReady(() => Notifications, _ => Notifications.Post(new SimpleNotification
{ {
Text = @"This link type is not yet supported!", Text = NotificationsStrings.LinkTypeNotSupported,
Icon = FontAwesome.Solid.LifeRing, Icon = FontAwesome.Solid.LifeRing,
})); }));
break; break;
@ -477,7 +477,7 @@ namespace osu.Game
{ {
Notifications.Post(new SimpleErrorNotification Notifications.Post(new SimpleErrorNotification
{ {
Text = $"The URL {url} has an unsupported or dangerous protocol and will not be opened.", Text = NotificationsStrings.UnsupportedOrDangerousUrlProtocol(url),
}); });
return; return;
@ -1147,7 +1147,7 @@ namespace osu.Game
Schedule(() => Notifications.Post(new SimpleNotification Schedule(() => Notifications.Post(new SimpleNotification
{ {
Icon = FontAwesome.Solid.EllipsisH, Icon = FontAwesome.Solid.EllipsisH,
Text = "Subsequent messages have been logged. Click to view log files.", Text = NotificationsStrings.SubsequentMessagesLogged,
Activated = () => Activated = () =>
{ {
Storage.GetStorageForDirectory(@"logs").PresentFileExternally(logFile); Storage.GetStorageForDirectory(@"logs").PresentFileExternally(logFile);
@ -1179,7 +1179,7 @@ namespace osu.Game
{ {
Notifications.Post(new SimpleNotification Notifications.Post(new SimpleNotification
{ {
Text = $"Disabling tablet support due to error: \"{message}\"", Text = NotificationsStrings.TabletSupportDisabledDueToError(message),
Icon = FontAwesome.Solid.PenSquare, Icon = FontAwesome.Solid.PenSquare,
IconColour = Colours.RedDark, IconColour = Colours.RedDark,
}); });
@ -1196,7 +1196,7 @@ namespace osu.Game
{ {
Schedule(() => Notifications.Post(new SimpleNotification Schedule(() => Notifications.Post(new SimpleNotification
{ {
Text = @"Encountered tablet warning, your tablet may not function correctly. Click here for a list of all tablets supported.", Text = NotificationsStrings.EncounteredTabletWarning,
Icon = FontAwesome.Solid.PenSquare, Icon = FontAwesome.Solid.PenSquare,
IconColour = Colours.YellowDark, IconColour = Colours.YellowDark,
Activated = () => Activated = () =>

View File

@ -335,11 +335,11 @@ namespace osu.Game.Rulesets.UI
/// <returns>The representing <see cref="DrawableHitObject{TObject}"/>.</returns> /// <returns>The representing <see cref="DrawableHitObject{TObject}"/>.</returns>
public abstract DrawableHitObject<TObject> CreateDrawableRepresentation(TObject h); public abstract DrawableHitObject<TObject> CreateDrawableRepresentation(TObject h);
public void Attach(KeyCounterDisplay keyCounter) => public void Attach(InputCountController inputCountController) =>
(KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(keyCounter); (KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(inputCountController);
public void Attach(ClicksPerSecondCalculator calculator) => public void Attach(ClicksPerSecondController controller) =>
(KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(calculator); (KeyBindingInputManager as ICanAttachHUDPieces)?.Attach(controller);
/// <summary> /// <summary>
/// Creates a key conversion input manager. An exception will be thrown if a valid <see cref="RulesetInputManager{T}"/> is not returned. /// Creates a key conversion input manager. An exception will be thrown if a valid <see cref="RulesetInputManager{T}"/> is not returned.

View File

@ -0,0 +1,21 @@
// 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.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.HUD.ClicksPerSecond;
namespace osu.Game.Rulesets.UI
{
/// <summary>
/// A target (generally always <see cref="DrawableRuleset"/>) which can attach various skinnable components.
/// </summary>
/// <remarks>
/// Attach methods will give the target permission to prepare the component into a usable state, usually via
/// calling methods on the component (attaching various gameplay devices).
/// </remarks>
public interface ICanAttachHUDPieces
{
void Attach(InputCountController inputCountController);
void Attach(ClicksPerSecondController controller);
}
}

View File

@ -0,0 +1,15 @@
// 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;
namespace osu.Game.Rulesets.UI
{
/// <summary>
/// Expose the <see cref="ReplayRecorder"/> in a capable <see cref="InputManager"/>.
/// </summary>
public interface IHasRecordingHandler
{
public ReplayRecorder? Recorder { set; }
}
}

View File

@ -0,0 +1,16 @@
// 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;
using osu.Game.Input.Handlers;
namespace osu.Game.Rulesets.UI
{
/// <summary>
/// Expose the <see cref="ReplayInputHandler"/> in a capable <see cref="InputManager"/>.
/// </summary>
public interface IHasReplayHandler
{
ReplayInputHandler? ReplayInputHandler { get; set; }
}
}

View File

@ -160,62 +160,37 @@ namespace osu.Game.Rulesets.UI
#region Key Counter Attachment #region Key Counter Attachment
public void Attach(KeyCounterDisplay keyCounter) public void Attach(InputCountController inputCountController)
{ {
var receptor = new ActionReceptor(keyCounter); var triggers = KeyBindingContainer.DefaultKeyBindings
.Select(b => b.GetAction<T>())
.Distinct()
.OrderBy(action => action)
.Select(action => new KeyCounterActionTrigger<T>(action))
.ToArray();
KeyBindingContainer.Add(receptor); KeyBindingContainer.AddRange(triggers);
inputCountController.AddRange(triggers);
keyCounter.SetReceptor(receptor);
keyCounter.AddRange(KeyBindingContainer.DefaultKeyBindings
.Select(b => b.GetAction<T>())
.Distinct()
.OrderBy(action => action)
.Select(action => new KeyCounterActionTrigger<T>(action)));
}
private partial class ActionReceptor : KeyCounterDisplay.Receptor, IKeyBindingHandler<T>
{
public ActionReceptor(KeyCounterDisplay target)
: base(target)
{
}
public bool OnPressed(KeyBindingPressEvent<T> e) => Target.Counters.Where(c => c.Trigger is KeyCounterActionTrigger<T>)
.Select(c => (KeyCounterActionTrigger<T>)c.Trigger)
.Any(c => c.OnPressed(e.Action, Clock.Rate >= 0));
public void OnReleased(KeyBindingReleaseEvent<T> e)
{
foreach (var c
in Target.Counters.Where(c => c.Trigger is KeyCounterActionTrigger<T>).Select(c => (KeyCounterActionTrigger<T>)c.Trigger))
c.OnReleased(e.Action, Clock.Rate >= 0);
}
} }
#endregion #endregion
#region Keys per second Counter Attachment #region Keys per second Counter Attachment
public void Attach(ClicksPerSecondCalculator calculator) public void Attach(ClicksPerSecondController controller) => KeyBindingContainer.Add(new ActionListener(controller));
{
var listener = new ActionListener(calculator);
KeyBindingContainer.Add(listener);
}
private partial class ActionListener : Component, IKeyBindingHandler<T> private partial class ActionListener : Component, IKeyBindingHandler<T>
{ {
private readonly ClicksPerSecondCalculator calculator; private readonly ClicksPerSecondController controller;
public ActionListener(ClicksPerSecondCalculator calculator) public ActionListener(ClicksPerSecondController controller)
{ {
this.calculator = calculator; this.controller = controller;
} }
public bool OnPressed(KeyBindingPressEvent<T> e) public bool OnPressed(KeyBindingPressEvent<T> e)
{ {
calculator.AddInputTimestamp(); controller.AddInputTimestamp();
return false; return false;
} }
@ -247,29 +222,6 @@ namespace osu.Game.Rulesets.UI
} }
} }
/// <summary>
/// Expose the <see cref="ReplayInputHandler"/> in a capable <see cref="InputManager"/>.
/// </summary>
public interface IHasReplayHandler
{
ReplayInputHandler ReplayInputHandler { get; set; }
}
public interface IHasRecordingHandler
{
public ReplayRecorder Recorder { set; }
}
/// <summary>
/// Supports attaching various HUD pieces.
/// Keys will be populated automatically and a receptor will be injected inside.
/// </summary>
public interface ICanAttachHUDPieces
{
void Attach(KeyCounterDisplay keyCounter);
void Attach(ClicksPerSecondCalculator calculator);
}
public class RulesetInputManagerInputState<T> : InputState public class RulesetInputManagerInputState<T> : InputState
where T : struct where T : struct
{ {

View File

@ -1,8 +1,6 @@
// 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; using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -24,13 +22,17 @@ namespace osu.Game.Screens.Edit.Compose.Components
private const float button_padding = 5; private const float button_padding = 5;
public Func<float, bool> OnRotation; public Func<float, bool>? OnRotation;
public Func<Vector2, Anchor, bool> OnScale; public Func<Vector2, Anchor, bool>? OnScale;
public Func<Direction, bool, bool> OnFlip; public Func<Direction, bool, bool>? OnFlip;
public Func<bool> OnReverse; public Func<bool>? OnReverse;
public Action OperationStarted; public Action? OperationStarted;
public Action OperationEnded; public Action? OperationEnded;
private SelectionBoxButton? reverseButton;
private SelectionBoxButton? rotateClockwiseButton;
private SelectionBoxButton? rotateCounterClockwiseButton;
private bool canReverse; private bool canReverse;
@ -134,7 +136,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
} }
} }
private string text; private string text = string.Empty;
public string Text public string Text
{ {
@ -150,13 +152,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
} }
} }
private SelectionBoxDragHandleContainer dragHandles; private SelectionBoxDragHandleContainer dragHandles = null!;
private FillFlowContainer buttons; private FillFlowContainer buttons = null!;
private OsuSpriteText selectionDetailsText; private OsuSpriteText? selectionDetailsText;
[Resolved] [Resolved]
private OsuColour colours { get; set; } private OsuColour colours { get; set; } = null!;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() => recreate(); private void load() => recreate();
@ -166,19 +168,16 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (e.Repeat || !e.ControlPressed) if (e.Repeat || !e.ControlPressed)
return false; return false;
bool runOperationFromHotkey(Func<bool> operation)
{
operationStarted();
bool result = operation?.Invoke() ?? false;
operationEnded();
return result;
}
switch (e.Key) switch (e.Key)
{ {
case Key.G: case Key.G:
return CanReverse && runOperationFromHotkey(OnReverse); return CanReverse && reverseButton?.TriggerClick() == true;
case Key.Comma:
return CanRotate && rotateCounterClockwiseButton?.TriggerClick() == true;
case Key.Period:
return CanRotate && rotateClockwiseButton?.TriggerClick() == true;
} }
return base.OnKeyDown(e); return base.OnKeyDown(e);
@ -256,13 +255,13 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (CanFlipX) addXFlipComponents(); if (CanFlipX) addXFlipComponents();
if (CanFlipY) addYFlipComponents(); if (CanFlipY) addYFlipComponents();
if (CanRotate) addRotationComponents(); if (CanRotate) addRotationComponents();
if (CanReverse) addButton(FontAwesome.Solid.Backward, "Reverse pattern (Ctrl-G)", () => OnReverse?.Invoke()); if (CanReverse) reverseButton = addButton(FontAwesome.Solid.Backward, "Reverse pattern (Ctrl-G)", () => OnReverse?.Invoke());
} }
private void addRotationComponents() private void addRotationComponents()
{ {
addButton(FontAwesome.Solid.Undo, "Rotate 90 degrees counter-clockwise", () => OnRotation?.Invoke(-90)); rotateCounterClockwiseButton = addButton(FontAwesome.Solid.Undo, "Rotate 90 degrees counter-clockwise (Ctrl-<)", () => OnRotation?.Invoke(-90));
addButton(FontAwesome.Solid.Redo, "Rotate 90 degrees clockwise", () => OnRotation?.Invoke(90)); rotateClockwiseButton = addButton(FontAwesome.Solid.Redo, "Rotate 90 degrees clockwise (Ctrl->)", () => OnRotation?.Invoke(90));
addRotateHandle(Anchor.TopLeft); addRotateHandle(Anchor.TopLeft);
addRotateHandle(Anchor.TopRight); addRotateHandle(Anchor.TopRight);
@ -300,7 +299,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
addButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically", () => OnFlip?.Invoke(Direction.Vertical, false)); addButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically", () => OnFlip?.Invoke(Direction.Vertical, false));
} }
private void addButton(IconUsage icon, string tooltip, Action action) private SelectionBoxButton addButton(IconUsage icon, string tooltip, Action action)
{ {
var button = new SelectionBoxButton(icon, tooltip) var button = new SelectionBoxButton(icon, tooltip)
{ {
@ -310,6 +309,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
button.OperationStarted += operationStarted; button.OperationStarted += operationStarted;
button.OperationEnded += operationEnded; button.OperationEnded += operationEnded;
buttons.Add(button); buttons.Add(button);
return button;
} }
private void addScaleHandle(Anchor anchor) private void addScaleHandle(Anchor anchor)

View File

@ -1,8 +1,6 @@
// 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; using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
@ -17,11 +15,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
{ {
public sealed partial class SelectionBoxButton : SelectionBoxControl, IHasTooltip public sealed partial class SelectionBoxButton : SelectionBoxControl, IHasTooltip
{ {
private SpriteIcon icon; private SpriteIcon icon = null!;
private readonly IconUsage iconUsage; private readonly IconUsage iconUsage;
public Action Action; public Action? Action;
public SelectionBoxButton(IconUsage iconUsage, string tooltip) public SelectionBoxButton(IconUsage iconUsage, string tooltip)
{ {
@ -49,6 +47,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected override bool OnClick(ClickEvent e) protected override bool OnClick(ClickEvent e)
{ {
Circle.FlashColour(Colours.GrayF, 300);
TriggerOperationStarted(); TriggerOperationStarted();
Action?.Invoke(); Action?.Invoke();
TriggerOperationEnded(); TriggerOperationEnded();

View File

@ -24,7 +24,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
public event Action OperationStarted; public event Action OperationStarted;
public event Action OperationEnded; public event Action OperationEnded;
private Circle circle; protected Circle Circle { get; private set; }
/// <summary> /// <summary>
/// Whether the user is currently holding the control with mouse. /// Whether the user is currently holding the control with mouse.
@ -41,7 +41,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
circle = new Circle Circle = new Circle
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
@ -85,9 +85,9 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected virtual void UpdateHoverState() protected virtual void UpdateHoverState()
{ {
if (IsHeld) if (IsHeld)
circle.FadeColour(Colours.GrayF, TRANSFORM_DURATION, Easing.OutQuint); Circle.FadeColour(Colours.GrayF, TRANSFORM_DURATION, Easing.OutQuint);
else else
circle.FadeColour(IsHovered ? Colours.Red : Colours.YellowDark, TRANSFORM_DURATION, Easing.OutQuint); Circle.FadeColour(IsHovered ? Colours.Red : Colours.YellowDark, TRANSFORM_DURATION, Easing.OutQuint);
this.ScaleTo(IsHeld || IsHovered ? 1.5f : 1, TRANSFORM_DURATION, Easing.OutQuint); this.ScaleTo(IsHeld || IsHovered ? 1.5f : 1, TRANSFORM_DURATION, Easing.OutQuint);
} }

View File

@ -8,7 +8,7 @@ using osu.Game.Rulesets.UI;
namespace osu.Game.Screens.Play.HUD.ClicksPerSecond namespace osu.Game.Screens.Play.HUD.ClicksPerSecond
{ {
public partial class ClicksPerSecondCalculator : Component public partial class ClicksPerSecondController : Component
{ {
private readonly List<double> timestamps = new List<double>(); private readonly List<double> timestamps = new List<double>();
@ -22,7 +22,7 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond
private IGameplayClock clock => frameStableClock ?? gameplayClock; private IGameplayClock clock => frameStableClock ?? gameplayClock;
public ClicksPerSecondCalculator() public ClicksPerSecondController()
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
} }

View File

@ -17,7 +17,7 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond
public partial class ClicksPerSecondCounter : RollingCounter<int>, ISerialisableDrawable public partial class ClicksPerSecondCounter : RollingCounter<int>, ISerialisableDrawable
{ {
[Resolved] [Resolved]
private ClicksPerSecondCalculator calculator { get; set; } = null!; private ClicksPerSecondController controller { get; set; } = null!;
protected override double RollingDuration => 350; protected override double RollingDuration => 350;
@ -38,7 +38,7 @@ namespace osu.Game.Screens.Play.HUD.ClicksPerSecond
{ {
base.Update(); base.Update();
Current.Value = calculator.Value; Current.Value = controller.Value;
} }
protected override IHasText CreateText() => new TextComponent(); protected override IHasText CreateText() => new TextComponent();

View File

@ -0,0 +1,33 @@
// 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.Collections.Generic;
using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
namespace osu.Game.Screens.Play.HUD
{
/// <summary>
/// Keeps track of key press counts for a current play session, exposing bindable counts which can
/// be used for display purposes.
/// </summary>
public partial class InputCountController : Component
{
public readonly Bindable<bool> IsCounting = new BindableBool(true);
private readonly BindableList<InputTrigger> triggers = new BindableList<InputTrigger>();
public IBindableList<InputTrigger> Triggers => triggers;
public void AddRange(IEnumerable<InputTrigger> triggers) => triggers.ForEach(Add);
public void Add(InputTrigger trigger)
{
// Note that these triggers are not added to the hierarchy here. It is presumed they are added externally at a
// more correct location (ie. inside a RulesetInputManager).
triggers.Add(trigger);
trigger.IsCounting.BindTo(IsCounting);
}
}
}

View File

@ -1,6 +1,7 @@
// 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.
using osu.Framework.Bindables;
using osu.Framework.Graphics; using osu.Framework.Graphics;
namespace osu.Game.Screens.Play.HUD namespace osu.Game.Screens.Play.HUD
@ -25,13 +26,38 @@ namespace osu.Game.Screens.Play.HUD
public event OnActivateCallback? OnActivate; public event OnActivateCallback? OnActivate;
public event OnDeactivateCallback? OnDeactivate; public event OnDeactivateCallback? OnDeactivate;
private readonly Bindable<int> activationCount = new BindableInt();
private readonly Bindable<bool> isCounting = new BindableBool(true);
/// <summary>
/// Number of times this <see cref="InputTrigger"/> has been activated.
/// </summary>
public IBindable<int> ActivationCount => activationCount;
/// <summary>
/// Whether any activation or deactivation of this <see cref="InputTrigger"/> impacts its <see cref="ActivationCount"/>
/// </summary>
public IBindable<bool> IsCounting => isCounting;
protected InputTrigger(string name) protected InputTrigger(string name)
{ {
Name = name; Name = name;
} }
protected void Activate(bool forwardPlayback = true) => OnActivate?.Invoke(forwardPlayback); protected void Activate(bool forwardPlayback = true)
{
if (forwardPlayback && isCounting.Value)
activationCount.Value++;
protected void Deactivate(bool forwardPlayback = true) => OnDeactivate?.Invoke(forwardPlayback); OnActivate?.Invoke(forwardPlayback);
}
protected void Deactivate(bool forwardPlayback = true)
{
if (!forwardPlayback && isCounting.Value)
activationCount.Value--;
OnDeactivate?.Invoke(forwardPlayback);
}
} }
} }

View File

@ -5,7 +5,7 @@ using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
@ -16,7 +16,7 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter
/// Keeps track of judgements for a current play session, exposing bindable counts which can /// Keeps track of judgements for a current play session, exposing bindable counts which can
/// be used for display purposes. /// be used for display purposes.
/// </summary> /// </summary>
public partial class JudgementTally : CompositeDrawable public partial class JudgementCountController : Component
{ {
[Resolved] [Resolved]
private ScoreProcessor scoreProcessor { get; set; } = null!; private ScoreProcessor scoreProcessor { get; set; } = null!;

View File

@ -18,9 +18,9 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter
public BindableBool ShowName = new BindableBool(); public BindableBool ShowName = new BindableBool();
public Bindable<FillDirection> Direction = new Bindable<FillDirection>(); public Bindable<FillDirection> Direction = new Bindable<FillDirection>();
public readonly JudgementTally.JudgementCount Result; public readonly JudgementCountController.JudgementCount Result;
public JudgementCounter(JudgementTally.JudgementCount result) => Result = result; public JudgementCounter(JudgementCountController.JudgementCount result) => Result = result;
public OsuSpriteText ResultName = null!; public OsuSpriteText ResultName = null!;
private FillFlowContainer flowContainer = null!; private FillFlowContainer flowContainer = null!;

View File

@ -34,7 +34,7 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter
public BindableBool ShowMaxJudgement { get; set; } = new BindableBool(true); public BindableBool ShowMaxJudgement { get; set; } = new BindableBool(true);
[Resolved] [Resolved]
private JudgementTally tally { get; set; } = null!; private JudgementCountController judgementCountController { get; set; } = null!;
protected FillFlowContainer<JudgementCounter> CounterFlow = null!; protected FillFlowContainer<JudgementCounter> CounterFlow = null!;
@ -49,7 +49,7 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter
AutoSizeAxes = Axes.Both AutoSizeAxes = Axes.Both
}; };
foreach (var result in tally.Results) foreach (var result in judgementCountController.Results)
CounterFlow.Add(createCounter(result)); CounterFlow.Add(createCounter(result));
} }
@ -123,7 +123,7 @@ namespace osu.Game.Screens.Play.HUD.JudgementCounter
} }
} }
private JudgementCounter createCounter(JudgementTally.JudgementCount info) => private JudgementCounter createCounter(JudgementCountController.JudgementCount info) =>
new JudgementCounter(info) new JudgementCounter(info)
{ {
State = { Value = Visibility.Hidden }, State = { Value = Visibility.Hidden },

View File

@ -2,7 +2,6 @@
// 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.Bindables; using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
namespace osu.Game.Screens.Play.HUD namespace osu.Game.Screens.Play.HUD
@ -17,24 +16,10 @@ namespace osu.Game.Screens.Play.HUD
/// </summary> /// </summary>
public readonly InputTrigger Trigger; public readonly InputTrigger Trigger;
/// <summary>
/// Whether the actions reported by <see cref="Trigger"/> should be counted.
/// </summary>
public Bindable<bool> IsCounting { get; } = new BindableBool(true);
private readonly Bindable<int> countPresses = new BindableInt
{
MinValue = 0
};
/// <summary> /// <summary>
/// The current count of registered key presses. /// The current count of registered key presses.
/// </summary> /// </summary>
public IBindable<int> CountPresses => countPresses; public IBindable<int> CountPresses => Trigger.ActivationCount;
private readonly Container content;
protected override Container<Drawable> Content => content;
/// <summary> /// <summary>
/// Whether this <see cref="KeyCounter"/> is currently in the "activated" state because the associated key is currently pressed. /// Whether this <see cref="KeyCounter"/> is currently in the "activated" state because the associated key is currently pressed.
@ -43,52 +28,26 @@ namespace osu.Game.Screens.Play.HUD
protected KeyCounter(InputTrigger trigger) protected KeyCounter(InputTrigger trigger)
{ {
InternalChildren = new Drawable[] Trigger = trigger;
{
content = new Container
{
RelativeSizeAxes = Axes.Both
},
Trigger = trigger,
};
Trigger.OnActivate += Activate; Trigger.OnActivate += Activate;
Trigger.OnDeactivate += Deactivate; Trigger.OnDeactivate += Deactivate;
} }
private void increment()
{
if (!IsCounting.Value)
return;
countPresses.Value++;
}
private void decrement()
{
if (!IsCounting.Value)
return;
countPresses.Value--;
}
protected virtual void Activate(bool forwardPlayback = true) protected virtual void Activate(bool forwardPlayback = true)
{ {
IsActive.Value = true; IsActive.Value = true;
if (forwardPlayback)
increment();
} }
protected virtual void Deactivate(bool forwardPlayback = true) protected virtual void Deactivate(bool forwardPlayback = true)
{ {
IsActive.Value = false; IsActive.Value = false;
if (!forwardPlayback)
decrement();
} }
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
{ {
base.Dispose(isDisposing); base.Dispose(isDisposing);
Trigger.OnActivate -= Activate; Trigger.OnActivate -= Activate;
Trigger.OnDeactivate -= Deactivate; Trigger.OnDeactivate -= Deactivate;
} }

View File

@ -2,10 +2,12 @@
// 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 System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
namespace osu.Game.Screens.Play.HUD namespace osu.Game.Screens.Play.HUD
{ {
public partial class KeyCounterActionTrigger<T> : InputTrigger public partial class KeyCounterActionTrigger<T> : InputTrigger, IKeyBindingHandler<T>
where T : struct where T : struct
{ {
public T Action { get; } public T Action { get; }
@ -16,21 +18,21 @@ namespace osu.Game.Screens.Play.HUD
Action = action; Action = action;
} }
public bool OnPressed(T action, bool forwards) public bool OnPressed(KeyBindingPressEvent<T> e)
{ {
if (!EqualityComparer<T>.Default.Equals(action, Action)) if (!EqualityComparer<T>.Default.Equals(e.Action, Action))
return false; return false;
Activate(forwards); Activate(Clock.Rate >= 0);
return false; return false;
} }
public void OnReleased(T action, bool forwards) public void OnReleased(KeyBindingReleaseEvent<T> e)
{ {
if (!EqualityComparer<T>.Default.Equals(action, Action)) if (!EqualityComparer<T>.Default.Equals(e.Action, Action))
return; return;
Deactivate(forwards); Deactivate(Clock.Rate >= 0);
} }
} }
} }

View File

@ -1,24 +1,20 @@
// 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.
using System; using System.Collections.Specialized;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Game.Configuration; using osu.Game.Configuration;
using osuTK; using osu.Game.Rulesets.UI;
using osu.Game.Skinning;
namespace osu.Game.Screens.Play.HUD namespace osu.Game.Screens.Play.HUD
{ {
/// <summary> /// <summary>
/// A flowing display of all gameplay keys. Individual keys can be added using <see cref="InputTrigger"/> implementations. /// A flowing display of all gameplay keys. Individual keys can be added using <see cref="InputTrigger"/> implementations.
/// </summary> /// </summary>
public abstract partial class KeyCounterDisplay : CompositeDrawable public abstract partial class KeyCounterDisplay : CompositeDrawable, ISerialisableDrawable
{ {
/// <summary> /// <summary>
/// Whether the key counter should be visible regardless of the configuration value. /// Whether the key counter should be visible regardless of the configuration value.
@ -26,95 +22,46 @@ namespace osu.Game.Screens.Play.HUD
/// </summary> /// </summary>
public Bindable<bool> AlwaysVisible { get; } = new Bindable<bool>(true); public Bindable<bool> AlwaysVisible { get; } = new Bindable<bool>(true);
/// <summary>
/// The <see cref="KeyCounter"/>s contained in this <see cref="KeyCounterDisplay"/>.
/// </summary>
public IEnumerable<KeyCounter> Counters => KeyFlow;
protected abstract FillFlowContainer<KeyCounter> KeyFlow { get; } protected abstract FillFlowContainer<KeyCounter> KeyFlow { get; }
/// <summary>
/// Whether the actions reported by all <see cref="InputTrigger"/>s within this <see cref="KeyCounterDisplay"/> should be counted.
/// </summary>
public Bindable<bool> IsCounting { get; } = new BindableBool(true);
protected readonly Bindable<bool> ConfigVisibility = new Bindable<bool>(); protected readonly Bindable<bool> ConfigVisibility = new Bindable<bool>();
private readonly IBindableList<InputTrigger> triggers = new BindableList<InputTrigger>();
[Resolved]
private InputCountController controller { get; set; } = null!;
protected abstract void UpdateVisibility(); protected abstract void UpdateVisibility();
private Receptor? receptor;
public void SetReceptor(Receptor receptor)
{
if (this.receptor != null)
throw new InvalidOperationException("Cannot set a new receptor when one is already active");
this.receptor = receptor;
}
/// <summary>
/// Add a <see cref="InputTrigger"/> to this display.
/// </summary>
public void Add(InputTrigger trigger)
{
var keyCounter = CreateCounter(trigger);
KeyFlow.Add(keyCounter);
IsCounting.BindTo(keyCounter.IsCounting);
}
/// <summary>
/// Add a range of <see cref="InputTrigger"/> to this display.
/// </summary>
public void AddRange(IEnumerable<InputTrigger> triggers) => triggers.ForEach(Add);
protected abstract KeyCounter CreateCounter(InputTrigger trigger); protected abstract KeyCounter CreateCounter(InputTrigger trigger);
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuConfigManager config) private void load(OsuConfigManager config, DrawableRuleset? drawableRuleset)
{ {
config.BindWith(OsuSetting.KeyOverlay, ConfigVisibility); config.BindWith(OsuSetting.KeyOverlay, ConfigVisibility);
if (drawableRuleset != null)
AlwaysVisible.BindTo(drawableRuleset.HasReplayLoaded);
} }
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
triggers.BindTo(controller.Triggers);
triggers.BindCollectionChanged(triggersChanged, true);
AlwaysVisible.BindValueChanged(_ => UpdateVisibility()); AlwaysVisible.BindValueChanged(_ => UpdateVisibility());
ConfigVisibility.BindValueChanged(_ => UpdateVisibility(), true); ConfigVisibility.BindValueChanged(_ => UpdateVisibility(), true);
} }
public override bool HandleNonPositionalInput => receptor == null; private void triggersChanged(object? sender, NotifyCollectionChangedEventArgs e)
public override bool HandlePositionalInput => receptor == null;
public partial class Receptor : Drawable
{ {
protected readonly KeyCounterDisplay Target; KeyFlow.Clear();
foreach (var trigger in controller.Triggers)
public Receptor(KeyCounterDisplay target) KeyFlow.Add(CreateCounter(trigger));
{
RelativeSizeAxes = Axes.Both;
Depth = float.MinValue;
Target = target;
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
protected override bool Handle(UIEvent e)
{
switch (e)
{
case KeyDownEvent:
case KeyUpEvent:
case MouseDownEvent:
case MouseUpEvent:
return Target.InternalChildren.Any(c => c.TriggerEvent(e));
}
return base.Handle(e);
}
} }
public bool UsesFixedAnchor { get; set; }
} }
} }

View File

@ -16,8 +16,10 @@ using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osu.Game.Localisation;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -26,8 +28,6 @@ using osu.Game.Screens.Play.HUD.ClicksPerSecond;
using osu.Game.Screens.Play.HUD.JudgementCounter; using osu.Game.Screens.Play.HUD.JudgementCounter;
using osu.Game.Skinning; using osu.Game.Skinning;
using osuTK; using osuTK;
using osu.Game.Localisation;
using osu.Game.Rulesets;
namespace osu.Game.Screens.Play namespace osu.Game.Screens.Play
{ {
@ -54,16 +54,18 @@ namespace osu.Game.Screens.Play
return child == bottomRightElements; return child == bottomRightElements;
} }
public readonly KeyCounterDisplay KeyCounter;
public readonly ModDisplay ModDisplay; public readonly ModDisplay ModDisplay;
public readonly HoldForMenuButton HoldToQuit; public readonly HoldForMenuButton HoldToQuit;
public readonly PlayerSettingsOverlay PlayerSettingsOverlay; public readonly PlayerSettingsOverlay PlayerSettingsOverlay;
[Cached] [Cached]
private readonly ClicksPerSecondCalculator clicksPerSecondCalculator; private readonly ClicksPerSecondController clicksPerSecondController;
[Cached] [Cached]
private readonly JudgementTally tally; public readonly InputCountController InputCountController;
[Cached]
private readonly JudgementCountController judgementCountController;
public Bindable<bool> ShowHealthBar = new Bindable<bool>(true); public Bindable<bool> ShowHealthBar = new Bindable<bool>(true);
@ -113,7 +115,9 @@ namespace osu.Game.Screens.Play
{ {
CreateFailingLayer(), CreateFailingLayer(),
//Needs to be initialized before skinnable drawables. //Needs to be initialized before skinnable drawables.
tally = new JudgementTally(), judgementCountController = new JudgementCountController(),
clicksPerSecondController = new ClicksPerSecondController(),
InputCountController = new InputCountController(),
mainComponents = new HUDComponentsContainer { AlwaysPresent = true, }, mainComponents = new HUDComponentsContainer { AlwaysPresent = true, },
rulesetComponents = drawableRuleset != null rulesetComponents = drawableRuleset != null
? new HUDComponentsContainer(drawableRuleset.Ruleset.RulesetInfo) { AlwaysPresent = true, } ? new HUDComponentsContainer(drawableRuleset.Ruleset.RulesetInfo) { AlwaysPresent = true, }
@ -145,7 +149,6 @@ namespace osu.Game.Screens.Play
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
Children = new Drawable[] Children = new Drawable[]
{ {
KeyCounter = CreateKeyCounter(),
HoldToQuit = CreateHoldForMenuButton(), HoldToQuit = CreateHoldForMenuButton(),
} }
}, },
@ -156,10 +159,9 @@ namespace osu.Game.Screens.Play
Padding = new MarginPadding(44), // enough margin to avoid the hit error display Padding = new MarginPadding(44), // enough margin to avoid the hit error display
Spacing = new Vector2(5) Spacing = new Vector2(5)
}, },
clicksPerSecondCalculator = new ClicksPerSecondCalculator(),
}; };
hideTargets = new List<Drawable> { mainComponents, rulesetComponents, KeyCounter, topRightElements }; hideTargets = new List<Drawable> { mainComponents, rulesetComponents, topRightElements };
if (!alwaysShowLeaderboard) if (!alwaysShowLeaderboard)
hideTargets.Add(LeaderboardFlow); hideTargets.Add(LeaderboardFlow);
@ -303,13 +305,13 @@ namespace osu.Game.Screens.Play
{ {
PlayerSettingsOverlay.Show(); PlayerSettingsOverlay.Show();
ModDisplay.FadeIn(200); ModDisplay.FadeIn(200);
KeyCounter.Margin = new MarginPadding(10) { Bottom = 30 }; InputCountController.Margin = new MarginPadding(10) { Bottom = 30 };
} }
else else
{ {
PlayerSettingsOverlay.Hide(); PlayerSettingsOverlay.Hide();
ModDisplay.Delay(2000).FadeOut(200); ModDisplay.Delay(2000).FadeOut(200);
KeyCounter.Margin = new MarginPadding(10); InputCountController.Margin = new MarginPadding(10);
} }
updateVisibility(); updateVisibility();
@ -319,8 +321,8 @@ namespace osu.Game.Screens.Play
{ {
if (drawableRuleset is ICanAttachHUDPieces attachTarget) if (drawableRuleset is ICanAttachHUDPieces attachTarget)
{ {
attachTarget.Attach(KeyCounter); attachTarget.Attach(InputCountController);
attachTarget.Attach(clicksPerSecondCalculator); attachTarget.Attach(clicksPerSecondController);
} }
replayLoaded.BindTo(drawableRuleset.HasReplayLoaded); replayLoaded.BindTo(drawableRuleset.HasReplayLoaded);
@ -331,12 +333,6 @@ namespace osu.Game.Screens.Play
ShowHealth = { BindTarget = ShowHealthBar } ShowHealth = { BindTarget = ShowHealthBar }
}; };
protected KeyCounterDisplay CreateKeyCounter() => new DefaultKeyCounterDisplay
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
};
protected HoldForMenuButton CreateHoldForMenuButton() => new HoldForMenuButton protected HoldForMenuButton CreateHoldForMenuButton() => new HoldForMenuButton
{ {
Anchor = Anchor.BottomRight, Anchor = Anchor.BottomRight,

View File

@ -429,13 +429,12 @@ namespace osu.Game.Screens.Play
IsPaused = { BindTarget = GameplayClockContainer.IsPaused }, IsPaused = { BindTarget = GameplayClockContainer.IsPaused },
ReplayLoaded = { BindTarget = DrawableRuleset.HasReplayLoaded }, ReplayLoaded = { BindTarget = DrawableRuleset.HasReplayLoaded },
}, },
KeyCounter = InputCountController =
{ {
IsCounting = IsCounting =
{ {
Value = false Value = false
}, },
AlwaysVisible = { BindTarget = DrawableRuleset.HasReplayLoaded },
}, },
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre Origin = Anchor.Centre
@ -475,7 +474,7 @@ namespace osu.Game.Screens.Play
{ {
updateGameplayState(); updateGameplayState();
updatePauseOnFocusLostState(); updatePauseOnFocusLostState();
HUDOverlay.KeyCounter.IsCounting.Value = !isBreakTime.NewValue; HUDOverlay.InputCountController.IsCounting.Value = !isBreakTime.NewValue;
} }
private void updateGameplayState() private void updateGameplayState()

View File

@ -57,6 +57,8 @@ namespace osu.Game.Screens.Select.Carousel
match &= !criteria.Creator.HasFilter || criteria.Creator.Matches(BeatmapInfo.Metadata.Author.Username); match &= !criteria.Creator.HasFilter || criteria.Creator.Matches(BeatmapInfo.Metadata.Author.Username);
match &= !criteria.Artist.HasFilter || criteria.Artist.Matches(BeatmapInfo.Metadata.Artist) || match &= !criteria.Artist.HasFilter || criteria.Artist.Matches(BeatmapInfo.Metadata.Artist) ||
criteria.Artist.Matches(BeatmapInfo.Metadata.ArtistUnicode); criteria.Artist.Matches(BeatmapInfo.Metadata.ArtistUnicode);
match &= !criteria.Title.HasFilter || criteria.Title.Matches(BeatmapInfo.Metadata.Title) ||
criteria.Title.Matches(BeatmapInfo.Metadata.TitleUnicode);
match &= !criteria.UserStarDifficulty.HasFilter || criteria.UserStarDifficulty.IsInRange(BeatmapInfo.StarRating); match &= !criteria.UserStarDifficulty.HasFilter || criteria.UserStarDifficulty.IsInRange(BeatmapInfo.StarRating);

View File

@ -3,6 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -31,6 +32,7 @@ namespace osu.Game.Screens.Select
public OptionalRange<BeatmapOnlineStatus> OnlineStatus; public OptionalRange<BeatmapOnlineStatus> OnlineStatus;
public OptionalTextFilter Creator; public OptionalTextFilter Creator;
public OptionalTextFilter Artist; public OptionalTextFilter Artist;
public OptionalTextFilter Title;
public OptionalRange<double> UserStarDifficulty = new OptionalRange<double> public OptionalRange<double> UserStarDifficulty = new OptionalRange<double>
{ {
@ -62,7 +64,7 @@ namespace osu.Game.Screens.Select
string remainingText = value; string remainingText = value;
// First handle quoted segments to ensure we keep inline spaces in exact matches. // First handle quoted segments to ensure we keep inline spaces in exact matches.
foreach (Match quotedSegment in Regex.Matches(searchText, "(\"[^\"]+\")")) foreach (Match quotedSegment in Regex.Matches(searchText, "(\"[^\"]+\"[!]?)"))
{ {
terms.Add(new OptionalTextFilter { SearchTerm = quotedSegment.Value }); terms.Add(new OptionalTextFilter { SearchTerm = quotedSegment.Value });
remainingText = remainingText.Replace(quotedSegment.Value, string.Empty); remainingText = remainingText.Replace(quotedSegment.Value, string.Empty);
@ -138,7 +140,7 @@ namespace osu.Game.Screens.Select
{ {
public bool HasFilter => !string.IsNullOrEmpty(SearchTerm); public bool HasFilter => !string.IsNullOrEmpty(SearchTerm);
public bool Exact { get; private set; } public MatchMode MatchMode { get; private set; }
public bool Matches(string value) public bool Matches(string value)
{ {
@ -149,10 +151,18 @@ namespace osu.Game.Screens.Select
if (string.IsNullOrEmpty(value)) if (string.IsNullOrEmpty(value))
return false; return false;
if (Exact) switch (MatchMode)
return Regex.IsMatch(value, $@"(^|\s){Regex.Escape(searchTerm)}($|\s)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); {
default:
case MatchMode.Substring:
return value.Contains(SearchTerm, StringComparison.InvariantCultureIgnoreCase);
return value.Contains(SearchTerm, StringComparison.InvariantCultureIgnoreCase); case MatchMode.IsolatedPhrase:
return Regex.IsMatch(value, $@"(^|\s){Regex.Escape(searchTerm)}($|\s)", RegexOptions.IgnoreCase | RegexOptions.CultureInvariant);
case MatchMode.FullPhrase:
return CultureInfo.InvariantCulture.CompareInfo.Compare(value, searchTerm, CompareOptions.IgnoreCase) == 0;
}
} }
private string searchTerm; private string searchTerm;
@ -162,12 +172,46 @@ namespace osu.Game.Screens.Select
get => searchTerm; get => searchTerm;
set set
{ {
searchTerm = value.Trim('"'); searchTerm = value;
Exact = searchTerm != value;
if (searchTerm.StartsWith('\"'))
{
// length check ensures that the quote character in the `StartsWith()` check above and the `EndsWith()` check below is not the same character.
if (searchTerm.EndsWith("\"!", StringComparison.Ordinal) && searchTerm.Length >= 3)
{
searchTerm = searchTerm.TrimEnd('!').Trim('\"');
MatchMode = MatchMode.FullPhrase;
}
else
{
searchTerm = searchTerm.Trim('\"');
MatchMode = MatchMode.IsolatedPhrase;
}
}
else
MatchMode = MatchMode.Substring;
} }
} }
public bool Equals(OptionalTextFilter other) => SearchTerm == other.SearchTerm; public bool Equals(OptionalTextFilter other) => SearchTerm == other.SearchTerm;
} }
public enum MatchMode
{
/// <summary>
/// Match using a simple "contains" substring match.
/// </summary>
Substring,
/// <summary>
/// Match for the search phrase being isolated by spaces, or at the start or end of the text.
/// </summary>
IsolatedPhrase,
/// <summary>
/// Match for the search phrase matching the full text in completion.
/// </summary>
FullPhrase,
}
} }
} }

View File

@ -16,7 +16,7 @@ namespace osu.Game.Screens.Select
public static class FilterQueryParser public static class FilterQueryParser
{ {
private static readonly Regex query_syntax_regex = new Regex( private static readonly Regex query_syntax_regex = new Regex(
@"\b(?<key>\w+)(?<op>(:|=|(>|<)(:|=)?))(?<value>("".*"")|(\S*))", @"\b(?<key>\w+)(?<op>(:|=|(>|<)(:|=)?))(?<value>("".*""[!]?)|(\S*))",
RegexOptions.Compiled | RegexOptions.IgnoreCase); RegexOptions.Compiled | RegexOptions.IgnoreCase);
internal static void ApplyQueries(FilterCriteria criteria, string query) internal static void ApplyQueries(FilterCriteria criteria, string query)
@ -73,6 +73,9 @@ namespace osu.Game.Screens.Select
case "artist": case "artist":
return TryUpdateCriteriaText(ref criteria.Artist, op, value); return TryUpdateCriteriaText(ref criteria.Artist, op, value);
case "title":
return TryUpdateCriteriaText(ref criteria.Title, op, value);
default: default:
return criteria.RulesetCriteria?.TryParseCustomKeywordCriteria(key, op, value) ?? false; return criteria.RulesetCriteria?.TryParseCustomKeywordCriteria(key, op, value) ?? false;
} }

View File

@ -12,6 +12,7 @@ using osu.Game.Audio;
using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Formats;
using osu.Game.Extensions; using osu.Game.Extensions;
using osu.Game.IO; using osu.Game.IO;
using osu.Game.Screens.Play;
using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.HUD.HitErrorMeters; using osu.Game.Screens.Play.HUD.HitErrorMeters;
using osuTK; using osuTK;
@ -113,6 +114,7 @@ namespace osu.Game.Skinning
var combo = container.OfType<DefaultComboCounter>().FirstOrDefault(); var combo = container.OfType<DefaultComboCounter>().FirstOrDefault();
var ppCounter = container.OfType<PerformancePointsCounter>().FirstOrDefault(); var ppCounter = container.OfType<PerformancePointsCounter>().FirstOrDefault();
var songProgress = container.OfType<ArgonSongProgress>().FirstOrDefault(); var songProgress = container.OfType<ArgonSongProgress>().FirstOrDefault();
var keyCounter = container.OfType<ArgonKeyCounterDisplay>().FirstOrDefault();
if (score != null) if (score != null)
{ {
@ -166,8 +168,20 @@ namespace osu.Game.Skinning
if (songProgress != null) if (songProgress != null)
{ {
songProgress.Position = new Vector2(0, -10); const float padding = 10;
songProgress.Position = new Vector2(0, -padding);
songProgress.Scale = new Vector2(0.9f, 1); songProgress.Scale = new Vector2(0.9f, 1);
if (keyCounter != null && hitError != null)
{
// Hard to find this at runtime, so taken from the most expanded state during replay.
const float song_progress_offset_height = 36 + padding;
keyCounter.Anchor = Anchor.BottomRight;
keyCounter.Origin = Anchor.BottomRight;
keyCounter.Position = new Vector2(-(hitError.Width + padding), -(padding * 2 + song_progress_offset_height));
}
} }
} }
}) })
@ -179,6 +193,7 @@ namespace osu.Game.Skinning
new DefaultAccuracyCounter(), new DefaultAccuracyCounter(),
new DefaultHealthDisplay(), new DefaultHealthDisplay(),
new ArgonSongProgress(), new ArgonSongProgress(),
new ArgonKeyCounterDisplay(),
new BarHitErrorMeter(), new BarHitErrorMeter(),
new BarHitErrorMeter(), new BarHitErrorMeter(),
new PerformancePointsCounter() new PerformancePointsCounter()

View File

@ -22,6 +22,7 @@ using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Play.HUD; using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Play.HUD.HitErrorMeters; using osu.Game.Screens.Play.HUD.HitErrorMeters;
using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
namespace osu.Game.Skinning namespace osu.Game.Skinning
@ -372,12 +373,22 @@ namespace osu.Game.Skinning
} }
var hitError = container.OfType<HitErrorMeter>().FirstOrDefault(); var hitError = container.OfType<HitErrorMeter>().FirstOrDefault();
var keyCounter = container.OfType<DefaultKeyCounterDisplay>().FirstOrDefault();
if (hitError != null) if (hitError != null)
{ {
hitError.Anchor = Anchor.BottomCentre; hitError.Anchor = Anchor.BottomCentre;
hitError.Origin = Anchor.CentreLeft; hitError.Origin = Anchor.CentreLeft;
hitError.Rotation = -90; hitError.Rotation = -90;
if (keyCounter != null)
{
const float padding = 10;
keyCounter.Anchor = Anchor.BottomRight;
keyCounter.Origin = Anchor.BottomRight;
keyCounter.Position = new Vector2(-padding, -(padding + hitError.Width));
}
} }
}) })
{ {
@ -389,6 +400,7 @@ namespace osu.Game.Skinning
new LegacyHealthDisplay(), new LegacyHealthDisplay(),
new LegacySongProgress(), new LegacySongProgress(),
new BarHitErrorMeter(), new BarHitErrorMeter(),
new DefaultKeyCounterDisplay()
} }
}; };
} }

View File

@ -90,6 +90,8 @@ namespace osu.Game.Skinning
var accuracy = container.OfType<DefaultAccuracyCounter>().FirstOrDefault(); var accuracy = container.OfType<DefaultAccuracyCounter>().FirstOrDefault();
var combo = container.OfType<DefaultComboCounter>().FirstOrDefault(); var combo = container.OfType<DefaultComboCounter>().FirstOrDefault();
var ppCounter = container.OfType<PerformancePointsCounter>().FirstOrDefault(); var ppCounter = container.OfType<PerformancePointsCounter>().FirstOrDefault();
var songProgress = container.OfType<DefaultSongProgress>().FirstOrDefault();
var keyCounter = container.OfType<DefaultKeyCounterDisplay>().FirstOrDefault();
if (score != null) if (score != null)
{ {
@ -141,6 +143,18 @@ namespace osu.Game.Skinning
hitError2.Origin = Anchor.CentreLeft; hitError2.Origin = Anchor.CentreLeft;
} }
} }
if (songProgress != null && keyCounter != null)
{
const float padding = 10;
// Hard to find this at runtime, so taken from the most expanded state during replay.
const float song_progress_offset_height = 73;
keyCounter.Anchor = Anchor.BottomRight;
keyCounter.Origin = Anchor.BottomRight;
keyCounter.Position = new Vector2(-padding, -(song_progress_offset_height + padding));
}
}) })
{ {
Children = new Drawable[] Children = new Drawable[]
@ -150,6 +164,7 @@ namespace osu.Game.Skinning
new DefaultAccuracyCounter(), new DefaultAccuracyCounter(),
new DefaultHealthDisplay(), new DefaultHealthDisplay(),
new DefaultSongProgress(), new DefaultSongProgress(),
new DefaultKeyCounterDisplay(),
new BarHitErrorMeter(), new BarHitErrorMeter(),
new BarHitErrorMeter(), new BarHitErrorMeter(),
new PerformancePointsCounter() new PerformancePointsCounter()