From c5cb4e4e7d754a7d076a3d91fac2ae7c841c19dd Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Sat, 12 Nov 2022 17:48:35 +0000 Subject: [PATCH 01/57] Add winner of Triangles mapping competition as a bundled beatmap https://osu.ppy.sh/home/news/2022-10-06-results-triangles --- osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs b/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs index 80af4108c7..053ac8fc17 100644 --- a/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs +++ b/osu.Game/Beatmaps/Drawables/BundledBeatmapDownloader.cs @@ -136,7 +136,9 @@ namespace osu.Game.Beatmaps.Drawables private static readonly string[] always_bundled_beatmaps = { // This thing is 40mb, I'm not sure we want it here... - @"1388906 Raphlesia & BilliumMoto - My Love.osz" + @"1388906 Raphlesia & BilliumMoto - My Love.osz", + // Winner of Triangles mapping competition: https://osu.ppy.sh/home/news/2022-10-06-results-triangles + @"1841885 cYsmix - triangles.osz", }; private static readonly string[] bundled_osu = From 3f8c4a5dfff5c094c2bb4ce6100da4f636a2a037 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?H=E1=BB=93=20Nguy=C3=AAn=20Minh?= Date: Sun, 13 Nov 2022 17:09:43 +0700 Subject: [PATCH 02/57] Stack Catch dash/normal touch input vertically --- .../UI/CatchTouchInputMapper.cs | 26 ++++++++++--------- 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs index e6736d6c93..12d695393f 100644 --- a/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs +++ b/osu.Game.Rulesets.Catch/UI/CatchTouchInputMapper.cs @@ -35,6 +35,8 @@ namespace osu.Game.Rulesets.Catch.UI private void load(CatchInputManager catchInputManager, OsuColour colours) { const float width = 0.15f; + // Ratio between normal move area height and total input height + const float normal_area_height_ratio = 0.45f; keyBindingContainer = catchInputManager.KeyBindingContainer; @@ -54,18 +56,18 @@ namespace osu.Game.Rulesets.Catch.UI Width = width, Children = new Drawable[] { - leftDashBox = new InputArea(TouchCatchAction.DashLeft, trackedActionSources) - { - RelativeSizeAxes = Axes.Both, - Width = 0.5f, - }, leftBox = new InputArea(TouchCatchAction.MoveLeft, trackedActionSources) { RelativeSizeAxes = Axes.Both, - Width = 0.5f, + Height = normal_area_height_ratio, Colour = colours.Gray9, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + leftDashBox = new InputArea(TouchCatchAction.DashLeft, trackedActionSources) + { + RelativeSizeAxes = Axes.Both, + Height = 1 - normal_area_height_ratio, }, } }, @@ -80,15 +82,15 @@ namespace osu.Game.Rulesets.Catch.UI rightBox = new InputArea(TouchCatchAction.MoveRight, trackedActionSources) { RelativeSizeAxes = Axes.Both, - Width = 0.5f, + Height = normal_area_height_ratio, Colour = colours.Gray9, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, }, rightDashBox = new InputArea(TouchCatchAction.DashRight, trackedActionSources) { RelativeSizeAxes = Axes.Both, - Width = 0.5f, - Anchor = Anchor.TopRight, - Origin = Anchor.TopRight, + Height = 1 - normal_area_height_ratio, }, } }, From a1af663682cce72d49244c194f072a6a1f064fe5 Mon Sep 17 00:00:00 2001 From: Dragon Date: Sun, 13 Nov 2022 20:49:26 +0100 Subject: [PATCH 03/57] Implemented previous messages lookup in the ChatTextBox.cs --- osu.Game/Overlays/Chat/ChatTextBox.cs | 57 +++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/osu.Game/Overlays/Chat/ChatTextBox.cs b/osu.Game/Overlays/Chat/ChatTextBox.cs index 887eb96c15..fc4c2ae727 100644 --- a/osu.Game/Overlays/Chat/ChatTextBox.cs +++ b/osu.Game/Overlays/Chat/ChatTextBox.cs @@ -1,14 +1,23 @@ // Copyright (c) ppy Pty Ltd . 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.Input.Events; using osu.Game.Graphics.UserInterface; using osu.Game.Resources.Localisation.Web; +using osuTK.Input; namespace osu.Game.Overlays.Chat { public class ChatTextBox : FocusedTextBox { + private readonly List messageHistory = new List(); + + private int messageIndex = -1; + + private string originalMessage = string.Empty; + public readonly BindableBool ShowSearch = new BindableBool(); public override bool HandleLeftRightArrows => !ShowSearch.Value; @@ -28,11 +37,59 @@ namespace osu.Game.Overlays.Chat }, true); } + protected override bool OnKeyDown(KeyDownEvent e) + { + /* Behavior: + * add when on last element -> last element stays + * subtract when on first element -> sets to original text + * reset indexing when Text is set to Empty + */ + + switch (e.Key) + { + case Key.Up: + if (messageIndex == -1) + originalMessage = Text; + + if (messageIndex == messageHistory.Count - 1) + return true; + + Text = messageHistory[++messageIndex]; + + return true; + + case Key.Down: + if (messageIndex == -1) + return true; + + if (messageIndex == 0) + { + messageIndex = -1; + Text = originalMessage; + return true; + } + + Text = messageHistory[--messageIndex]; + + return true; + } + + bool onKeyDown = base.OnKeyDown(e); + + if (string.IsNullOrEmpty(Text)) + messageIndex = -1; + + return onKeyDown; + } + protected override void Commit() { if (ShowSearch.Value) return; + messageHistory.Insert(0, Text); + messageIndex = -1; + base.Commit(); } } From b9590320b750d64ed532ce0621f54019940c1f4c Mon Sep 17 00:00:00 2001 From: Dragon Date: Sun, 13 Nov 2022 22:34:02 +0100 Subject: [PATCH 04/57] Moved implementation to ChatRecentTextBox.cs and derived ChatTextBox.cs and StandAloneChatDisplay.cs from it. --- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 14 ++-- osu.Game/Overlays/Chat/ChatRecentTextBox.cs | 74 +++++++++++++++++++ osu.Game/Overlays/Chat/ChatTextBox.cs | 60 +-------------- 3 files changed, 83 insertions(+), 65 deletions(-) create mode 100644 osu.Game/Overlays/Chat/ChatRecentTextBox.cs diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index 81db3f0d53..de0387e017 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -12,7 +12,6 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Chat; using osu.Game.Resources.Localisation.Web; using osuTK.Graphics; @@ -120,17 +119,20 @@ namespace osu.Game.Online.Chat AddInternal(drawableChannel); } - public class ChatTextBox : FocusedTextBox + public class ChatTextBox : ChatRecentTextBox { protected override bool OnKeyDown(KeyDownEvent e) { // Chat text boxes are generally used in places where they retain focus, but shouldn't block interaction with other // elements on the same screen. - switch (e.Key) + if (!HoldFocus) { - case Key.Up: - case Key.Down: - return false; + switch (e.Key) + { + case Key.Up: + case Key.Down: + return false; + } } return base.OnKeyDown(e); diff --git a/osu.Game/Overlays/Chat/ChatRecentTextBox.cs b/osu.Game/Overlays/Chat/ChatRecentTextBox.cs new file mode 100644 index 0000000000..87bc3ee48c --- /dev/null +++ b/osu.Game/Overlays/Chat/ChatRecentTextBox.cs @@ -0,0 +1,74 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using osu.Framework.Input.Events; +using osu.Game.Graphics.UserInterface; +using osuTK.Input; + +namespace osu.Game.Overlays.Chat +{ + public class ChatRecentTextBox : FocusedTextBox + { + private readonly List messageHistory = new List(); + + private int messageIndex = -1; + + private string originalMessage = string.Empty; + + protected override bool OnKeyDown(KeyDownEvent e) + { + /* Behavior: + * add when on last element -> last element stays + * subtract when on first element -> sets to original text + * reset indexing when Text is set to Empty + */ + + switch (e.Key) + { + case Key.Up: + if (messageIndex == -1) + originalMessage = Text; + + if (messageIndex == messageHistory.Count - 1) + return true; + + Text = messageHistory[++messageIndex]; + + return true; + + case Key.Down: + if (messageIndex == -1) + return true; + + if (messageIndex == 0) + { + messageIndex = -1; + Text = originalMessage; + return true; + } + + Text = messageHistory[--messageIndex]; + + return true; + } + + bool onKeyDown = base.OnKeyDown(e); + + if (string.IsNullOrEmpty(Text)) + messageIndex = -1; + + return onKeyDown; + } + + protected override void Commit() + { + if (!string.IsNullOrEmpty(Text)) + messageHistory.Insert(0, Text); + + messageIndex = -1; + + base.Commit(); + } + } +} diff --git a/osu.Game/Overlays/Chat/ChatTextBox.cs b/osu.Game/Overlays/Chat/ChatTextBox.cs index fc4c2ae727..d3c1a4ad8b 100644 --- a/osu.Game/Overlays/Chat/ChatTextBox.cs +++ b/osu.Game/Overlays/Chat/ChatTextBox.cs @@ -1,23 +1,13 @@ // Copyright (c) ppy Pty Ltd . 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.Input.Events; -using osu.Game.Graphics.UserInterface; using osu.Game.Resources.Localisation.Web; -using osuTK.Input; namespace osu.Game.Overlays.Chat { - public class ChatTextBox : FocusedTextBox + public class ChatTextBox : ChatRecentTextBox { - private readonly List messageHistory = new List(); - - private int messageIndex = -1; - - private string originalMessage = string.Empty; - public readonly BindableBool ShowSearch = new BindableBool(); public override bool HandleLeftRightArrows => !ShowSearch.Value; @@ -37,59 +27,11 @@ namespace osu.Game.Overlays.Chat }, true); } - protected override bool OnKeyDown(KeyDownEvent e) - { - /* Behavior: - * add when on last element -> last element stays - * subtract when on first element -> sets to original text - * reset indexing when Text is set to Empty - */ - - switch (e.Key) - { - case Key.Up: - if (messageIndex == -1) - originalMessage = Text; - - if (messageIndex == messageHistory.Count - 1) - return true; - - Text = messageHistory[++messageIndex]; - - return true; - - case Key.Down: - if (messageIndex == -1) - return true; - - if (messageIndex == 0) - { - messageIndex = -1; - Text = originalMessage; - return true; - } - - Text = messageHistory[--messageIndex]; - - return true; - } - - bool onKeyDown = base.OnKeyDown(e); - - if (string.IsNullOrEmpty(Text)) - messageIndex = -1; - - return onKeyDown; - } - protected override void Commit() { if (ShowSearch.Value) return; - messageHistory.Insert(0, Text); - messageIndex = -1; - base.Commit(); } } From 6d83af01e21228e56753d200e7aee0c94e0b2e67 Mon Sep 17 00:00:00 2001 From: Terochi Date: Tue, 15 Nov 2022 13:06:02 +0100 Subject: [PATCH 05/57] Moved and renamed MessageHistoryTextBox.cs for better fit. --- .../UserInterface/MessageHistoryTextBox.cs} | 21 +++++++++++-------- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 3 ++- osu.Game/Overlays/Chat/ChatTextBox.cs | 3 ++- 3 files changed, 16 insertions(+), 11 deletions(-) rename osu.Game/{Overlays/Chat/ChatRecentTextBox.cs => Graphics/UserInterface/MessageHistoryTextBox.cs} (82%) diff --git a/osu.Game/Overlays/Chat/ChatRecentTextBox.cs b/osu.Game/Graphics/UserInterface/MessageHistoryTextBox.cs similarity index 82% rename from osu.Game/Overlays/Chat/ChatRecentTextBox.cs rename to osu.Game/Graphics/UserInterface/MessageHistoryTextBox.cs index 87bc3ee48c..45497e0451 100644 --- a/osu.Game/Overlays/Chat/ChatRecentTextBox.cs +++ b/osu.Game/Graphics/UserInterface/MessageHistoryTextBox.cs @@ -3,12 +3,11 @@ using System.Collections.Generic; using osu.Framework.Input.Events; -using osu.Game.Graphics.UserInterface; using osuTK.Input; -namespace osu.Game.Overlays.Chat +namespace osu.Game.Graphics.UserInterface { - public class ChatRecentTextBox : FocusedTextBox + public class MessageHistoryTextBox : FocusedTextBox { private readonly List messageHistory = new List(); @@ -16,6 +15,15 @@ namespace osu.Game.Overlays.Chat private string originalMessage = string.Empty; + public MessageHistoryTextBox() + { + Current.ValueChanged += text => + { + if (string.IsNullOrEmpty(text.NewValue)) + messageIndex = -1; + }; + } + protected override bool OnKeyDown(KeyDownEvent e) { /* Behavior: @@ -53,12 +61,7 @@ namespace osu.Game.Overlays.Chat return true; } - bool onKeyDown = base.OnKeyDown(e); - - if (string.IsNullOrEmpty(Text)) - messageIndex = -1; - - return onKeyDown; + return base.OnKeyDown(e); } protected override void Commit() diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index de0387e017..fe279d50a9 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -12,6 +12,7 @@ using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Chat; using osu.Game.Resources.Localisation.Web; using osuTK.Graphics; @@ -119,7 +120,7 @@ namespace osu.Game.Online.Chat AddInternal(drawableChannel); } - public class ChatTextBox : ChatRecentTextBox + public class ChatTextBox : MessageHistoryTextBox { protected override bool OnKeyDown(KeyDownEvent e) { diff --git a/osu.Game/Overlays/Chat/ChatTextBox.cs b/osu.Game/Overlays/Chat/ChatTextBox.cs index d3c1a4ad8b..26ff4a5b4e 100644 --- a/osu.Game/Overlays/Chat/ChatTextBox.cs +++ b/osu.Game/Overlays/Chat/ChatTextBox.cs @@ -2,11 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Bindables; +using osu.Game.Graphics.UserInterface; using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.Chat { - public class ChatTextBox : ChatRecentTextBox + public class ChatTextBox : MessageHistoryTextBox { public readonly BindableBool ShowSearch = new BindableBool(); From a79af6671eceb918dd598a0c60d997030f57fdcb Mon Sep 17 00:00:00 2001 From: Terochi Date: Tue, 15 Nov 2022 13:07:05 +0100 Subject: [PATCH 06/57] Added SetUp for new tests. --- .../Online/TestSceneChatManipulation.cs | 74 +++++++++++++++++++ 1 file changed, 74 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneChatManipulation.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatManipulation.cs b/osu.Game.Tests/Visual/Online/TestSceneChatManipulation.cs new file mode 100644 index 0000000000..6de2584c72 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneChatManipulation.cs @@ -0,0 +1,74 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable disable + +using NUnit.Framework; +using osu.Framework.Graphics; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Overlays.Chat; + +namespace osu.Game.Tests.Visual.Online +{ + [TestFixture] + public class TestSceneChatManipulation : OsuTestScene + { + private ChatTextBox box; + private OsuSpriteText text; + + [SetUp] + public void SetUp() + { + Schedule(() => + { + Children = new Drawable[] + { + box = new ChatTextBox + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Width = 0.99f, + }, + text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Width = 0.99f, + Y = -box.Height, + Font = OsuFont.Default.With(size: 20), + } + }; + box.OnCommit += (_, __) => + { + text.Text = $"{nameof(box.OnCommit)}: {box.Text}"; + box.Text = string.Empty; + box.TakeFocus(); + text.FadeOutFromOne(1000, Easing.InQuint); + }; + }); + } + + [Test] + public void TestReachingLimitOfMessages() + { + } + + [Test] + public void TestStayOnLastIndex() + { + } + + [Test] + public void TestKeepOriginalMessage() + { + } + + [Test] + public void TestResetIndexOnEmpty() + { + } + } +} From 3d4962e1810e3f53e22125cf1012a5c036dade05 Mon Sep 17 00:00:00 2001 From: Terochi Date: Tue, 15 Nov 2022 16:12:24 +0100 Subject: [PATCH 07/57] Added functioning tests. --- .../Online/TestSceneChatManipulation.cs | 59 +++++++++++++++++-- ...ageHistoryTextBox.cs => HistoryTextBox.cs} | 32 +++++----- osu.Game/Online/Chat/StandAloneChatDisplay.cs | 2 +- osu.Game/Overlays/Chat/ChatTextBox.cs | 2 +- 4 files changed, 69 insertions(+), 26 deletions(-) rename osu.Game/Graphics/UserInterface/{MessageHistoryTextBox.cs => HistoryTextBox.cs} (62%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatManipulation.cs b/osu.Game.Tests/Visual/Online/TestSceneChatManipulation.cs index 6de2584c72..4fe0cb685e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatManipulation.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatManipulation.cs @@ -3,16 +3,18 @@ #nullable disable +using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Overlays.Chat; +using osuTK.Input; namespace osu.Game.Tests.Visual.Online { [TestFixture] - public class TestSceneChatManipulation : OsuTestScene + public class TestSceneChatManipulation : OsuManualInputManagerTestScene { private ChatTextBox box; private OsuSpriteText text; @@ -41,6 +43,7 @@ namespace osu.Game.Tests.Visual.Online Font = OsuFont.Default.With(size: 20), } }; + box.OnCommit += (_, __) => { text.Text = $"{nameof(box.OnCommit)}: {box.Text}"; @@ -48,27 +51,71 @@ namespace osu.Game.Tests.Visual.Online box.TakeFocus(); text.FadeOutFromOne(1000, Easing.InQuint); }; - }); - } - [Test] - public void TestReachingLimitOfMessages() - { + box.TakeFocus(); + }); } [Test] public void TestStayOnLastIndex() { + addMessages(2); + AddRepeatStep("Move to last", () => InputManager.Key(Key.Up), 2); + + string lastText = string.Empty; + AddStep("Move up", () => + { + lastText = box.Text; + InputManager.Key(Key.Up); + }); + + AddAssert("Text hasn't changed", () => lastText == box.Text); } [Test] public void TestKeepOriginalMessage() { + addMessages(1); + AddStep("Start writing", () => box.Text = "A random 文, ..."); + + AddStep("Move up", () => InputManager.Key(Key.Up)); + AddStep("Rewrite old message", () => box.Text = "Old Message"); + + AddStep("Move back down", () => InputManager.Key(Key.Down)); + AddAssert("Text back to previous", () => box.Text == "A random 文, ..."); } [Test] public void TestResetIndexOnEmpty() { + addMessages(2); + AddRepeatStep("Move up", () => InputManager.Key(Key.Up), 2); + AddStep("Remove text", () => box.Text = string.Empty); + + AddStep("Move up again", () => InputManager.Key(Key.Up)); + AddAssert("Back to first message", () => box.Text == "Message 2"); + } + + [Test] + public void TestReachingLimitOfMessages() + { + addMessages(100); + AddAssert("List is full of <100-1>", () => + Enumerable.Range(0, 100).Select(number => $"Message {100 - number}").SequenceEqual(box.MessageHistory)); + + addMessages(2); + AddAssert("List is full of <102-3>", () => + Enumerable.Range(0, 100).Select(number => $"Message {102 - number}").SequenceEqual(box.MessageHistory)); + } + + private void addMessages(int count) + { + int iterations = 0; + AddRepeatStep("Add messages", () => + { + box.Text = $"Message {++iterations}"; + InputManager.Key(Key.Enter); + }, count); } } } diff --git a/osu.Game/Graphics/UserInterface/MessageHistoryTextBox.cs b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs similarity index 62% rename from osu.Game/Graphics/UserInterface/MessageHistoryTextBox.cs rename to osu.Game/Graphics/UserInterface/HistoryTextBox.cs index 45497e0451..1b553576d5 100644 --- a/osu.Game/Graphics/UserInterface/MessageHistoryTextBox.cs +++ b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs @@ -7,56 +7,52 @@ using osuTK.Input; namespace osu.Game.Graphics.UserInterface { - public class MessageHistoryTextBox : FocusedTextBox + public class HistoryTextBox : FocusedTextBox { private readonly List messageHistory = new List(); - private int messageIndex = -1; + public IReadOnlyList MessageHistory => messageHistory; + + private int historyIndex = -1; private string originalMessage = string.Empty; - public MessageHistoryTextBox() + public HistoryTextBox() { Current.ValueChanged += text => { if (string.IsNullOrEmpty(text.NewValue)) - messageIndex = -1; + historyIndex = -1; }; } protected override bool OnKeyDown(KeyDownEvent e) { - /* Behavior: - * add when on last element -> last element stays - * subtract when on first element -> sets to original text - * reset indexing when Text is set to Empty - */ - switch (e.Key) { case Key.Up: - if (messageIndex == -1) + if (historyIndex == -1) originalMessage = Text; - if (messageIndex == messageHistory.Count - 1) + if (historyIndex == messageHistory.Count - 1) return true; - Text = messageHistory[++messageIndex]; + Text = messageHistory[++historyIndex]; return true; case Key.Down: - if (messageIndex == -1) + if (historyIndex == -1) return true; - if (messageIndex == 0) + if (historyIndex == 0) { - messageIndex = -1; + historyIndex = -1; Text = originalMessage; return true; } - Text = messageHistory[--messageIndex]; + Text = messageHistory[--historyIndex]; return true; } @@ -69,7 +65,7 @@ namespace osu.Game.Graphics.UserInterface if (!string.IsNullOrEmpty(Text)) messageHistory.Insert(0, Text); - messageIndex = -1; + historyIndex = -1; base.Commit(); } diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index fe279d50a9..03728b427f 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -120,7 +120,7 @@ namespace osu.Game.Online.Chat AddInternal(drawableChannel); } - public class ChatTextBox : MessageHistoryTextBox + public class ChatTextBox : HistoryTextBox { protected override bool OnKeyDown(KeyDownEvent e) { diff --git a/osu.Game/Overlays/Chat/ChatTextBox.cs b/osu.Game/Overlays/Chat/ChatTextBox.cs index 26ff4a5b4e..f0bdbce08d 100644 --- a/osu.Game/Overlays/Chat/ChatTextBox.cs +++ b/osu.Game/Overlays/Chat/ChatTextBox.cs @@ -7,7 +7,7 @@ using osu.Game.Resources.Localisation.Web; namespace osu.Game.Overlays.Chat { - public class ChatTextBox : MessageHistoryTextBox + public class ChatTextBox : HistoryTextBox { public readonly BindableBool ShowSearch = new BindableBool(); From 44c3e71746b43337ad8cbaab0410e73d8f959d54 Mon Sep 17 00:00:00 2001 From: Terochi Date: Tue, 15 Nov 2022 18:10:43 +0100 Subject: [PATCH 08/57] Reversed indexing --- .../Graphics/UserInterface/HistoryTextBox.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs index 1b553576d5..96c0734d63 100644 --- a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs +++ b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs @@ -13,7 +13,7 @@ namespace osu.Game.Graphics.UserInterface public IReadOnlyList MessageHistory => messageHistory; - private int historyIndex = -1; + private int historyIndex; private string originalMessage = string.Empty; @@ -22,7 +22,7 @@ namespace osu.Game.Graphics.UserInterface Current.ValueChanged += text => { if (string.IsNullOrEmpty(text.NewValue)) - historyIndex = -1; + historyIndex = messageHistory.Count; }; } @@ -31,28 +31,28 @@ namespace osu.Game.Graphics.UserInterface switch (e.Key) { case Key.Up: - if (historyIndex == -1) + if (historyIndex == messageHistory.Count) originalMessage = Text; - if (historyIndex == messageHistory.Count - 1) + if (historyIndex == 0) return true; - Text = messageHistory[++historyIndex]; + Text = messageHistory[--historyIndex]; return true; case Key.Down: - if (historyIndex == -1) + if (historyIndex == messageHistory.Count) return true; - if (historyIndex == 0) + if (historyIndex == messageHistory.Count - 1) { - historyIndex = -1; + historyIndex = messageHistory.Count; Text = originalMessage; return true; } - Text = messageHistory[--historyIndex]; + Text = messageHistory[++historyIndex]; return true; } @@ -63,9 +63,9 @@ namespace osu.Game.Graphics.UserInterface protected override void Commit() { if (!string.IsNullOrEmpty(Text)) - messageHistory.Insert(0, Text); + messageHistory.Add(Text); - historyIndex = -1; + historyIndex = messageHistory.Count; base.Commit(); } From 5253f5309e0224ef993786ffb84fd9d7a64f212e Mon Sep 17 00:00:00 2001 From: Terochi Date: Tue, 15 Nov 2022 23:51:57 +0100 Subject: [PATCH 09/57] Added more tests for new features --- .../Online/TestSceneChatManipulation.cs | 75 ++++++++++++++++++- .../Graphics/UserInterface/HistoryTextBox.cs | 57 ++++++++++---- 2 files changed, 116 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatManipulation.cs b/osu.Game.Tests/Visual/Online/TestSceneChatManipulation.cs index 4fe0cb685e..efa4cd41b3 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatManipulation.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatManipulation.cs @@ -8,7 +8,7 @@ using NUnit.Framework; using osu.Framework.Graphics; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Overlays.Chat; +using osu.Game.Graphics.UserInterface; using osuTK.Input; namespace osu.Game.Tests.Visual.Online @@ -16,7 +16,7 @@ namespace osu.Game.Tests.Visual.Online [TestFixture] public class TestSceneChatManipulation : OsuManualInputManagerTestScene { - private ChatTextBox box; + private HistoryTextBox box; private OsuSpriteText text; [SetUp] @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual.Online { Children = new Drawable[] { - box = new ChatTextBox + box = new HistoryTextBox(5) { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -56,6 +56,75 @@ namespace osu.Game.Tests.Visual.Online }); } + [Test] + public void TestEmptyHistory() + { + const string temp = "Temp message"; + AddStep("Set text", () => box.Text = temp); + + AddStep("Move down", () => InputManager.Key(Key.Down)); + AddAssert("Text is the same", () => box.Text == temp); + + AddStep("Move Up", () => InputManager.Key(Key.Up)); + AddAssert("Text is the same", () => box.Text == temp); + } + + [Test] + public void TestPartialHistory() + { + addMessages(2); + + const string temp = "Temp message"; + AddStep("Set text", () => box.Text = temp); + + AddStep("Move down", () => InputManager.Key(Key.Down)); + AddAssert("Text is the same", () => box.Text == temp); + + AddRepeatStep("Move Up", () => InputManager.Key(Key.Up), 2); + AddAssert("Same as 1st message", () => box.Text == "Message 1"); + + AddStep("Move Up", () => InputManager.Key(Key.Up)); + AddAssert("Text is the same", () => box.Text == "Message 1"); + + AddRepeatStep("Move down", () => InputManager.Key(Key.Down), 2); + AddAssert("Same as temp message", () => box.Text == temp); + + AddStep("Move down", () => InputManager.Key(Key.Down)); + AddAssert("Text is the same", () => box.Text == temp); + } + + [Test] + public void TestFullHistory() + { + addMessages(5); + AddAssert("History saved as <1-5>", () => + Enumerable.Range(1, 5).Select(number => $"Message {number}").SequenceEqual(box.MessageHistory)); + + const string temp = "Temp message"; + AddStep("Set text", () => box.Text = temp); + + AddStep("Move down", () => InputManager.Key(Key.Down)); + AddAssert("Text is the same", () => box.Text == temp); + + addMessages(2); + AddAssert("Overwrote history to <3-7>", () => + Enumerable.Range(3, 5).Select(number => $"Message {number}").SequenceEqual(box.MessageHistory)); + + AddStep("Set text", () => box.Text = temp); + + AddRepeatStep("Move Up", () => InputManager.Key(Key.Up), 5); + AddAssert("Same as 3rd message", () => box.Text == "Message 3"); + + AddStep("Move Up", () => InputManager.Key(Key.Up)); + AddAssert("Text is the same", () => box.Text == "Message 3"); + + AddRepeatStep("Move down", () => InputManager.Key(Key.Down), 4); + AddAssert("Same as previous message", () => box.Text == "Message 7"); + + AddStep("Move down", () => InputManager.Key(Key.Down)); + AddAssert("Same as temp message", () => box.Text == temp); + } + [Test] public void TestStayOnLastIndex() { diff --git a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs index 96c0734d63..c91e5dcb41 100644 --- a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs +++ b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using osu.Framework.Input.Events; using osuTK.Input; @@ -9,50 +10,71 @@ namespace osu.Game.Graphics.UserInterface { public class HistoryTextBox : FocusedTextBox { - private readonly List messageHistory = new List(); + private readonly int historyLimit; + + private readonly List messageHistory; public IReadOnlyList MessageHistory => messageHistory; - private int historyIndex; + private int startIndex; private string originalMessage = string.Empty; + private int nullIndex => -1; + private int historyIndex = -1; + private int endIndex => (messageHistory.Count + startIndex - 1) % Math.Max(1, messageHistory.Count); - public HistoryTextBox() + public HistoryTextBox(int historyLimit = 100) { + this.historyLimit = historyLimit; + messageHistory = new List(historyLimit); + Current.ValueChanged += text => { if (string.IsNullOrEmpty(text.NewValue)) - historyIndex = messageHistory.Count; + historyIndex = nullIndex; }; } + public string GetOldMessage(int index) + { + if (index < 0 || index >= messageHistory.Count) + throw new ArgumentOutOfRangeException(); + + return messageHistory[(startIndex + index) % messageHistory.Count]; + } + protected override bool OnKeyDown(KeyDownEvent e) { switch (e.Key) { case Key.Up: - if (historyIndex == messageHistory.Count) + if (historyIndex == nullIndex) + { + historyIndex = endIndex; originalMessage = Text; + } - if (historyIndex == 0) + if (historyIndex == startIndex) return true; - Text = messageHistory[--historyIndex]; + historyIndex = (historyLimit + historyIndex - 1) % historyLimit; + Text = messageHistory[historyIndex]; return true; case Key.Down: - if (historyIndex == messageHistory.Count) + if (historyIndex == nullIndex) return true; - if (historyIndex == messageHistory.Count - 1) + if (historyIndex == endIndex) { - historyIndex = messageHistory.Count; + historyIndex = nullIndex; Text = originalMessage; return true; } - Text = messageHistory[++historyIndex]; + historyIndex = (historyIndex + 1) % historyLimit; + Text = messageHistory[historyIndex]; return true; } @@ -63,9 +85,18 @@ namespace osu.Game.Graphics.UserInterface protected override void Commit() { if (!string.IsNullOrEmpty(Text)) - messageHistory.Add(Text); + { + if (messageHistory.Count == historyLimit) + { + messageHistory[startIndex++] = Text; + } + else + { + messageHistory.Add(Text); + } + } - historyIndex = messageHistory.Count; + historyIndex = nullIndex; base.Commit(); } From 19dc31c7ae6c3b5a2ad6bdd3422e1d60eb5ec8d5 Mon Sep 17 00:00:00 2001 From: Terochi Date: Wed, 16 Nov 2022 13:12:43 +0100 Subject: [PATCH 10/57] Changed tests. --- .../Online/TestSceneChatManipulation.cs | 75 +++++++------------ 1 file changed, 29 insertions(+), 46 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatManipulation.cs b/osu.Game.Tests/Visual/Online/TestSceneChatManipulation.cs index efa4cd41b3..3bff321d0d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatManipulation.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatManipulation.cs @@ -6,6 +6,7 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Input; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -19,6 +20,8 @@ namespace osu.Game.Tests.Visual.Online private HistoryTextBox box; private OsuSpriteText text; + private int messageCounter; + [SetUp] public void SetUp() { @@ -52,6 +55,8 @@ namespace osu.Game.Tests.Visual.Online text.FadeOutFromOne(1000, Easing.InQuint); }; + messageCounter = 0; + box.TakeFocus(); }); } @@ -97,8 +102,8 @@ namespace osu.Game.Tests.Visual.Online public void TestFullHistory() { addMessages(5); - AddAssert("History saved as <1-5>", () => - Enumerable.Range(1, 5).Select(number => $"Message {number}").SequenceEqual(box.MessageHistory)); + AddAssert("History saved as <5-1>", () => + Enumerable.Range(0, 5).Select(number => $"Message {5 - number}").SequenceEqual(box.MessageHistory)); const string temp = "Temp message"; AddStep("Set text", () => box.Text = temp); @@ -107,8 +112,8 @@ namespace osu.Game.Tests.Visual.Online AddAssert("Text is the same", () => box.Text == temp); addMessages(2); - AddAssert("Overwrote history to <3-7>", () => - Enumerable.Range(3, 5).Select(number => $"Message {number}").SequenceEqual(box.MessageHistory)); + AddAssert("Overrode history to <7-3>", () => + Enumerable.Range(0, 5).Select(number => $"Message {7 - number}").SequenceEqual(box.MessageHistory)); AddStep("Set text", () => box.Text = temp); @@ -126,63 +131,41 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestStayOnLastIndex() + public void TestOverrideFullHistory() { - addMessages(2); - AddRepeatStep("Move to last", () => InputManager.Key(Key.Up), 2); + addMessages(5); + AddAssert("History saved as <5-1>", () => + Enumerable.Range(0, 5).Select(number => $"Message {5 - number}").SequenceEqual(box.MessageHistory)); - string lastText = string.Empty; - AddStep("Move up", () => - { - lastText = box.Text; - InputManager.Key(Key.Up); - }); - - AddAssert("Text hasn't changed", () => lastText == box.Text); + addMessages(6); + AddAssert("Overrode history to <11-7>", () => + Enumerable.Range(0, 5).Select(number => $"Message {11 - number}").SequenceEqual(box.MessageHistory)); } [Test] - public void TestKeepOriginalMessage() - { - addMessages(1); - AddStep("Start writing", () => box.Text = "A random 文, ..."); - - AddStep("Move up", () => InputManager.Key(Key.Up)); - AddStep("Rewrite old message", () => box.Text = "Old Message"); - - AddStep("Move back down", () => InputManager.Key(Key.Down)); - AddAssert("Text back to previous", () => box.Text == "A random 文, ..."); - } - - [Test] - public void TestResetIndexOnEmpty() + public void TestResetIndex() { addMessages(2); - AddRepeatStep("Move up", () => InputManager.Key(Key.Up), 2); + + AddRepeatStep("Move Up", () => InputManager.Key(Key.Up), 2); + AddAssert("Same as 1st message", () => box.Text == "Message 1"); + AddStep("Remove text", () => box.Text = string.Empty); + AddStep("Move Up", () => InputManager.Key(Key.Up)); + AddAssert("Same as previous message", () => box.Text == "Message 2"); - AddStep("Move up again", () => InputManager.Key(Key.Up)); - AddAssert("Back to first message", () => box.Text == "Message 2"); - } - - [Test] - public void TestReachingLimitOfMessages() - { - addMessages(100); - AddAssert("List is full of <100-1>", () => - Enumerable.Range(0, 100).Select(number => $"Message {100 - number}").SequenceEqual(box.MessageHistory)); - - addMessages(2); - AddAssert("List is full of <102-3>", () => - Enumerable.Range(0, 100).Select(number => $"Message {102 - number}").SequenceEqual(box.MessageHistory)); + AddStep("Move Up", () => InputManager.Key(Key.Up)); + AddStep("Select text", () => InputManager.Keys(PlatformAction.SelectAll)); + AddStep("Replace text", () => box.Text = "New text"); + AddStep("Move Up", () => InputManager.Key(Key.Up)); + AddAssert("Same as previous message", () => box.Text == "Message 2"); } private void addMessages(int count) { - int iterations = 0; AddRepeatStep("Add messages", () => { - box.Text = $"Message {++iterations}"; + box.Text = $"Message {++messageCounter}"; InputManager.Key(Key.Enter); }, count); } From 0100c01b82f547ced0d3b040fbba14d88b13847e Mon Sep 17 00:00:00 2001 From: Terochi Date: Wed, 16 Nov 2022 13:16:01 +0100 Subject: [PATCH 11/57] Implemented finite limit of stored history. --- .../Graphics/UserInterface/HistoryTextBox.cs | 64 +++++++++++-------- 1 file changed, 39 insertions(+), 25 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs index c91e5dcb41..6c9df5aca2 100644 --- a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs +++ b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.Linq; using osu.Framework.Input.Events; using osuTK.Input; @@ -12,16 +13,23 @@ namespace osu.Game.Graphics.UserInterface { private readonly int historyLimit; + private bool everythingSelected; + + public int HistoryLength => messageHistory.Count; + private readonly List messageHistory; - public IReadOnlyList MessageHistory => messageHistory; + public IReadOnlyList MessageHistory => + Enumerable.Range(0, HistoryLength).Select(GetOldMessage).ToList(); + + private string originalMessage = string.Empty; + + private int historyIndex = -1; private int startIndex; - private string originalMessage = string.Empty; - private int nullIndex => -1; - private int historyIndex = -1; - private int endIndex => (messageHistory.Count + startIndex - 1) % Math.Max(1, messageHistory.Count); + private int getNormalizedIndex(int index) => + (HistoryLength + startIndex - index - 1) % HistoryLength; public HistoryTextBox(int historyLimit = 100) { @@ -30,17 +38,27 @@ namespace osu.Game.Graphics.UserInterface Current.ValueChanged += text => { - if (string.IsNullOrEmpty(text.NewValue)) - historyIndex = nullIndex; + if (string.IsNullOrEmpty(text.NewValue) || everythingSelected) + { + historyIndex = -1; + everythingSelected = false; + } }; } + protected override void OnTextSelectionChanged(TextSelectionType selectionType) + { + everythingSelected = SelectedText == Text; + + base.OnTextSelectionChanged(selectionType); + } + public string GetOldMessage(int index) { - if (index < 0 || index >= messageHistory.Count) + if (index < 0 || index >= HistoryLength) throw new ArgumentOutOfRangeException(); - return messageHistory[(startIndex + index) % messageHistory.Count]; + return HistoryLength == 0 ? string.Empty : messageHistory[getNormalizedIndex(index)]; } protected override bool OnKeyDown(KeyDownEvent e) @@ -48,33 +66,28 @@ namespace osu.Game.Graphics.UserInterface switch (e.Key) { case Key.Up: - if (historyIndex == nullIndex) - { - historyIndex = endIndex; - originalMessage = Text; - } - - if (historyIndex == startIndex) + if (historyIndex == HistoryLength - 1) return true; - historyIndex = (historyLimit + historyIndex - 1) % historyLimit; - Text = messageHistory[historyIndex]; + if (historyIndex == -1) + originalMessage = Text; + + Text = messageHistory[getNormalizedIndex(++historyIndex)]; return true; case Key.Down: - if (historyIndex == nullIndex) + if (historyIndex == -1) return true; - if (historyIndex == endIndex) + if (historyIndex == 0) { - historyIndex = nullIndex; + historyIndex = -1; Text = originalMessage; return true; } - historyIndex = (historyIndex + 1) % historyLimit; - Text = messageHistory[historyIndex]; + Text = messageHistory[getNormalizedIndex(--historyIndex)]; return true; } @@ -86,9 +99,10 @@ namespace osu.Game.Graphics.UserInterface { if (!string.IsNullOrEmpty(Text)) { - if (messageHistory.Count == historyLimit) + if (HistoryLength == historyLimit) { messageHistory[startIndex++] = Text; + startIndex %= historyLimit; } else { @@ -96,7 +110,7 @@ namespace osu.Game.Graphics.UserInterface } } - historyIndex = nullIndex; + historyIndex = -1; base.Commit(); } From a9747d367cc9a7321c414622a89777e89f321ca0 Mon Sep 17 00:00:00 2001 From: Dragon Date: Thu, 17 Nov 2022 10:18:17 +0100 Subject: [PATCH 12/57] Cleaning up --- .../TestSceneHistoryTextBox.cs} | 23 ++++++------- .../Graphics/UserInterface/HistoryTextBox.cs | 34 +++++++++---------- 2 files changed, 28 insertions(+), 29 deletions(-) rename osu.Game.Tests/Visual/{Online/TestSceneChatManipulation.cs => UserInterface/TestSceneHistoryTextBox.cs} (95%) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatManipulation.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs similarity index 95% rename from osu.Game.Tests/Visual/Online/TestSceneChatManipulation.cs rename to osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs index 3bff321d0d..f27711f512 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatManipulation.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs @@ -12,16 +12,18 @@ using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osuTK.Input; -namespace osu.Game.Tests.Visual.Online +namespace osu.Game.Tests.Visual.UserInterface { [TestFixture] - public class TestSceneChatManipulation : OsuManualInputManagerTestScene + public class TestSceneHistoryTextBox : OsuManualInputManagerTestScene { - private HistoryTextBox box; - private OsuSpriteText text; + private const string temp = "Temp message"; private int messageCounter; + private HistoryTextBox box; + private OsuSpriteText text; + [SetUp] public void SetUp() { @@ -49,6 +51,9 @@ namespace osu.Game.Tests.Visual.Online box.OnCommit += (_, __) => { + if (string.IsNullOrEmpty(box.Text)) + return; + text.Text = $"{nameof(box.OnCommit)}: {box.Text}"; box.Text = string.Empty; box.TakeFocus(); @@ -64,7 +69,6 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestEmptyHistory() { - const string temp = "Temp message"; AddStep("Set text", () => box.Text = temp); AddStep("Move down", () => InputManager.Key(Key.Down)); @@ -78,8 +82,6 @@ namespace osu.Game.Tests.Visual.Online public void TestPartialHistory() { addMessages(2); - - const string temp = "Temp message"; AddStep("Set text", () => box.Text = temp); AddStep("Move down", () => InputManager.Key(Key.Down)); @@ -102,21 +104,18 @@ namespace osu.Game.Tests.Visual.Online public void TestFullHistory() { addMessages(5); + AddStep("Set text", () => box.Text = temp); AddAssert("History saved as <5-1>", () => Enumerable.Range(0, 5).Select(number => $"Message {5 - number}").SequenceEqual(box.MessageHistory)); - const string temp = "Temp message"; - AddStep("Set text", () => box.Text = temp); - AddStep("Move down", () => InputManager.Key(Key.Down)); AddAssert("Text is the same", () => box.Text == temp); addMessages(2); + AddStep("Set text", () => box.Text = temp); AddAssert("Overrode history to <7-3>", () => Enumerable.Range(0, 5).Select(number => $"Message {7 - number}").SequenceEqual(box.MessageHistory)); - AddStep("Set text", () => box.Text = temp); - AddRepeatStep("Move Up", () => InputManager.Key(Key.Up), 5); AddAssert("Same as 3rd message", () => box.Text == "Message 3"); diff --git a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs index 6c9df5aca2..bebe21da47 100644 --- a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs +++ b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs @@ -12,27 +12,27 @@ namespace osu.Game.Graphics.UserInterface public class HistoryTextBox : FocusedTextBox { private readonly int historyLimit; - - private bool everythingSelected; - - public int HistoryLength => messageHistory.Count; - private readonly List messageHistory; public IReadOnlyList MessageHistory => Enumerable.Range(0, HistoryLength).Select(GetOldMessage).ToList(); - private string originalMessage = string.Empty; - - private int historyIndex = -1; + public int HistoryLength => messageHistory.Count; private int startIndex; + private int selectedIndex = -1; + + private string originalMessage = string.Empty; + private bool everythingSelected; private int getNormalizedIndex(int index) => (HistoryLength + startIndex - index - 1) % HistoryLength; public HistoryTextBox(int historyLimit = 100) { + if (historyLimit <= 0) + throw new ArgumentOutOfRangeException(); + this.historyLimit = historyLimit; messageHistory = new List(historyLimit); @@ -40,7 +40,7 @@ namespace osu.Game.Graphics.UserInterface { if (string.IsNullOrEmpty(text.NewValue) || everythingSelected) { - historyIndex = -1; + selectedIndex = -1; everythingSelected = false; } }; @@ -66,28 +66,28 @@ namespace osu.Game.Graphics.UserInterface switch (e.Key) { case Key.Up: - if (historyIndex == HistoryLength - 1) + if (selectedIndex == HistoryLength - 1) return true; - if (historyIndex == -1) + if (selectedIndex == -1) originalMessage = Text; - Text = messageHistory[getNormalizedIndex(++historyIndex)]; + Text = messageHistory[getNormalizedIndex(++selectedIndex)]; return true; case Key.Down: - if (historyIndex == -1) + if (selectedIndex == -1) return true; - if (historyIndex == 0) + if (selectedIndex == 0) { - historyIndex = -1; + selectedIndex = -1; Text = originalMessage; return true; } - Text = messageHistory[getNormalizedIndex(--historyIndex)]; + Text = messageHistory[getNormalizedIndex(--selectedIndex)]; return true; } @@ -110,7 +110,7 @@ namespace osu.Game.Graphics.UserInterface } } - historyIndex = -1; + selectedIndex = -1; base.Commit(); } From a25c94d567e9bdc4407e9882b0917bd85c6ba2ed Mon Sep 17 00:00:00 2001 From: Dragon Date: Thu, 17 Nov 2022 11:35:13 +0100 Subject: [PATCH 13/57] Replacing structure to use LimitedCapacityQueue.cs --- .../UserInterface/TestSceneHistoryTextBox.cs | 11 +-- .../Graphics/UserInterface/HistoryTextBox.cs | 70 +++++++------------ 2 files changed, 31 insertions(+), 50 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs index f27711f512..862777c500 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs @@ -49,7 +49,7 @@ namespace osu.Game.Tests.Visual.UserInterface } }; - box.OnCommit += (_, __) => + box.OnCommit += (_, _) => { if (string.IsNullOrEmpty(box.Text)) return; @@ -70,6 +70,7 @@ namespace osu.Game.Tests.Visual.UserInterface public void TestEmptyHistory() { AddStep("Set text", () => box.Text = temp); + AddAssert("History is empty", () => !box.GetMessageHistory().Any()); AddStep("Move down", () => InputManager.Key(Key.Down)); AddAssert("Text is the same", () => box.Text == temp); @@ -106,7 +107,7 @@ namespace osu.Game.Tests.Visual.UserInterface addMessages(5); AddStep("Set text", () => box.Text = temp); AddAssert("History saved as <5-1>", () => - Enumerable.Range(0, 5).Select(number => $"Message {5 - number}").SequenceEqual(box.MessageHistory)); + Enumerable.Range(0, 5).Select(number => $"Message {5 - number}").SequenceEqual(box.GetMessageHistory())); AddStep("Move down", () => InputManager.Key(Key.Down)); AddAssert("Text is the same", () => box.Text == temp); @@ -114,7 +115,7 @@ namespace osu.Game.Tests.Visual.UserInterface addMessages(2); AddStep("Set text", () => box.Text = temp); AddAssert("Overrode history to <7-3>", () => - Enumerable.Range(0, 5).Select(number => $"Message {7 - number}").SequenceEqual(box.MessageHistory)); + Enumerable.Range(0, 5).Select(number => $"Message {7 - number}").SequenceEqual(box.GetMessageHistory())); AddRepeatStep("Move Up", () => InputManager.Key(Key.Up), 5); AddAssert("Same as 3rd message", () => box.Text == "Message 3"); @@ -134,11 +135,11 @@ namespace osu.Game.Tests.Visual.UserInterface { addMessages(5); AddAssert("History saved as <5-1>", () => - Enumerable.Range(0, 5).Select(number => $"Message {5 - number}").SequenceEqual(box.MessageHistory)); + Enumerable.Range(0, 5).Select(number => $"Message {5 - number}").SequenceEqual(box.GetMessageHistory())); addMessages(6); AddAssert("Overrode history to <11-7>", () => - Enumerable.Range(0, 5).Select(number => $"Message {11 - number}").SequenceEqual(box.MessageHistory)); + Enumerable.Range(0, 5).Select(number => $"Message {11 - number}").SequenceEqual(box.GetMessageHistory())); } [Test] diff --git a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs index bebe21da47..17858ea16d 100644 --- a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs +++ b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs @@ -1,46 +1,44 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; -using System.Linq; using osu.Framework.Input.Events; +using osu.Game.Rulesets.Difficulty.Utils; using osuTK.Input; namespace osu.Game.Graphics.UserInterface { public class HistoryTextBox : FocusedTextBox { - private readonly int historyLimit; - private readonly List messageHistory; + private readonly LimitedCapacityQueue messageHistory; - public IReadOnlyList MessageHistory => - Enumerable.Range(0, HistoryLength).Select(GetOldMessage).ToList(); + public IEnumerable GetMessageHistory() + { + if (HistoryCount == 0) + yield break; - public int HistoryLength => messageHistory.Count; + for (int i = HistoryCount - 1; i >= 0; i--) + yield return messageHistory[i]; + } - private int startIndex; - private int selectedIndex = -1; + public int HistoryCount => messageHistory.Count; + + private int selectedIndex; private string originalMessage = string.Empty; private bool everythingSelected; - private int getNormalizedIndex(int index) => - (HistoryLength + startIndex - index - 1) % HistoryLength; + public string GetOldMessage(int index) => messageHistory[HistoryCount - index - 1]; - public HistoryTextBox(int historyLimit = 100) + public HistoryTextBox(int capacity = 100) { - if (historyLimit <= 0) - throw new ArgumentOutOfRangeException(); - - this.historyLimit = historyLimit; - messageHistory = new List(historyLimit); + messageHistory = new LimitedCapacityQueue(capacity); Current.ValueChanged += text => { if (string.IsNullOrEmpty(text.NewValue) || everythingSelected) { - selectedIndex = -1; + selectedIndex = HistoryCount; everythingSelected = false; } }; @@ -53,41 +51,33 @@ namespace osu.Game.Graphics.UserInterface base.OnTextSelectionChanged(selectionType); } - public string GetOldMessage(int index) - { - if (index < 0 || index >= HistoryLength) - throw new ArgumentOutOfRangeException(); - - return HistoryLength == 0 ? string.Empty : messageHistory[getNormalizedIndex(index)]; - } - protected override bool OnKeyDown(KeyDownEvent e) { switch (e.Key) { case Key.Up: - if (selectedIndex == HistoryLength - 1) + if (selectedIndex == 0) return true; - if (selectedIndex == -1) + if (selectedIndex == HistoryCount) originalMessage = Text; - Text = messageHistory[getNormalizedIndex(++selectedIndex)]; + Text = messageHistory[--selectedIndex]; return true; case Key.Down: - if (selectedIndex == -1) + if (selectedIndex == HistoryCount) return true; - if (selectedIndex == 0) + if (selectedIndex == HistoryCount - 1) { - selectedIndex = -1; + selectedIndex = HistoryCount; Text = originalMessage; return true; } - Text = messageHistory[getNormalizedIndex(--selectedIndex)]; + Text = messageHistory[++selectedIndex]; return true; } @@ -98,19 +88,9 @@ namespace osu.Game.Graphics.UserInterface protected override void Commit() { if (!string.IsNullOrEmpty(Text)) - { - if (HistoryLength == historyLimit) - { - messageHistory[startIndex++] = Text; - startIndex %= historyLimit; - } - else - { - messageHistory.Add(Text); - } - } + messageHistory.Enqueue(Text); - selectedIndex = -1; + selectedIndex = HistoryCount; base.Commit(); } From e8ca9f5dc584eccf74370977db246788385535c0 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 19 Nov 2022 00:19:49 +0300 Subject: [PATCH 14/57] Rework BarGraph to use Quads --- osu.Game/Graphics/UserInterface/Bar.cs | 12 +- osu.Game/Graphics/UserInterface/BarGraph.cs | 211 +++++++++++++++++--- 2 files changed, 188 insertions(+), 35 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/Bar.cs b/osu.Game/Graphics/UserInterface/Bar.cs index 3c87b166ac..e9a20761b3 100644 --- a/osu.Game/Graphics/UserInterface/Bar.cs +++ b/osu.Game/Graphics/UserInterface/Bar.cs @@ -109,15 +109,11 @@ namespace osu.Game.Graphics.UserInterface } } - [Flags] public enum BarDirection { - LeftToRight = 1, - RightToLeft = 1 << 1, - TopToBottom = 1 << 2, - BottomToTop = 1 << 3, - - Vertical = TopToBottom | BottomToTop, - Horizontal = LeftToRight | RightToLeft, + LeftToRight, + RightToLeft, + TopToBottom, + BottomToTop } } diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index 2e9fd6734f..45eeba11d5 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -5,15 +5,23 @@ using osuTK; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using System.Collections.Generic; using System.Linq; -using osu.Framework.Extensions.EnumExtensions; +using osu.Framework.Graphics.Rendering; +using osu.Framework.Graphics.Shaders; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Textures; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Utils; +using System; namespace osu.Game.Graphics.UserInterface { - public class BarGraph : FillFlowContainer + public class BarGraph : Drawable { + private const int resize_duration = 250; + private const Easing easing = Easing.InOutCubic; + /// /// Manually sets the max value, if null is instead used /// @@ -21,22 +29,21 @@ namespace osu.Game.Graphics.UserInterface private BarDirection direction = BarDirection.BottomToTop; - public new BarDirection Direction + public BarDirection Direction { get => direction; set { - direction = value; - base.Direction = direction.HasFlagFast(BarDirection.Horizontal) ? FillDirection.Vertical : FillDirection.Horizontal; + if (direction == value) + return; - foreach (var bar in Children) - { - bar.Size = direction.HasFlagFast(BarDirection.Horizontal) ? new Vector2(1, 1.0f / Children.Count) : new Vector2(1.0f / Children.Count, 1); - bar.Direction = direction; - } + direction = value; + Invalidate(Invalidation.DrawNode); } } + private IEnumerable bars; + /// /// A list of floats that defines the length of each /// @@ -44,38 +51,188 @@ namespace osu.Game.Graphics.UserInterface { set { - List bars = Children.ToList(); + List newBars = bars?.ToList() ?? new List(); - foreach (var bar in value.Select((length, index) => new { Value = length, Bar = bars.Count > index ? bars[index] : null })) + int newCount = value.Count(); + + float size = newCount; + if (size != 0) + size = 1.0f / size; + + foreach (var bar in value.Select((length, index) => new { Value = length, Bar = newBars.Count > index ? newBars[index] : null })) { float length = MaxValue ?? value.Max(); if (length != 0) - length = bar.Value / length; - - float size = value.Count(); - if (size != 0) - size = 1.0f / size; + length = Math.Max(0f, bar.Value / length); if (bar.Bar != null) { - bar.Bar.Length = length; - bar.Bar.Size = direction.HasFlagFast(BarDirection.Horizontal) ? new Vector2(1, size) : new Vector2(size, 1); + bar.Bar.OldValue = bar.Bar.Value; + + bar.Bar.Value = length; + bar.Bar.ShortSide = size; } else { - Add(new Bar + newBars.Add(new BarDescriptor { - RelativeSizeAxes = Axes.Both, - Size = direction.HasFlagFast(BarDirection.Horizontal) ? new Vector2(1, size) : new Vector2(size, 1), - Length = length, - Direction = Direction, + Value = length, + ShortSide = size }); } } - //I'm using ToList() here because Where() returns an Enumerable which can change it's elements afterwards - RemoveRange(Children.Where((_, index) => index >= value.Count()).ToList(), true); + if (newBars.Count > newCount) + newBars.RemoveRange(newCount, newBars.Count - newCount); + + bars = newBars; + + animationStartTime = Clock.CurrentTime; + animationComplete = false; } } + + private double animationStartTime; + private bool animationComplete; + + private IShader shader = null!; + private Texture texture = null!; + + [BackgroundDependencyLoader] + private void load(IRenderer renderer, ShaderManager shaders) + { + texture = renderer.WhitePixel; + shader = shaders.Load(VertexShaderDescriptor.TEXTURE_2, FragmentShaderDescriptor.TEXTURE); + } + + protected override void Update() + { + base.Update(); + + if (noBars) + return; + + double currentTime = Clock.CurrentTime; + + if (currentTime < animationStartTime + resize_duration) + { + foreach (var bar in bars) + bar.IntermediateValue = Interpolation.ValueAt(currentTime, bar.OldValue, bar.Value, animationStartTime, animationStartTime + resize_duration, easing); + + Invalidate(Invalidation.DrawNode); + return; + } + else if (!animationComplete) + { + foreach (var bar in bars) + bar.IntermediateValue = bar.Value; + + Invalidate(Invalidation.DrawNode); + + animationComplete = true; + return; + } + } + + private bool noBars => bars?.Any() != true; + + protected override DrawNode CreateDrawNode() => new BarGraphDrawNode(this); + + private class BarGraphDrawNode : DrawNode + { + public new BarGraph Source => (BarGraph)base.Source; + + public BarGraphDrawNode(BarGraph source) + : base(source) + { + } + + private IShader shader = null!; + private Texture texture = null!; + private Vector2 drawSize; + private BarDirection direction; + + private readonly List bars = new List(); + + public override void ApplyState() + { + base.ApplyState(); + + shader = Source.shader; + texture = Source.texture; + drawSize = Source.DrawSize; + direction = Source.direction; + + bars.Clear(); + + if (Source.noBars) + return; + + bars.AddRange(Source.bars); + } + + public override void Draw(IRenderer renderer) + { + base.Draw(renderer); + + if (!bars.Any()) + return; + + shader.Bind(); + + for (int i = 0; i < bars.Count; i++) + { + var bar = bars[i]; + + float barHeight = drawSize.Y * ((direction == BarDirection.TopToBottom || direction == BarDirection.BottomToTop) ? bar.IntermediateValue : bar.ShortSide); + float barWidth = drawSize.X * ((direction == BarDirection.LeftToRight || direction == BarDirection.RightToLeft) ? bar.IntermediateValue : bar.ShortSide); + + Vector2 topLeft; + + switch (direction) + { + default: + case BarDirection.LeftToRight: + topLeft = new Vector2(0, i * barHeight); + break; + + case BarDirection.RightToLeft: + topLeft = new Vector2(drawSize.X - barWidth, i * barHeight); + break; + + case BarDirection.TopToBottom: + topLeft = new Vector2(i * barWidth, 0); + break; + + case BarDirection.BottomToTop: + topLeft = new Vector2(i * barWidth, drawSize.Y - barHeight); + break; + } + + Vector2 topRight = topLeft + new Vector2(barWidth, 0); + Vector2 bottomLeft = topLeft + new Vector2(0, barHeight); + Vector2 bottomRight = bottomLeft + new Vector2(barWidth, 0); + + var drawQuad = new Quad( + Vector2Extensions.Transform(topLeft, DrawInfo.Matrix), + Vector2Extensions.Transform(topRight, DrawInfo.Matrix), + Vector2Extensions.Transform(bottomLeft, DrawInfo.Matrix), + Vector2Extensions.Transform(bottomRight, DrawInfo.Matrix) + ); + + renderer.DrawQuad(texture, drawQuad, DrawColourInfo.Colour); + } + + shader.Unbind(); + } + } + + private class BarDescriptor + { + public float OldValue { get; set; } + public float Value { get; set; } + public float IntermediateValue { get; set; } + public float ShortSide { get; set; } + } } } From 11f5fddc1f4a803ac3f737b7c628730d9ee23359 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 19 Nov 2022 09:57:52 +0300 Subject: [PATCH 15/57] Remove redundant returns --- osu.Game/Graphics/UserInterface/BarGraph.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index 45eeba11d5..b82911ea56 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -120,7 +120,6 @@ namespace osu.Game.Graphics.UserInterface bar.IntermediateValue = Interpolation.ValueAt(currentTime, bar.OldValue, bar.Value, animationStartTime, animationStartTime + resize_duration, easing); Invalidate(Invalidation.DrawNode); - return; } else if (!animationComplete) { @@ -130,7 +129,6 @@ namespace osu.Game.Graphics.UserInterface Invalidate(Invalidation.DrawNode); animationComplete = true; - return; } } From 9b8f98735cdc642ab7ea5d050bab9f96e77de491 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 19 Nov 2022 10:16:58 +0300 Subject: [PATCH 16/57] Use struct for bars description --- osu.Game/Graphics/UserInterface/BarGraph.cs | 55 +++++++++++---------- 1 file changed, 28 insertions(+), 27 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index b82911ea56..efc5eace0d 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -42,7 +42,7 @@ namespace osu.Game.Graphics.UserInterface } } - private IEnumerable bars; + private readonly List bars = new List(); /// /// A list of floats that defines the length of each @@ -51,30 +51,31 @@ namespace osu.Game.Graphics.UserInterface { set { - List newBars = bars?.ToList() ?? new List(); - int newCount = value.Count(); float size = newCount; if (size != 0) size = 1.0f / size; - foreach (var bar in value.Select((length, index) => new { Value = length, Bar = newBars.Count > index ? newBars[index] : null })) + float maxLength = MaxValue ?? value.Max(); + + foreach (var bar in value.Select((length, index) => new { Value = length, Index = index })) { - float length = MaxValue ?? value.Max(); - if (length != 0) - length = Math.Max(0f, bar.Value / length); + float length = maxLength == 0 ? 0 : Math.Max(0f, bar.Value / maxLength); - if (bar.Bar != null) + if (bar.Index < bars.Count) { - bar.Bar.OldValue = bar.Bar.Value; + BarStruct b = bars[bar.Index]; - bar.Bar.Value = length; - bar.Bar.ShortSide = size; + b.OldValue = b.Value; + b.Value = length; + b.ShortSide = size; + + bars[bar.Index] = b; } else { - newBars.Add(new BarDescriptor + bars.Add(new BarStruct { Value = length, ShortSide = size @@ -82,10 +83,8 @@ namespace osu.Game.Graphics.UserInterface } } - if (newBars.Count > newCount) - newBars.RemoveRange(newCount, newBars.Count - newCount); - - bars = newBars; + if (bars.Count > newCount) + bars.RemoveRange(newCount, bars.Count - newCount); animationStartTime = Clock.CurrentTime; animationComplete = false; @@ -109,22 +108,30 @@ namespace osu.Game.Graphics.UserInterface { base.Update(); - if (noBars) + if (!bars.Any()) return; double currentTime = Clock.CurrentTime; if (currentTime < animationStartTime + resize_duration) { - foreach (var bar in bars) + for (int i = 0; i < bars.Count(); i++) + { + BarStruct bar = bars[i]; bar.IntermediateValue = Interpolation.ValueAt(currentTime, bar.OldValue, bar.Value, animationStartTime, animationStartTime + resize_duration, easing); + bars[i] = bar; + } Invalidate(Invalidation.DrawNode); } else if (!animationComplete) { - foreach (var bar in bars) + for (int i = 0; i < bars.Count(); i++) + { + BarStruct bar = bars[i]; bar.IntermediateValue = bar.Value; + bars[i] = bar; + } Invalidate(Invalidation.DrawNode); @@ -132,8 +139,6 @@ namespace osu.Game.Graphics.UserInterface } } - private bool noBars => bars?.Any() != true; - protected override DrawNode CreateDrawNode() => new BarGraphDrawNode(this); private class BarGraphDrawNode : DrawNode @@ -150,7 +155,7 @@ namespace osu.Game.Graphics.UserInterface private Vector2 drawSize; private BarDirection direction; - private readonly List bars = new List(); + private readonly List bars = new List(); public override void ApplyState() { @@ -162,10 +167,6 @@ namespace osu.Game.Graphics.UserInterface direction = Source.direction; bars.Clear(); - - if (Source.noBars) - return; - bars.AddRange(Source.bars); } @@ -225,7 +226,7 @@ namespace osu.Game.Graphics.UserInterface } } - private class BarDescriptor + private struct BarStruct { public float OldValue { get; set; } public float Value { get; set; } From 05992d3aa8eabf806e2fdcf2074effd819719cf2 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 19 Nov 2022 10:23:37 +0300 Subject: [PATCH 17/57] CI fix --- osu.Game/Graphics/UserInterface/BarGraph.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index efc5eace0d..8552c2196c 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -115,7 +115,7 @@ namespace osu.Game.Graphics.UserInterface if (currentTime < animationStartTime + resize_duration) { - for (int i = 0; i < bars.Count(); i++) + for (int i = 0; i < bars.Count; i++) { BarStruct bar = bars[i]; bar.IntermediateValue = Interpolation.ValueAt(currentTime, bar.OldValue, bar.Value, animationStartTime, animationStartTime + resize_duration, easing); @@ -126,7 +126,7 @@ namespace osu.Game.Graphics.UserInterface } else if (!animationComplete) { - for (int i = 0; i < bars.Count(); i++) + for (int i = 0; i < bars.Count; i++) { BarStruct bar = bars[i]; bar.IntermediateValue = bar.Value; From 0239103b6b0f24b6b5d956794be01575d3d1a7ed Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 19 Nov 2022 11:33:14 +0300 Subject: [PATCH 18/57] Fix BeatmapOverlay crashing test scene --- osu.Game/Graphics/UserInterface/BarGraph.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index 8552c2196c..56a2a4f579 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -57,7 +57,7 @@ namespace osu.Game.Graphics.UserInterface if (size != 0) size = 1.0f / size; - float maxLength = MaxValue ?? value.Max(); + float maxLength = MaxValue ?? (newCount == 0 ? 0 : value.Max()); foreach (var bar in value.Select((length, index) => new { Value = length, Index = index })) { From eff6c7be6499aecd6ecf7bfb3faf999334b04d41 Mon Sep 17 00:00:00 2001 From: Terochi Date: Sat, 19 Nov 2022 16:53:35 +0100 Subject: [PATCH 19/57] Removed unnecessary methods, changed tests, and moved LimitedCapacityQueue.cs to a more generic namespace. --- .../Difficulty/Skills/Rhythm.cs | 2 +- .../NonVisual/LimitedCapacityQueueTest.cs | 2 +- .../UserInterface/TestSceneHistoryTextBox.cs | 24 ++----------------- .../Graphics/UserInterface/HistoryTextBox.cs | 14 +---------- .../Utils/LimitedCapacityQueue.cs | 2 +- 5 files changed, 6 insertions(+), 38 deletions(-) rename osu.Game/{Rulesets/Difficulty => }/Utils/LimitedCapacityQueue.cs (98%) diff --git a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs index 4a2b97a4cb..ff187a133a 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/Skills/Rhythm.cs @@ -6,10 +6,10 @@ using System; using osu.Game.Rulesets.Difficulty.Preprocessing; using osu.Game.Rulesets.Difficulty.Skills; -using osu.Game.Rulesets.Difficulty.Utils; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Taiko.Difficulty.Preprocessing; using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Utils; namespace osu.Game.Rulesets.Taiko.Difficulty.Skills { diff --git a/osu.Game.Tests/NonVisual/LimitedCapacityQueueTest.cs b/osu.Game.Tests/NonVisual/LimitedCapacityQueueTest.cs index 8cd26901c5..8809ce3adc 100644 --- a/osu.Game.Tests/NonVisual/LimitedCapacityQueueTest.cs +++ b/osu.Game.Tests/NonVisual/LimitedCapacityQueueTest.cs @@ -5,7 +5,7 @@ using System; using NUnit.Framework; -using osu.Game.Rulesets.Difficulty.Utils; +using osu.Game.Utils; namespace osu.Game.Tests.NonVisual { diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs index 862777c500..8918e53056 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs @@ -1,9 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - -using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Input; @@ -21,8 +18,8 @@ namespace osu.Game.Tests.Visual.UserInterface private int messageCounter; - private HistoryTextBox box; - private OsuSpriteText text; + private HistoryTextBox box = null!; + private OsuSpriteText text = null!; [SetUp] public void SetUp() @@ -70,7 +67,6 @@ namespace osu.Game.Tests.Visual.UserInterface public void TestEmptyHistory() { AddStep("Set text", () => box.Text = temp); - AddAssert("History is empty", () => !box.GetMessageHistory().Any()); AddStep("Move down", () => InputManager.Key(Key.Down)); AddAssert("Text is the same", () => box.Text == temp); @@ -106,16 +102,12 @@ namespace osu.Game.Tests.Visual.UserInterface { addMessages(5); AddStep("Set text", () => box.Text = temp); - AddAssert("History saved as <5-1>", () => - Enumerable.Range(0, 5).Select(number => $"Message {5 - number}").SequenceEqual(box.GetMessageHistory())); AddStep("Move down", () => InputManager.Key(Key.Down)); AddAssert("Text is the same", () => box.Text == temp); addMessages(2); AddStep("Set text", () => box.Text = temp); - AddAssert("Overrode history to <7-3>", () => - Enumerable.Range(0, 5).Select(number => $"Message {7 - number}").SequenceEqual(box.GetMessageHistory())); AddRepeatStep("Move Up", () => InputManager.Key(Key.Up), 5); AddAssert("Same as 3rd message", () => box.Text == "Message 3"); @@ -130,18 +122,6 @@ namespace osu.Game.Tests.Visual.UserInterface AddAssert("Same as temp message", () => box.Text == temp); } - [Test] - public void TestOverrideFullHistory() - { - addMessages(5); - AddAssert("History saved as <5-1>", () => - Enumerable.Range(0, 5).Select(number => $"Message {5 - number}").SequenceEqual(box.GetMessageHistory())); - - addMessages(6); - AddAssert("Overrode history to <11-7>", () => - Enumerable.Range(0, 5).Select(number => $"Message {11 - number}").SequenceEqual(box.GetMessageHistory())); - } - [Test] public void TestResetIndex() { diff --git a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs index 17858ea16d..e9da67e553 100644 --- a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs +++ b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs @@ -1,9 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; using osu.Framework.Input.Events; -using osu.Game.Rulesets.Difficulty.Utils; +using osu.Game.Utils; using osuTK.Input; namespace osu.Game.Graphics.UserInterface @@ -12,15 +11,6 @@ namespace osu.Game.Graphics.UserInterface { private readonly LimitedCapacityQueue messageHistory; - public IEnumerable GetMessageHistory() - { - if (HistoryCount == 0) - yield break; - - for (int i = HistoryCount - 1; i >= 0; i--) - yield return messageHistory[i]; - } - public int HistoryCount => messageHistory.Count; private int selectedIndex; @@ -28,8 +18,6 @@ namespace osu.Game.Graphics.UserInterface private string originalMessage = string.Empty; private bool everythingSelected; - public string GetOldMessage(int index) => messageHistory[HistoryCount - index - 1]; - public HistoryTextBox(int capacity = 100) { messageHistory = new LimitedCapacityQueue(capacity); diff --git a/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityQueue.cs b/osu.Game/Utils/LimitedCapacityQueue.cs similarity index 98% rename from osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityQueue.cs rename to osu.Game/Utils/LimitedCapacityQueue.cs index d20eb5e885..86a106a678 100644 --- a/osu.Game/Rulesets/Difficulty/Utils/LimitedCapacityQueue.cs +++ b/osu.Game/Utils/LimitedCapacityQueue.cs @@ -7,7 +7,7 @@ using System; using System.Collections; using System.Collections.Generic; -namespace osu.Game.Rulesets.Difficulty.Utils +namespace osu.Game.Utils { /// /// An indexed queue with limited capacity. From 6f449a583e57230e3af26dd8d40ab6051dc036b9 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 19 Nov 2022 23:27:48 +0300 Subject: [PATCH 20/57] Handle empty values as a separate case --- osu.Game.Tests/Visual/Online/TestSceneGraph.cs | 2 ++ osu.Game/Graphics/UserInterface/BarGraph.cs | 13 +++++++++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneGraph.cs b/osu.Game.Tests/Visual/Online/TestSceneGraph.cs index 2f3331b141..8b0536651d 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneGraph.cs @@ -3,6 +3,7 @@ #nullable disable +using System; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; @@ -32,6 +33,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("values from 1-10", () => graph.Values = Enumerable.Range(1, 10).Select(i => (float)i)); AddStep("values from 1-100", () => graph.Values = Enumerable.Range(1, 100).Select(i => (float)i)); AddStep("reversed values from 1-10", () => graph.Values = Enumerable.Range(1, 10).Reverse().Select(i => (float)i)); + AddStep("empty values", () => graph.Values = Array.Empty()); AddStep("Bottom to top", () => graph.Direction = BarDirection.BottomToTop); AddStep("Top to bottom", () => graph.Direction = BarDirection.TopToBottom); AddStep("Left to right", () => graph.Direction = BarDirection.LeftToRight); diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index 56a2a4f579..c9bffc605e 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -51,13 +51,18 @@ namespace osu.Game.Graphics.UserInterface { set { + if (!value.Any()) + { + bars.Clear(); + Invalidate(Invalidation.DrawNode); + return; + } + int newCount = value.Count(); - float size = newCount; - if (size != 0) - size = 1.0f / size; + float size = 1.0f / newCount; - float maxLength = MaxValue ?? (newCount == 0 ? 0 : value.Max()); + float maxLength = MaxValue ?? value.Max(); foreach (var bar in value.Select((length, index) => new { Value = length, Index = index })) { From f1201454b723cf9f00df4729d0390adecaa0da84 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 19 Nov 2022 23:29:50 +0300 Subject: [PATCH 21/57] Use value tuples --- osu.Game/Graphics/UserInterface/BarGraph.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index c9bffc605e..f2352c91da 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -64,7 +64,7 @@ namespace osu.Game.Graphics.UserInterface float maxLength = MaxValue ?? value.Max(); - foreach (var bar in value.Select((length, index) => new { Value = length, Index = index })) + foreach (var bar in value.Select((length, index) => (Value: length, Index: index))) { float length = maxLength == 0 ? 0 : Math.Max(0f, bar.Value / maxLength); From 67ee9f39154dae692caa3c96d8a5b7cfb1fc5643 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 19 Nov 2022 23:34:55 +0300 Subject: [PATCH 22/57] Naming adjustments --- osu.Game/Graphics/UserInterface/BarGraph.cs | 40 ++++++++++----------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index f2352c91da..661057cbde 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -42,7 +42,7 @@ namespace osu.Game.Graphics.UserInterface } } - private readonly List bars = new List(); + private readonly List bars = new List(); /// /// A list of floats that defines the length of each @@ -70,20 +70,20 @@ namespace osu.Game.Graphics.UserInterface if (bar.Index < bars.Count) { - BarStruct b = bars[bar.Index]; + BarInfo b = bars[bar.Index]; - b.OldValue = b.Value; - b.Value = length; - b.ShortSide = size; + b.InitialLength = b.FinalLength; + b.FinalLength = length; + b.Breadth = size; bars[bar.Index] = b; } else { - bars.Add(new BarStruct + bars.Add(new BarInfo { - Value = length, - ShortSide = size + FinalLength = length, + Breadth = size }); } } @@ -122,8 +122,8 @@ namespace osu.Game.Graphics.UserInterface { for (int i = 0; i < bars.Count; i++) { - BarStruct bar = bars[i]; - bar.IntermediateValue = Interpolation.ValueAt(currentTime, bar.OldValue, bar.Value, animationStartTime, animationStartTime + resize_duration, easing); + BarInfo bar = bars[i]; + bar.InstantaneousLength = Interpolation.ValueAt(currentTime, bar.InitialLength, bar.FinalLength, animationStartTime, animationStartTime + resize_duration, easing); bars[i] = bar; } @@ -133,8 +133,8 @@ namespace osu.Game.Graphics.UserInterface { for (int i = 0; i < bars.Count; i++) { - BarStruct bar = bars[i]; - bar.IntermediateValue = bar.Value; + BarInfo bar = bars[i]; + bar.InstantaneousLength = bar.FinalLength; bars[i] = bar; } @@ -160,7 +160,7 @@ namespace osu.Game.Graphics.UserInterface private Vector2 drawSize; private BarDirection direction; - private readonly List bars = new List(); + private readonly List bars = new List(); public override void ApplyState() { @@ -188,8 +188,8 @@ namespace osu.Game.Graphics.UserInterface { var bar = bars[i]; - float barHeight = drawSize.Y * ((direction == BarDirection.TopToBottom || direction == BarDirection.BottomToTop) ? bar.IntermediateValue : bar.ShortSide); - float barWidth = drawSize.X * ((direction == BarDirection.LeftToRight || direction == BarDirection.RightToLeft) ? bar.IntermediateValue : bar.ShortSide); + float barHeight = drawSize.Y * ((direction == BarDirection.TopToBottom || direction == BarDirection.BottomToTop) ? bar.InstantaneousLength : bar.Breadth); + float barWidth = drawSize.X * ((direction == BarDirection.LeftToRight || direction == BarDirection.RightToLeft) ? bar.InstantaneousLength : bar.Breadth); Vector2 topLeft; @@ -231,12 +231,12 @@ namespace osu.Game.Graphics.UserInterface } } - private struct BarStruct + private struct BarInfo { - public float OldValue { get; set; } - public float Value { get; set; } - public float IntermediateValue { get; set; } - public float ShortSide { get; set; } + public float InitialLength { get; set; } + public float FinalLength { get; set; } + public float InstantaneousLength { get; set; } + public float Breadth { get; set; } } } } From 6c62cfb8301e5e6f29f64a17ec0323d5a23caca0 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sat, 19 Nov 2022 23:40:02 +0300 Subject: [PATCH 23/57] Store barBreadth as a separate float --- osu.Game/Graphics/UserInterface/BarGraph.cs | 22 +++++++++------------ 1 file changed, 9 insertions(+), 13 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index 661057cbde..404b925372 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -43,6 +43,7 @@ namespace osu.Game.Graphics.UserInterface } private readonly List bars = new List(); + private float barBreadth; /// /// A list of floats that defines the length of each @@ -60,7 +61,7 @@ namespace osu.Game.Graphics.UserInterface int newCount = value.Count(); - float size = 1.0f / newCount; + barBreadth = 1.0f / newCount; float maxLength = MaxValue ?? value.Max(); @@ -74,18 +75,12 @@ namespace osu.Game.Graphics.UserInterface b.InitialLength = b.FinalLength; b.FinalLength = length; - b.Breadth = size; bars[bar.Index] = b; + continue; } - else - { - bars.Add(new BarInfo - { - FinalLength = length, - Breadth = size - }); - } + + bars.Add(new BarInfo { FinalLength = length }); } if (bars.Count > newCount) @@ -159,6 +154,7 @@ namespace osu.Game.Graphics.UserInterface private Texture texture = null!; private Vector2 drawSize; private BarDirection direction; + private float barBreadth; private readonly List bars = new List(); @@ -170,6 +166,7 @@ namespace osu.Game.Graphics.UserInterface texture = Source.texture; drawSize = Source.DrawSize; direction = Source.direction; + barBreadth = Source.barBreadth; bars.Clear(); bars.AddRange(Source.bars); @@ -188,8 +185,8 @@ namespace osu.Game.Graphics.UserInterface { var bar = bars[i]; - float barHeight = drawSize.Y * ((direction == BarDirection.TopToBottom || direction == BarDirection.BottomToTop) ? bar.InstantaneousLength : bar.Breadth); - float barWidth = drawSize.X * ((direction == BarDirection.LeftToRight || direction == BarDirection.RightToLeft) ? bar.InstantaneousLength : bar.Breadth); + float barHeight = drawSize.Y * ((direction == BarDirection.TopToBottom || direction == BarDirection.BottomToTop) ? bar.InstantaneousLength : barBreadth); + float barWidth = drawSize.X * ((direction == BarDirection.LeftToRight || direction == BarDirection.RightToLeft) ? bar.InstantaneousLength : barBreadth); Vector2 topLeft; @@ -236,7 +233,6 @@ namespace osu.Game.Graphics.UserInterface public float InitialLength { get; set; } public float FinalLength { get; set; } public float InstantaneousLength { get; set; } - public float Breadth { get; set; } } } } From 2cb966b47cad8127fb828fb440698cb9724a6c97 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 20 Nov 2022 01:20:57 +0300 Subject: [PATCH 24/57] Rework BarsInfo struct --- osu.Game/Graphics/UserInterface/BarGraph.cs | 134 ++++++++++++-------- 1 file changed, 83 insertions(+), 51 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index 404b925372..6df0302d1c 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -42,7 +42,8 @@ namespace osu.Game.Graphics.UserInterface } } - private readonly List bars = new List(); + private readonly BarsInfo bars = new BarsInfo(0); + private float barBreadth; /// @@ -71,16 +72,11 @@ namespace osu.Game.Graphics.UserInterface if (bar.Index < bars.Count) { - BarInfo b = bars[bar.Index]; - - b.InitialLength = b.FinalLength; - b.FinalLength = length; - - bars[bar.Index] = b; + bars.UpdateLength(bar.Index, length); continue; } - bars.Add(new BarInfo { FinalLength = length }); + bars.AddBar(length); } if (bars.Count > newCount) @@ -108,31 +104,19 @@ namespace osu.Game.Graphics.UserInterface { base.Update(); - if (!bars.Any()) + if (!bars.Any) return; double currentTime = Clock.CurrentTime; if (currentTime < animationStartTime + resize_duration) { - for (int i = 0; i < bars.Count; i++) - { - BarInfo bar = bars[i]; - bar.InstantaneousLength = Interpolation.ValueAt(currentTime, bar.InitialLength, bar.FinalLength, animationStartTime, animationStartTime + resize_duration, easing); - bars[i] = bar; - } - + bars.Animate(animationStartTime, currentTime); Invalidate(Invalidation.DrawNode); } else if (!animationComplete) { - for (int i = 0; i < bars.Count; i++) - { - BarInfo bar = bars[i]; - bar.InstantaneousLength = bar.FinalLength; - bars[i] = bar; - } - + bars.FinishAnimation(); Invalidate(Invalidation.DrawNode); animationComplete = true; @@ -155,8 +139,7 @@ namespace osu.Game.Graphics.UserInterface private Vector2 drawSize; private BarDirection direction; private float barBreadth; - - private readonly List bars = new List(); + private BarsInfo bars; public override void ApplyState() { @@ -167,26 +150,19 @@ namespace osu.Game.Graphics.UserInterface drawSize = Source.DrawSize; direction = Source.direction; barBreadth = Source.barBreadth; - - bars.Clear(); - bars.AddRange(Source.bars); + bars = Source.bars; } public override void Draw(IRenderer renderer) { base.Draw(renderer); - if (!bars.Any()) - return; - shader.Bind(); for (int i = 0; i < bars.Count; i++) { - var bar = bars[i]; - - float barHeight = drawSize.Y * ((direction == BarDirection.TopToBottom || direction == BarDirection.BottomToTop) ? bar.InstantaneousLength : barBreadth); - float barWidth = drawSize.X * ((direction == BarDirection.LeftToRight || direction == BarDirection.RightToLeft) ? bar.InstantaneousLength : barBreadth); + float barHeight = drawSize.Y * ((direction == BarDirection.TopToBottom || direction == BarDirection.BottomToTop) ? bars.InstantaneousLength(i) : barBreadth); + float barWidth = drawSize.X * ((direction == BarDirection.LeftToRight || direction == BarDirection.RightToLeft) ? bars.InstantaneousLength(i) : barBreadth); Vector2 topLeft; @@ -210,29 +186,85 @@ namespace osu.Game.Graphics.UserInterface break; } - Vector2 topRight = topLeft + new Vector2(barWidth, 0); - Vector2 bottomLeft = topLeft + new Vector2(0, barHeight); - Vector2 bottomRight = bottomLeft + new Vector2(barWidth, 0); - - var drawQuad = new Quad( - Vector2Extensions.Transform(topLeft, DrawInfo.Matrix), - Vector2Extensions.Transform(topRight, DrawInfo.Matrix), - Vector2Extensions.Transform(bottomLeft, DrawInfo.Matrix), - Vector2Extensions.Transform(bottomRight, DrawInfo.Matrix) - ); - - renderer.DrawQuad(texture, drawQuad, DrawColourInfo.Colour); + renderer.DrawQuad( + texture, + new Quad( + Vector2Extensions.Transform(topLeft, DrawInfo.Matrix), + Vector2Extensions.Transform(topLeft + new Vector2(barWidth, 0), DrawInfo.Matrix), + Vector2Extensions.Transform(topLeft + new Vector2(0, barHeight), DrawInfo.Matrix), + Vector2Extensions.Transform(topLeft + new Vector2(barWidth, barHeight), DrawInfo.Matrix) + ), + DrawColourInfo.Colour); } shader.Unbind(); } } - private struct BarInfo + private struct BarsInfo { - public float InitialLength { get; set; } - public float FinalLength { get; set; } - public float InstantaneousLength { get; set; } + private readonly List initialLengths; + private readonly List finalLengths; + private readonly List instantaneousLengths; + + public bool Any => initialLengths.Any(); + + public int Count => initialLengths.Count; + + public BarsInfo(int initialCount) + { + initialLengths = new List(); + finalLengths = new List(); + instantaneousLengths = new List(); + + for (int i = 0; i < initialCount; i++) + { + initialLengths.Add(0); + finalLengths.Add(0); + instantaneousLengths.Add(0); + } + } + + public float InstantaneousLength(int index) => instantaneousLengths[index]; + + public void UpdateLength(int index, float newLength) + { + initialLengths[index] = finalLengths[index]; + finalLengths[index] = newLength; + } + + public void AddBar(float finalLength) + { + initialLengths.Add(0); + finalLengths.Add(finalLength); + instantaneousLengths.Add(0); + } + + public void Clear() + { + initialLengths.Clear(); + finalLengths.Clear(); + instantaneousLengths.Clear(); + } + + public void RemoveRange(int index, int count) + { + initialLengths.RemoveRange(index, count); + finalLengths.RemoveRange(index, count); + instantaneousLengths.RemoveRange(index, count); + } + + public void Animate(double animationStartTime, double currentTime) + { + for (int i = 0; i < Count; i++) + instantaneousLengths[i] = Interpolation.ValueAt(currentTime, initialLengths[i], finalLengths[i], animationStartTime, animationStartTime + resize_duration, easing); + } + + public void FinishAnimation() + { + for (int i = 0; i < Count; i++) + instantaneousLengths[i] = finalLengths[i]; + } } } } From fbfcf49ea6cc7c1639887fc5669da01493ea5d3c Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 20 Nov 2022 02:13:54 +0300 Subject: [PATCH 25/57] Remove readonly modifier from struct fields --- osu.Game/Graphics/UserInterface/BarGraph.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index 6df0302d1c..4e1d708531 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -203,9 +203,9 @@ namespace osu.Game.Graphics.UserInterface private struct BarsInfo { - private readonly List initialLengths; - private readonly List finalLengths; - private readonly List instantaneousLengths; + private List initialLengths; + private List finalLengths; + private List instantaneousLengths; public bool Any => initialLengths.Any(); From fcb52ee237ad57a57beb949304d85e640f4ac53e Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 20 Nov 2022 02:28:07 +0300 Subject: [PATCH 26/57] Make BarsInfo a readonly struct --- osu.Game/Graphics/UserInterface/BarGraph.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index 4e1d708531..50c634455b 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -42,7 +42,7 @@ namespace osu.Game.Graphics.UserInterface } } - private readonly BarsInfo bars = new BarsInfo(0); + private BarsInfo bars = new BarsInfo(0); private float barBreadth; @@ -201,11 +201,11 @@ namespace osu.Game.Graphics.UserInterface } } - private struct BarsInfo + private readonly struct BarsInfo { - private List initialLengths; - private List finalLengths; - private List instantaneousLengths; + private readonly List initialLengths; + private readonly List finalLengths; + private readonly List instantaneousLengths; public bool Any => initialLengths.Any(); From 36141cb2a4ef2452879cd2932aade19668f8fb35 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 20 Nov 2022 05:14:07 +0300 Subject: [PATCH 27/57] Make BarsInfo a class --- osu.Game/Graphics/UserInterface/BarGraph.cs | 64 ++++++++++----------- 1 file changed, 29 insertions(+), 35 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index 50c634455b..498a480c06 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -42,9 +42,7 @@ namespace osu.Game.Graphics.UserInterface } } - private BarsInfo bars = new BarsInfo(0); - - private float barBreadth; + private readonly BarsInfo bars = new BarsInfo(); /// /// A list of floats that defines the length of each @@ -62,7 +60,7 @@ namespace osu.Game.Graphics.UserInterface int newCount = value.Count(); - barBreadth = 1.0f / newCount; + bars.Breadth = 1.0f / newCount; float maxLength = MaxValue ?? value.Max(); @@ -139,7 +137,8 @@ namespace osu.Game.Graphics.UserInterface private Vector2 drawSize; private BarDirection direction; private float barBreadth; - private BarsInfo bars; + + private readonly List lengths = new List(); public override void ApplyState() { @@ -149,8 +148,10 @@ namespace osu.Game.Graphics.UserInterface texture = Source.texture; drawSize = Source.DrawSize; direction = Source.direction; - barBreadth = Source.barBreadth; - bars = Source.bars; + barBreadth = Source.bars.Breadth; + + lengths.Clear(); + lengths.AddRange(Source.bars.InstantaneousLengths); } public override void Draw(IRenderer renderer) @@ -159,10 +160,10 @@ namespace osu.Game.Graphics.UserInterface shader.Bind(); - for (int i = 0; i < bars.Count; i++) + for (int i = 0; i < lengths.Count; i++) { - float barHeight = drawSize.Y * ((direction == BarDirection.TopToBottom || direction == BarDirection.BottomToTop) ? bars.InstantaneousLength(i) : barBreadth); - float barWidth = drawSize.X * ((direction == BarDirection.LeftToRight || direction == BarDirection.RightToLeft) ? bars.InstantaneousLength(i) : barBreadth); + float barHeight = drawSize.Y * ((direction == BarDirection.TopToBottom || direction == BarDirection.BottomToTop) ? lengths[i] : barBreadth); + float barWidth = drawSize.X * ((direction == BarDirection.LeftToRight || direction == BarDirection.RightToLeft) ? lengths[i] : barBreadth); Vector2 topLeft; @@ -201,31 +202,18 @@ namespace osu.Game.Graphics.UserInterface } } - private readonly struct BarsInfo + private class BarsInfo { - private readonly List initialLengths; - private readonly List finalLengths; - private readonly List instantaneousLengths; + public bool Any => Count > 0; - public bool Any => initialLengths.Any(); + public int Count { get; private set; } - public int Count => initialLengths.Count; + public float Breadth { get; set; } - public BarsInfo(int initialCount) - { - initialLengths = new List(); - finalLengths = new List(); - instantaneousLengths = new List(); + public List InstantaneousLengths { get; } = new List(); - for (int i = 0; i < initialCount; i++) - { - initialLengths.Add(0); - finalLengths.Add(0); - instantaneousLengths.Add(0); - } - } - - public float InstantaneousLength(int index) => instantaneousLengths[index]; + private readonly List initialLengths = new List(); + private readonly List finalLengths = new List(); public void UpdateLength(int index, float newLength) { @@ -237,33 +225,39 @@ namespace osu.Game.Graphics.UserInterface { initialLengths.Add(0); finalLengths.Add(finalLength); - instantaneousLengths.Add(0); + InstantaneousLengths.Add(0); + + Count++; } public void Clear() { initialLengths.Clear(); finalLengths.Clear(); - instantaneousLengths.Clear(); + InstantaneousLengths.Clear(); + + Count = 0; } public void RemoveRange(int index, int count) { initialLengths.RemoveRange(index, count); finalLengths.RemoveRange(index, count); - instantaneousLengths.RemoveRange(index, count); + InstantaneousLengths.RemoveRange(index, count); + + Count -= count; } public void Animate(double animationStartTime, double currentTime) { for (int i = 0; i < Count; i++) - instantaneousLengths[i] = Interpolation.ValueAt(currentTime, initialLengths[i], finalLengths[i], animationStartTime, animationStartTime + resize_duration, easing); + InstantaneousLengths[i] = Interpolation.ValueAt(currentTime, initialLengths[i], finalLengths[i], animationStartTime, animationStartTime + resize_duration, easing); } public void FinishAnimation() { for (int i = 0; i < Count; i++) - instantaneousLengths[i] = finalLengths[i]; + InstantaneousLengths[i] = finalLengths[i]; } } } From 33b2fe46d950fc125035290b8663ffbfc2858d70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sun, 20 Nov 2022 12:29:41 +0100 Subject: [PATCH 28/57] Add xmldoc to `HistoryTextBox` --- osu.Game/Graphics/UserInterface/HistoryTextBox.cs | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs index e9da67e553..5d2cd18e9b 100644 --- a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs +++ b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs @@ -7,6 +7,12 @@ using osuTK.Input; namespace osu.Game.Graphics.UserInterface { + /// + /// A which additionally retains a history of text committed, up to a limit + /// (100 by default, specified in constructor). + /// The history of committed text can be navigated using up/down arrows. + /// This resembles the operation of command-line terminals. + /// public class HistoryTextBox : FocusedTextBox { private readonly LimitedCapacityQueue messageHistory; @@ -18,6 +24,13 @@ namespace osu.Game.Graphics.UserInterface private string originalMessage = string.Empty; private bool everythingSelected; + /// + /// Creates a new . + /// + /// + /// The maximum number of committed lines to keep in history. + /// When exceeded, the oldest lines in history will be dropped to make space for new ones. + /// public HistoryTextBox(int capacity = 100) { messageHistory = new LimitedCapacityQueue(capacity); From 18c79dfda3f08552dbdb518a90cd2e68b863de56 Mon Sep 17 00:00:00 2001 From: Andrei Zavatski Date: Sun, 20 Nov 2022 23:00:13 +0300 Subject: [PATCH 29/57] Move all the logic into BarsInfo class --- osu.Game/Graphics/UserInterface/BarGraph.cs | 74 +++++++++------------ 1 file changed, 30 insertions(+), 44 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/BarGraph.cs b/osu.Game/Graphics/UserInterface/BarGraph.cs index 498a480c06..3f356c0225 100644 --- a/osu.Game/Graphics/UserInterface/BarGraph.cs +++ b/osu.Game/Graphics/UserInterface/BarGraph.cs @@ -58,27 +58,9 @@ namespace osu.Game.Graphics.UserInterface return; } - int newCount = value.Count(); - - bars.Breadth = 1.0f / newCount; - float maxLength = MaxValue ?? value.Max(); - foreach (var bar in value.Select((length, index) => (Value: length, Index: index))) - { - float length = maxLength == 0 ? 0 : Math.Max(0f, bar.Value / maxLength); - - if (bar.Index < bars.Count) - { - bars.UpdateLength(bar.Index, length); - continue; - } - - bars.AddBar(length); - } - - if (bars.Count > newCount) - bars.RemoveRange(newCount, bars.Count - newCount); + bars.SetLengths(value.Select(v => maxLength == 0 ? 0 : Math.Max(0f, v / maxLength)).ToArray()); animationStartTime = Clock.CurrentTime; animationComplete = false; @@ -208,44 +190,48 @@ namespace osu.Game.Graphics.UserInterface public int Count { get; private set; } - public float Breadth { get; set; } + public float Breadth { get; private set; } public List InstantaneousLengths { get; } = new List(); private readonly List initialLengths = new List(); private readonly List finalLengths = new List(); - public void UpdateLength(int index, float newLength) + public void Clear() => SetLengths(Array.Empty()); + + public void SetLengths(float[] newLengths) { - initialLengths[index] = finalLengths[index]; - finalLengths[index] = newLength; - } + int newCount = newLengths.Length; - public void AddBar(float finalLength) - { - initialLengths.Add(0); - finalLengths.Add(finalLength); - InstantaneousLengths.Add(0); + for (int i = 0; i < newCount; i++) + { + // If we have an old bar at this index - change it's length + if (i < Count) + { + initialLengths[i] = finalLengths[i]; + finalLengths[i] = newLengths[i]; - Count++; - } + continue; + } - public void Clear() - { - initialLengths.Clear(); - finalLengths.Clear(); - InstantaneousLengths.Clear(); + // If exceeded old bars count - add new one + initialLengths.Add(0); + finalLengths.Add(newLengths[i]); + InstantaneousLengths.Add(0); + } - Count = 0; - } + // Remove excessive bars + if (Count > newCount) + { + int barsToRemove = Count - newCount; - public void RemoveRange(int index, int count) - { - initialLengths.RemoveRange(index, count); - finalLengths.RemoveRange(index, count); - InstantaneousLengths.RemoveRange(index, count); + initialLengths.RemoveRange(newCount, barsToRemove); + finalLengths.RemoveRange(newCount, barsToRemove); + InstantaneousLengths.RemoveRange(newCount, barsToRemove); + } - Count -= count; + Count = newCount; + Breadth = Count == 0 ? 0 : (1f / Count); } public void Animate(double animationStartTime, double currentTime) From d0ff5be7e646d54e78f123ed7c763615b012c9c9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Nov 2022 12:15:40 +0900 Subject: [PATCH 30/57] Fix `TestSceneTabletSettings` falling off the bottom of the screen --- .../Visual/Settings/TestSceneTabletSettings.cs | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index d1d3748c26..84f5ce7627 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics; using osu.Framework.Input.Handlers.Tablet; using osu.Framework.Testing; using osu.Framework.Utils; +using osu.Game.Graphics.Containers; using osu.Game.Overlays; using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings.Sections.Input; @@ -36,12 +37,17 @@ namespace osu.Game.Tests.Visual.Settings Children = new Drawable[] { - settings = new TabletSettings(tabletHandler) + new OsuScrollContainer(Direction.Vertical) { - RelativeSizeAxes = Axes.None, - Width = SettingsPanel.PANEL_WIDTH, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = settings = new TabletSettings(tabletHandler) + { + RelativeSizeAxes = Axes.None, + Width = SettingsPanel.PANEL_WIDTH, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + } } }; }); From c7ae83768751d64635c287733bc727038616f8f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Nov 2022 12:45:54 +0900 Subject: [PATCH 31/57] Increase maximum aspect ratio for tablet settings to 23:9 / 2.55 --- osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs index f7c372a037..b6efa00cdb 100644 --- a/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Input/TabletSettings.cs @@ -45,9 +45,9 @@ namespace osu.Game.Overlays.Settings.Sections.Input private GameHost host { get; set; } /// - /// Based on ultrawide monitor configurations. + /// Based on ultrawide monitor configurations, plus a bit of lenience for users which are intentionally aiming for higher horizontal velocity. /// - private const float largest_feasible_aspect_ratio = 21f / 9; + private const float largest_feasible_aspect_ratio = 23f / 9; private readonly BindableNumber aspectRatio = new BindableFloat(1) { From 93a189603a3bffd446fa5561a411b08c05ad232d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Nov 2022 14:20:36 +0900 Subject: [PATCH 32/57] Hide spinner ticks / bonus from results screen when not applicable to score --- osu.Game/Scoring/ScoreInfo.cs | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/osu.Game/Scoring/ScoreInfo.cs b/osu.Game/Scoring/ScoreInfo.cs index 1b36ae176d..a9ced62c95 100644 --- a/osu.Game/Scoring/ScoreInfo.cs +++ b/osu.Game/Scoring/ScoreInfo.cs @@ -296,6 +296,13 @@ namespace osu.Game.Scoring break; } + case HitResult.LargeBonus: + case HitResult.SmallBonus: + if (MaximumStatistics.TryGetValue(r.result, out int count) && count > 0) + yield return new HitResultDisplayStatistic(r.result, value, null, r.displayName); + + break; + case HitResult.SmallTickMiss: case HitResult.LargeTickMiss: break; From 981264b011d95332c2d3ac059bdeae4411e5a34d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Nov 2022 14:51:41 +0900 Subject: [PATCH 33/57] Avoid crashing when a system audio device provides a `null` name --- .../Settings/Sections/Audio/AudioDevicesSettings.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs index 7cb9efa1b9..9b53d62272 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/AudioDevicesSettings.cs @@ -59,7 +59,11 @@ namespace osu.Game.Overlays.Settings.Sections.Audio // the dropdown. BASS does not give us a simple mechanism to select // specific audio devices in such a case anyways. Such // functionality would require involved OS-specific code. - dropdown.Items = deviceItems.Distinct().ToList(); + dropdown.Items = deviceItems + // Dropdown doesn't like null items. Somehow we are seeing some arrive here (see https://github.com/ppy/osu/issues/21271) + .Where(i => i != null) + .Distinct() + .ToList(); } protected override void Dispose(bool isDisposing) From 3da21e596af9f376bc54eb8259bd8e0fc533d90f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Nov 2022 16:06:36 +0900 Subject: [PATCH 34/57] Add support for storyboards with incorrect path specifications (`\\` instead of `\`) Closes https://github.com/ppy/osu/issues/21204. --- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 6991500df5..a4e15f790a 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -147,7 +147,11 @@ namespace osu.Game.Beatmaps.Formats ); } - protected string CleanFilename(string path) => path.Trim('"').ToStandardisedPath(); + protected string CleanFilename(string path) => path + // User error which is supported by stable (https://github.com/ppy/osu/issues/21204) + .Replace(@"\\", @"\") + .Trim('"') + .ToStandardisedPath(); public enum Section { From 8f942f130b82b6ec5c37018a4d67d745eb160a28 Mon Sep 17 00:00:00 2001 From: Terochi Date: Mon, 21 Nov 2022 09:32:44 +0100 Subject: [PATCH 35/57] Variant 1: edit changes history, empty text resets index --- .../UserInterface/TestSceneHistoryTextBox.cs | 59 ++++--------------- .../Graphics/UserInterface/HistoryTextBox.cs | 18 ++---- osu.Game/Utils/LimitedCapacityQueue.cs | 7 +++ 3 files changed, 24 insertions(+), 60 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs index 8918e53056..6ead7bc168 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs @@ -3,7 +3,6 @@ using NUnit.Framework; using osu.Framework.Graphics; -using osu.Framework.Input; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -64,62 +63,32 @@ namespace osu.Game.Tests.Visual.UserInterface } [Test] - public void TestEmptyHistory() + public void TestChangedHistory() { + addMessages(2); AddStep("Set text", () => box.Text = temp); + AddStep("Move up", () => InputManager.Key(Key.Up)); + AddStep("Change text", () => box.Text = "New message"); AddStep("Move down", () => InputManager.Key(Key.Down)); - AddAssert("Text is the same", () => box.Text == temp); - - AddStep("Move Up", () => InputManager.Key(Key.Up)); - AddAssert("Text is the same", () => box.Text == temp); + AddStep("Move up", () => InputManager.Key(Key.Up)); + AddAssert("Changes kept", () => box.Text == "New message"); } [Test] - public void TestPartialHistory() + public void TestInputOnEdge() { addMessages(2); AddStep("Set text", () => box.Text = temp); AddStep("Move down", () => InputManager.Key(Key.Down)); - AddAssert("Text is the same", () => box.Text == temp); + AddAssert("Text unchanged", () => box.Text == temp); - AddRepeatStep("Move Up", () => InputManager.Key(Key.Up), 2); + AddRepeatStep("Move up", () => InputManager.Key(Key.Up), 2); AddAssert("Same as 1st message", () => box.Text == "Message 1"); - AddStep("Move Up", () => InputManager.Key(Key.Up)); - AddAssert("Text is the same", () => box.Text == "Message 1"); - - AddRepeatStep("Move down", () => InputManager.Key(Key.Down), 2); - AddAssert("Same as temp message", () => box.Text == temp); - - AddStep("Move down", () => InputManager.Key(Key.Down)); - AddAssert("Text is the same", () => box.Text == temp); - } - - [Test] - public void TestFullHistory() - { - addMessages(5); - AddStep("Set text", () => box.Text = temp); - - AddStep("Move down", () => InputManager.Key(Key.Down)); - AddAssert("Text is the same", () => box.Text == temp); - - addMessages(2); - AddStep("Set text", () => box.Text = temp); - - AddRepeatStep("Move Up", () => InputManager.Key(Key.Up), 5); - AddAssert("Same as 3rd message", () => box.Text == "Message 3"); - - AddStep("Move Up", () => InputManager.Key(Key.Up)); - AddAssert("Text is the same", () => box.Text == "Message 3"); - - AddRepeatStep("Move down", () => InputManager.Key(Key.Down), 4); - AddAssert("Same as previous message", () => box.Text == "Message 7"); - - AddStep("Move down", () => InputManager.Key(Key.Down)); - AddAssert("Same as temp message", () => box.Text == temp); + AddStep("Move up", () => InputManager.Key(Key.Up)); + AddAssert("Text unchanged", () => box.Text == "Message 1"); } [Test] @@ -133,12 +102,6 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("Remove text", () => box.Text = string.Empty); AddStep("Move Up", () => InputManager.Key(Key.Up)); AddAssert("Same as previous message", () => box.Text == "Message 2"); - - AddStep("Move Up", () => InputManager.Key(Key.Up)); - AddStep("Select text", () => InputManager.Keys(PlatformAction.SelectAll)); - AddStep("Replace text", () => box.Text = "New text"); - AddStep("Move Up", () => InputManager.Key(Key.Up)); - AddAssert("Same as previous message", () => box.Text == "Message 2"); } private void addMessages(int count) diff --git a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs index 5d2cd18e9b..b0ca5beb66 100644 --- a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs +++ b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs @@ -22,7 +22,6 @@ namespace osu.Game.Graphics.UserInterface private int selectedIndex; private string originalMessage = string.Empty; - private bool everythingSelected; /// /// Creates a new . @@ -37,21 +36,11 @@ namespace osu.Game.Graphics.UserInterface Current.ValueChanged += text => { - if (string.IsNullOrEmpty(text.NewValue) || everythingSelected) - { + if (string.IsNullOrEmpty(text.NewValue)) selectedIndex = HistoryCount; - everythingSelected = false; - } }; } - protected override void OnTextSelectionChanged(TextSelectionType selectionType) - { - everythingSelected = SelectedText == Text; - - base.OnTextSelectionChanged(selectionType); - } - protected override bool OnKeyDown(KeyDownEvent e) { switch (e.Key) @@ -62,6 +51,8 @@ namespace osu.Game.Graphics.UserInterface if (selectedIndex == HistoryCount) originalMessage = Text; + else if (!string.IsNullOrEmpty(Text)) + messageHistory[selectedIndex] = Text; Text = messageHistory[--selectedIndex]; @@ -71,6 +62,9 @@ namespace osu.Game.Graphics.UserInterface if (selectedIndex == HistoryCount) return true; + if (!string.IsNullOrEmpty(Text)) + messageHistory[selectedIndex] = Text; + if (selectedIndex == HistoryCount - 1) { selectedIndex = HistoryCount; diff --git a/osu.Game/Utils/LimitedCapacityQueue.cs b/osu.Game/Utils/LimitedCapacityQueue.cs index 86a106a678..8f3b36a059 100644 --- a/osu.Game/Utils/LimitedCapacityQueue.cs +++ b/osu.Game/Utils/LimitedCapacityQueue.cs @@ -103,6 +103,13 @@ namespace osu.Game.Utils return array[(start + index) % capacity]; } + set + { + if (index < 0 || index >= Count) + throw new ArgumentOutOfRangeException(nameof(index)); + + array[(start + index) % capacity] = value; + } } /// From 672e1cd45bfc1ad6d495ad833d02c7ae7a9e7b40 Mon Sep 17 00:00:00 2001 From: Terochi Date: Mon, 21 Nov 2022 09:38:33 +0100 Subject: [PATCH 36/57] Variant 2: edit changes history, cannot reset index (similar to stable) --- .../Visual/UserInterface/TestSceneHistoryTextBox.cs | 2 +- osu.Game/Graphics/UserInterface/HistoryTextBox.cs | 6 ------ 2 files changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs index 6ead7bc168..e0f734fce0 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("Remove text", () => box.Text = string.Empty); AddStep("Move Up", () => InputManager.Key(Key.Up)); - AddAssert("Same as previous message", () => box.Text == "Message 2"); + AddAssert("Text unchanged", () => box.Text == string.Empty); } private void addMessages(int count) diff --git a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs index b0ca5beb66..6791812b98 100644 --- a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs +++ b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs @@ -33,12 +33,6 @@ namespace osu.Game.Graphics.UserInterface public HistoryTextBox(int capacity = 100) { messageHistory = new LimitedCapacityQueue(capacity); - - Current.ValueChanged += text => - { - if (string.IsNullOrEmpty(text.NewValue)) - selectedIndex = HistoryCount; - }; } protected override bool OnKeyDown(KeyDownEvent e) From 58288275a6f38291a283840ec12e6009f8479332 Mon Sep 17 00:00:00 2001 From: Terochi Date: Mon, 21 Nov 2022 09:43:36 +0100 Subject: [PATCH 37/57] Variant 3: cannot change history, cannot reset index (the "default") --- .../Visual/UserInterface/TestSceneHistoryTextBox.cs | 2 +- osu.Game/Graphics/UserInterface/HistoryTextBox.cs | 5 ----- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs index e0f734fce0..414c1276e1 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs @@ -72,7 +72,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("Change text", () => box.Text = "New message"); AddStep("Move down", () => InputManager.Key(Key.Down)); AddStep("Move up", () => InputManager.Key(Key.Up)); - AddAssert("Changes kept", () => box.Text == "New message"); + AddAssert("Changes lost", () => box.Text == "Message 2"); } [Test] diff --git a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs index 6791812b98..290457564b 100644 --- a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs +++ b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs @@ -45,8 +45,6 @@ namespace osu.Game.Graphics.UserInterface if (selectedIndex == HistoryCount) originalMessage = Text; - else if (!string.IsNullOrEmpty(Text)) - messageHistory[selectedIndex] = Text; Text = messageHistory[--selectedIndex]; @@ -56,9 +54,6 @@ namespace osu.Game.Graphics.UserInterface if (selectedIndex == HistoryCount) return true; - if (!string.IsNullOrEmpty(Text)) - messageHistory[selectedIndex] = Text; - if (selectedIndex == HistoryCount - 1) { selectedIndex = HistoryCount; From 7dc7729ac2464c9967c1deff87056d1721897626 Mon Sep 17 00:00:00 2001 From: Terochi Date: Mon, 21 Nov 2022 10:11:26 +0100 Subject: [PATCH 38/57] Variant 4: cannot change history, empty text/everything selected resets index (current with bug fix) --- .../UserInterface/TestSceneHistoryTextBox.cs | 9 +++++- .../Graphics/UserInterface/HistoryTextBox.cs | 28 +++++++++++++++++++ 2 files changed, 36 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs index 414c1276e1..29f1e29b55 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Graphics; +using osu.Framework.Input; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -101,7 +102,13 @@ namespace osu.Game.Tests.Visual.UserInterface AddStep("Remove text", () => box.Text = string.Empty); AddStep("Move Up", () => InputManager.Key(Key.Up)); - AddAssert("Text unchanged", () => box.Text == string.Empty); + AddAssert("Same as previous message", () => box.Text == "Message 2"); + + AddStep("Move Up", () => InputManager.Key(Key.Up)); + AddStep("Select text", () => InputManager.Keys(PlatformAction.SelectAll)); + AddStep("Replace text", () => box.Text = "New text"); + AddStep("Move Up", () => InputManager.Key(Key.Up)); + AddAssert("Same as previous message", () => box.Text == "Message 2"); } private void addMessages(int count) diff --git a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs index 290457564b..f2b975226c 100644 --- a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs +++ b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs @@ -22,6 +22,7 @@ namespace osu.Game.Graphics.UserInterface private int selectedIndex; private string originalMessage = string.Empty; + private bool everythingSelected; /// /// Creates a new . @@ -33,6 +34,29 @@ namespace osu.Game.Graphics.UserInterface public HistoryTextBox(int capacity = 100) { messageHistory = new LimitedCapacityQueue(capacity); + + Current.ValueChanged += text => + { + if (string.IsNullOrEmpty(text.NewValue) || everythingSelected) + { + selectedIndex = HistoryCount; + everythingSelected = false; + } + }; + } + + protected override void OnTextDeselected() + { + base.OnTextDeselected(); + + everythingSelected = false; + } + + protected override void OnTextSelectionChanged(TextSelectionType selectionType) + { + base.OnTextSelectionChanged(selectionType); + + everythingSelected = SelectedText == Text; } protected override bool OnKeyDown(KeyDownEvent e) @@ -43,6 +67,8 @@ namespace osu.Game.Graphics.UserInterface if (selectedIndex == 0) return true; + everythingSelected = false; + if (selectedIndex == HistoryCount) originalMessage = Text; @@ -54,6 +80,8 @@ namespace osu.Game.Graphics.UserInterface if (selectedIndex == HistoryCount) return true; + everythingSelected = false; + if (selectedIndex == HistoryCount - 1) { selectedIndex = HistoryCount; From d81ef541bc8ff8f49119b4491a6842277c60983c Mon Sep 17 00:00:00 2001 From: Terochi Date: Mon, 21 Nov 2022 10:17:28 +0100 Subject: [PATCH 39/57] Variant 5: cannot change history, edit resets index --- .../UserInterface/TestSceneHistoryTextBox.cs | 9 +------- .../Graphics/UserInterface/HistoryTextBox.cs | 22 +------------------ 2 files changed, 2 insertions(+), 29 deletions(-) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs index 29f1e29b55..78b1bdb18c 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs @@ -3,7 +3,6 @@ using NUnit.Framework; using osu.Framework.Graphics; -using osu.Framework.Input; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -100,13 +99,7 @@ namespace osu.Game.Tests.Visual.UserInterface AddRepeatStep("Move Up", () => InputManager.Key(Key.Up), 2); AddAssert("Same as 1st message", () => box.Text == "Message 1"); - AddStep("Remove text", () => box.Text = string.Empty); - AddStep("Move Up", () => InputManager.Key(Key.Up)); - AddAssert("Same as previous message", () => box.Text == "Message 2"); - - AddStep("Move Up", () => InputManager.Key(Key.Up)); - AddStep("Select text", () => InputManager.Keys(PlatformAction.SelectAll)); - AddStep("Replace text", () => box.Text = "New text"); + AddStep("Change text", () => box.Text = "New message"); AddStep("Move Up", () => InputManager.Key(Key.Up)); AddAssert("Same as previous message", () => box.Text == "Message 2"); } diff --git a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs index f2b975226c..0958e1832e 100644 --- a/osu.Game/Graphics/UserInterface/HistoryTextBox.cs +++ b/osu.Game/Graphics/UserInterface/HistoryTextBox.cs @@ -22,7 +22,6 @@ namespace osu.Game.Graphics.UserInterface private int selectedIndex; private string originalMessage = string.Empty; - private bool everythingSelected; /// /// Creates a new . @@ -37,28 +36,13 @@ namespace osu.Game.Graphics.UserInterface Current.ValueChanged += text => { - if (string.IsNullOrEmpty(text.NewValue) || everythingSelected) + if (selectedIndex != HistoryCount && text.NewValue != messageHistory[selectedIndex]) { selectedIndex = HistoryCount; - everythingSelected = false; } }; } - protected override void OnTextDeselected() - { - base.OnTextDeselected(); - - everythingSelected = false; - } - - protected override void OnTextSelectionChanged(TextSelectionType selectionType) - { - base.OnTextSelectionChanged(selectionType); - - everythingSelected = SelectedText == Text; - } - protected override bool OnKeyDown(KeyDownEvent e) { switch (e.Key) @@ -67,8 +51,6 @@ namespace osu.Game.Graphics.UserInterface if (selectedIndex == 0) return true; - everythingSelected = false; - if (selectedIndex == HistoryCount) originalMessage = Text; @@ -80,8 +62,6 @@ namespace osu.Game.Graphics.UserInterface if (selectedIndex == HistoryCount) return true; - everythingSelected = false; - if (selectedIndex == HistoryCount - 1) { selectedIndex = HistoryCount; From b404b87f6830824d104362dc33a1cc640547912b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Nov 2022 18:26:53 +0900 Subject: [PATCH 40/57] Realign white line on argon hold note ends to match hit target --- .../Skinning/Argon/ArgonHoldNoteTailPiece.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs index e1068c6cd8..00cd37b6cf 100644 --- a/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs +++ b/osu.Game.Rulesets.Mania/Skinning/Argon/ArgonHoldNoteTailPiece.cs @@ -38,6 +38,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon }, new Container { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, RelativeSizeAxes = Axes.Both, Height = 0.82f, Masking = true, @@ -54,6 +56,8 @@ namespace osu.Game.Rulesets.Mania.Skinning.Argon { RelativeSizeAxes = Axes.X, Height = ArgonNotePiece.CORNER_RADIUS * 2, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, }, }; } From ff5cb116f03ef2850b5d5dd68533da5bb5a1b059 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Nov 2022 19:27:06 +0100 Subject: [PATCH 41/57] Fix weird scroll container sizing --- osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs index 84f5ce7627..5e75bd7bc1 100644 --- a/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs +++ b/osu.Game.Tests/Visual/Settings/TestSceneTabletSettings.cs @@ -39,8 +39,7 @@ namespace osu.Game.Tests.Visual.Settings { new OsuScrollContainer(Direction.Vertical) { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, + RelativeSizeAxes = Axes.Both, Child = settings = new TabletSettings(tabletHandler) { RelativeSizeAxes = Axes.None, From 735cac3104f4ba43eaa9fbdb0d909f506dfe58ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Nov 2022 20:56:38 +0100 Subject: [PATCH 42/57] Rewrite rank graph test to use more modern style --- .../Visual/Online/TestSceneRankGraph.cs | 87 ++++++++++++------- 1 file changed, 58 insertions(+), 29 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs b/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs index 1b1a5c7c6a..304c710b10 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs @@ -1,13 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -#nullable disable - using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Testing; using osu.Game.Graphics; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; @@ -23,33 +22,14 @@ namespace osu.Game.Tests.Visual.Online [Cached] private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink); - public TestSceneRankGraph() + private RankGraph graph = null!; + + private const int history_length = 89; + + [SetUpSteps] + public void SetUpSteps() { - RankGraph graph; - - int[] data = new int[89]; - int[] dataWithZeros = new int[89]; - int[] smallData = new int[89]; - int[] edgyData = new int[89]; - - for (int i = 0; i < 89; i++) - data[i] = dataWithZeros[i] = (i + 1) * 1000; - - for (int i = 20; i < 60; i++) - dataWithZeros[i] = 0; - - for (int i = 79; i < 89; i++) - smallData[i] = 100000 - i * 1000; - - bool edge = true; - - for (int i = 0; i < 20; i++) - { - edgyData[i] = 100000 + (edge ? 1000 : -1000) * (i + 1); - edge = !edge; - } - - Add(new Container + AddStep("create graph", () => Child = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, @@ -67,8 +47,14 @@ namespace osu.Game.Tests.Visual.Online } } }); + } + [Test] + public void TestNullUser() => AddStep("null user", () => graph.Statistics.Value = null); + + [Test] + public void TestRankOnly() => AddStep("rank only", () => { graph.Statistics.Value = new UserStatistics @@ -78,6 +64,14 @@ namespace osu.Game.Tests.Visual.Online }; }); + [Test] + public void TestWithRankHistory() + { + int[] data = new int[history_length]; + + for (int i = 0; i < history_length; i++) + data[i] = (i + 1) * 1000; + AddStep("with rank history", () => { graph.Statistics.Value = new UserStatistics @@ -86,10 +80,22 @@ namespace osu.Game.Tests.Visual.Online PP = 12345, RankHistory = new APIRankHistory { - Data = data, + Data = data } }; }); + } + + [Test] + public void TestRanksWithZeroValues() + { + int[] dataWithZeros = new int[history_length]; + + for (int i = 0; i < history_length; i++) + { + if (i < 20 || i >= 60) + dataWithZeros[i] = (i + 1) * 1000; + } AddStep("with zero values", () => { @@ -103,6 +109,15 @@ namespace osu.Game.Tests.Visual.Online } }; }); + } + + [Test] + public void TestSmallAmountOfData() + { + int[] smallData = new int[history_length]; + + for (int i = history_length - 10; i < history_length; i++) + smallData[i] = 100000 - i * 1000; AddStep("small amount of data", () => { @@ -116,6 +131,20 @@ namespace osu.Game.Tests.Visual.Online } }; }); + } + + [Test] + public void TestHistoryWithEdges() + { + int[] edgyData = new int[89]; + + bool edge = true; + + for (int i = 0; i < 20; i++) + { + edgyData[i] = 100000 + (edge ? 1000 : -1000) * (i + 1); + edge = !edge; + } AddStep("graph with edges", () => { From 1777a6013619b021c078224ff02c86aad21014ea Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Nov 2022 20:58:52 +0100 Subject: [PATCH 43/57] Add failing assertions --- .../Visual/Online/TestSceneRankGraph.cs | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs b/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs index 304c710b10..ce4d11800b 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; @@ -8,6 +9,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Testing; using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.Profile.Header.Components; @@ -50,11 +52,15 @@ namespace osu.Game.Tests.Visual.Online } [Test] - public void TestNullUser() => + public void TestNullUser() + { AddStep("null user", () => graph.Statistics.Value = null); + AddAssert("line graph hidden", () => this.ChildrenOfType().All(graph => graph.Alpha == 0)); + } [Test] - public void TestRankOnly() => + public void TestRankOnly() + { AddStep("rank only", () => { graph.Statistics.Value = new UserStatistics @@ -63,6 +69,8 @@ namespace osu.Game.Tests.Visual.Online PP = 12345, }; }); + AddAssert("line graph hidden", () => this.ChildrenOfType().All(graph => graph.Alpha == 0)); + } [Test] public void TestWithRankHistory() @@ -84,6 +92,7 @@ namespace osu.Game.Tests.Visual.Online } }; }); + AddAssert("line graph shown", () => this.ChildrenOfType().All(graph => graph.Alpha == 1)); } [Test] @@ -109,6 +118,7 @@ namespace osu.Game.Tests.Visual.Online } }; }); + AddAssert("line graph shown", () => this.ChildrenOfType().All(graph => graph.Alpha == 1)); } [Test] @@ -131,6 +141,7 @@ namespace osu.Game.Tests.Visual.Online } }; }); + AddAssert("line graph shown", () => this.ChildrenOfType().All(graph => graph.Alpha == 1)); } [Test] @@ -158,6 +169,7 @@ namespace osu.Game.Tests.Visual.Online } }; }); + AddAssert("line graph shown", () => this.ChildrenOfType().All(graph => graph.Alpha == 1)); } } } From 41039340cfcaf81dee840f56812a1f0f6974fa96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 21 Nov 2022 21:00:06 +0100 Subject: [PATCH 44/57] Fix rank graphs not showing in test due to unset `IsRanked` --- osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs b/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs index ce4d11800b..d05f1f02f7 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneRankGraph.cs @@ -65,6 +65,7 @@ namespace osu.Game.Tests.Visual.Online { graph.Statistics.Value = new UserStatistics { + IsRanked = true, GlobalRank = 123456, PP = 12345, }; @@ -84,6 +85,7 @@ namespace osu.Game.Tests.Visual.Online { graph.Statistics.Value = new UserStatistics { + IsRanked = true, GlobalRank = 89000, PP = 12345, RankHistory = new APIRankHistory @@ -110,6 +112,7 @@ namespace osu.Game.Tests.Visual.Online { graph.Statistics.Value = new UserStatistics { + IsRanked = true, GlobalRank = 89000, PP = 12345, RankHistory = new APIRankHistory @@ -133,6 +136,7 @@ namespace osu.Game.Tests.Visual.Online { graph.Statistics.Value = new UserStatistics { + IsRanked = true, GlobalRank = 12000, PP = 12345, RankHistory = new APIRankHistory @@ -161,6 +165,7 @@ namespace osu.Game.Tests.Visual.Online { graph.Statistics.Value = new UserStatistics { + IsRanked = true, GlobalRank = 12000, PP = 12345, RankHistory = new APIRankHistory From 57723107dd313833902032268921f779346f5472 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Nov 2022 16:15:32 +0900 Subject: [PATCH 45/57] Fix osu!mania hold notes occasionally getting in a visually incorrect hit state To correctly end a mania hold note, `endHold()` needs to be called. This was not happening in a very specific scenario: - The hold note's head is not hit - The user pressed the column's key within the hold note's tail's window, but does so to hit the next object (a note in proximity to the hold note's tail). - The hit policy forces a miss on the hold note, but `endHold()` is not called - `CheckForResult` is not called after this point due to `Judged` being `true`. Closes #21311. --- .../Objects/Drawables/DrawableHoldNote.cs | 14 ++++++++++++-- .../Objects/Drawables/DrawableManiaHitObject.cs | 2 +- 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs index c68eec610c..86d4ad0a36 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableHoldNote.cs @@ -262,14 +262,24 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables tick.MissForcefully(); } - ApplyResult(r => r.Type = Tail.IsHit ? r.Judgement.MaxResult : r.Judgement.MinResult); - endHold(); + if (Tail.IsHit) + ApplyResult(r => r.Type = r.Judgement.MaxResult); + else + MissForcefully(); } if (Tail.Judged && !Tail.IsHit) HoldBrokenTime = Time.Current; } + public override void MissForcefully() + { + base.MissForcefully(); + + // Important that this is always called when a result is applied. + endHold(); + } + public bool OnPressed(KeyBindingPressEvent e) { if (AllJudged) diff --git a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs index 73dc937a00..51f8ab1cf8 100644 --- a/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs +++ b/osu.Game.Rulesets.Mania/Objects/Drawables/DrawableManiaHitObject.cs @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables /// /// Causes this to get missed, disregarding all conditions in implementations of . /// - public void MissForcefully() => ApplyResult(r => r.Type = r.Judgement.MinResult); + public virtual void MissForcefully() => ApplyResult(r => r.Type = r.Judgement.MinResult); } public abstract class DrawableManiaHitObject : DrawableManiaHitObject From c1b193149c67b061c7c5597b953f5e5b0aa487de Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Nov 2022 17:09:10 +0900 Subject: [PATCH 46/57] Add test coverage of input near end of hold note with nearby subsequent note Covers the scenarios mentioned in #21371. Turns out this seems mostly okay already, so there are no fixes applied here. --- .../TestSceneHoldNoteInput.cs | 165 +++++++++++++++++- 1 file changed, 160 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs index 0296303867..0cba2076be 100644 --- a/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs +++ b/osu.Game.Rulesets.Mania.Tests/TestSceneHoldNoteInput.cs @@ -23,6 +23,15 @@ using osu.Game.Tests.Visual; namespace osu.Game.Rulesets.Mania.Tests { + /// + /// Diagrams in this class are represented as: + /// - : time + /// O : note + /// [ ] : hold note + /// + /// x : button press + /// o : button release + /// public class TestSceneHoldNoteInput : RateAdjustedBeatmapTestScene { private const double time_before_head = 250; @@ -223,6 +232,149 @@ namespace osu.Game.Rulesets.Mania.Tests assertTailJudgement(HitResult.Meh); } + /// + /// -----[ ]-O------------- + /// xo o + /// + [Test] + public void TestPressAndReleaseJustBeforeTailWithNearbyNoteAndCloseByHead() + { + Note note; + + const int duration = 50; + + var beatmap = new Beatmap + { + HitObjects = + { + // hold note is very short, to make the head still in range + new HoldNote + { + StartTime = time_head, + Duration = duration, + Column = 0, + }, + { + // Next note within tail lenience + note = new Note + { + StartTime = time_head + duration + 10 + } + } + }, + BeatmapInfo = + { + Difficulty = new BeatmapDifficulty { SliderTickRate = 4 }, + Ruleset = new ManiaRuleset().RulesetInfo + }, + }; + + performTest(new List + { + new ManiaReplayFrame(time_head + duration, ManiaAction.Key1), + new ManiaReplayFrame(time_head + duration + 10), + }, beatmap); + + assertHeadJudgement(HitResult.Good); + assertTailJudgement(HitResult.Perfect); + + assertHitObjectJudgement(note, HitResult.Miss); + } + + /// + /// -----[ ]--O-- + /// xo o + /// + [Test] + public void TestPressAndReleaseJustBeforeTailWithNearbyNote() + { + Note note; + + var beatmap = new Beatmap + { + HitObjects = + { + new HoldNote + { + StartTime = time_head, + Duration = time_tail - time_head, + Column = 0, + }, + { + // Next note within tail lenience + note = new Note + { + StartTime = time_tail + 50 + } + } + }, + BeatmapInfo = + { + Difficulty = new BeatmapDifficulty { SliderTickRate = 4 }, + Ruleset = new ManiaRuleset().RulesetInfo + }, + }; + + performTest(new List + { + new ManiaReplayFrame(time_tail - 10, ManiaAction.Key1), + new ManiaReplayFrame(time_tail), + }, beatmap); + + assertHeadJudgement(HitResult.Miss); + assertTickJudgement(HitResult.LargeTickMiss); + assertTailJudgement(HitResult.Miss); + + assertHitObjectJudgement(note, HitResult.Good); + } + + /// + /// -----[ ]--O-- + /// xo o + /// + [Test] + public void TestPressAndReleaseJustAfterTailWithNearbyNote() + { + Note note; + + var beatmap = new Beatmap + { + HitObjects = + { + new HoldNote + { + StartTime = time_head, + Duration = time_tail - time_head, + Column = 0, + }, + { + // Next note within tail lenience + note = new Note + { + StartTime = time_tail + 50 + } + } + }, + BeatmapInfo = + { + Difficulty = new BeatmapDifficulty { SliderTickRate = 4 }, + Ruleset = new ManiaRuleset().RulesetInfo + }, + }; + + performTest(new List + { + new ManiaReplayFrame(time_tail + 10, ManiaAction.Key1), + new ManiaReplayFrame(time_tail + 20), + }, beatmap); + + assertHeadJudgement(HitResult.Miss); + assertTickJudgement(HitResult.LargeTickMiss); + assertTailJudgement(HitResult.Miss); + + assertHitObjectJudgement(note, HitResult.Great); + } + /// /// -----[ ]----- /// xo o @@ -351,20 +503,23 @@ namespace osu.Game.Rulesets.Mania.Tests .All(j => j.Type.IsHit())); } + private void assertHitObjectJudgement(HitObject hitObject, HitResult result) + => AddAssert($"object judged as {result}", () => judgementResults.First(j => j.HitObject == hitObject).Type, () => Is.EqualTo(result)); + private void assertHeadJudgement(HitResult result) - => AddAssert($"head judged as {result}", () => judgementResults.First(j => j.HitObject is Note).Type == result); + => AddAssert($"head judged as {result}", () => judgementResults.First(j => j.HitObject is Note).Type, () => Is.EqualTo(result)); private void assertTailJudgement(HitResult result) - => AddAssert($"tail judged as {result}", () => judgementResults.Single(j => j.HitObject is TailNote).Type == result); + => AddAssert($"tail judged as {result}", () => judgementResults.Single(j => j.HitObject is TailNote).Type, () => Is.EqualTo(result)); private void assertNoteJudgement(HitResult result) - => AddAssert($"hold note judged as {result}", () => judgementResults.Single(j => j.HitObject is HoldNote).Type == result); + => AddAssert($"hold note judged as {result}", () => judgementResults.Single(j => j.HitObject is HoldNote).Type, () => Is.EqualTo(result)); private void assertTickJudgement(HitResult result) - => AddAssert($"any tick judged as {result}", () => judgementResults.Where(j => j.HitObject is HoldNoteTick).Any(j => j.Type == result)); + => AddAssert($"any tick judged as {result}", () => judgementResults.Where(j => j.HitObject is HoldNoteTick).Select(j => j.Type), () => Does.Contain(result)); private void assertLastTickJudgement(HitResult result) - => AddAssert($"last tick judged as {result}", () => judgementResults.Last(j => j.HitObject is HoldNoteTick).Type == result); + => AddAssert($"last tick judged as {result}", () => judgementResults.Last(j => j.HitObject is HoldNoteTick).Type, () => Is.EqualTo(result)); private ScoreAccessibleReplayPlayer currentPlayer; From de163b2bb55752d48b405a5fd3ed3f2f6104b172 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Nov 2022 19:48:55 +0900 Subject: [PATCH 47/57] Change song select to only allow volume adjusting if alt is held while scrolling --- osu.Game/Screens/Select/SongSelect.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 5d5019567a..18ea0f69a2 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -304,6 +304,16 @@ namespace osu.Game.Screens.Select modSelectOverlayRegistration = OverlayManager?.RegisterBlockingOverlay(ModSelect); } + protected override bool OnScroll(ScrollEvent e) + { + // Match stable behaviour of only alt-scroll adjusting volume. + // Supporting scroll adjust without a modifier key just feels bad, since there are so many scrollable elements on the screen. + if (!e.CurrentState.Keyboard.AltPressed) + return true; + + return base.OnScroll(e); + } + /// /// Creates the buttons to be displayed in the footer. /// From 5343f6922cbfdae09fb87654e2aa1d198a71c270 Mon Sep 17 00:00:00 2001 From: Joppe27 Date: Tue, 22 Nov 2022 17:22:00 +0100 Subject: [PATCH 48/57] Add legacy circle piece animations based on combo --- .../Skinning/Legacy/LegacyCirclePiece.cs | 44 ++++++++++++++++++- 1 file changed, 43 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs index 6b2576a564..2a260b8cb3 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs @@ -2,13 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Animations; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; +using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Taiko.Objects; +using osu.Game.Screens.Play; using osu.Game.Skinning; using osuTK; using osuTK.Graphics; @@ -18,6 +21,13 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy public class LegacyCirclePiece : CompositeDrawable, IHasAccentColour { private Drawable backgroundLayer = null!; + private Drawable? foregroundLayer; + + private Bindable currentCombo { get; } = new BindableInt(); + + private int animationFrame; + private int multiplier; + private double beatLength; // required for editor blueprints (not sure why these circle pieces are zero size). public override Quad ScreenSpaceDrawQuad => backgroundLayer.ScreenSpaceDrawQuad; @@ -27,6 +37,12 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy RelativeSizeAxes = Axes.Both; } + [Resolved] + private GameplayState? gameplayState { get; set; } + + [Resolved] + private IBeatSyncProvider? beatSyncProvider { get; set; } + [BackgroundDependencyLoader] private void load(ISkinSource skin, DrawableHitObject drawableHitObject) { @@ -45,7 +61,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy // backgroundLayer is guaranteed to exist due to the pre-check in TaikoLegacySkinTransformer. AddInternal(backgroundLayer = new LegacyKiaiFlashingDrawable(() => getDrawableFor("circle"))); - var foregroundLayer = getDrawableFor("circleoverlay"); + foregroundLayer = getDrawableFor("circleoverlay"); if (foregroundLayer != null) AddInternal(foregroundLayer); @@ -58,6 +74,9 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy c.Anchor = Anchor.Centre; c.Origin = Anchor.Centre; } + + if (gameplayState != null) + currentCombo.BindTo(gameplayState.ScoreProcessor.Combo); } protected override void LoadComplete() @@ -74,6 +93,29 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy // This ensures they are scaled relative to each other but also match the expected DrawableHit size. foreach (var c in InternalChildren) c.Scale = new Vector2(DrawHeight / 128); + + if (currentCombo.Value >= 150) + { + multiplier = 2; + } + else if (currentCombo.Value >= 50) + { + multiplier = 1; + } + else + { + (foregroundLayer as IFramedAnimation)?.GotoFrame(0); + return; + } + + if (beatSyncProvider?.ControlPoints != null) + { + beatLength = beatSyncProvider.ControlPoints.TimingPointAt(LifetimeStart).BeatLength; + + animationFrame = Time.Current % ((beatLength * 2) / multiplier) >= beatLength / multiplier ? 0 : 1; + + (foregroundLayer as IFramedAnimation)?.GotoFrame(animationFrame); + } } private Color4 accentColour; From db34fa99b1adc5a1d422da1b93e129fdcda31dbd Mon Sep 17 00:00:00 2001 From: Joppe27 Date: Tue, 22 Nov 2022 17:22:34 +0100 Subject: [PATCH 49/57] Add tests and clean up inefficient code --- .../Skinning/TestSceneDrawableDrumRoll.cs | 25 ++++++++- .../Skinning/TestSceneDrawableDrumRollKiai.cs | 30 ---------- .../Skinning/TestSceneDrawableHit.cs | 55 +++++++++++++++++++ .../Skinning/TestSceneDrawableHitKiai.cs | 30 ---------- 4 files changed, 79 insertions(+), 61 deletions(-) delete mode 100644 osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableDrumRollKiai.cs delete mode 100644 osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHitKiai.cs diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableDrumRoll.cs index e42dc254ac..7a3439d8eb 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableDrumRoll.cs @@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning }; [Test] - public void DrumrollTest() + public void TestDrumroll() { AddStep("Drum roll", () => SetContents(_ => { @@ -57,6 +57,14 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning })); } + [Test] + public void TestDrumrollKiai() + { + AddStep("Create beatmap", createBeatmap); + + TestDrumroll(); + } + private DrumRoll createDrumRollAtCurrentTime(bool strong = false) { var drumroll = new DrumRoll @@ -73,5 +81,20 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning return drumroll; } + + private void createBeatmap() + { + var controlPointInfo = new ControlPointInfo(); + + controlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 }); + controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true }); + + Beatmap.Value = CreateWorkingBeatmap(new Beatmap + { + ControlPointInfo = controlPointInfo + }); + + Beatmap.Value.Track.Start(); + } } } diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableDrumRollKiai.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableDrumRollKiai.cs deleted file mode 100644 index 53977150e7..0000000000 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableDrumRollKiai.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using NUnit.Framework; -using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Beatmaps; - -namespace osu.Game.Rulesets.Taiko.Tests.Skinning -{ - [TestFixture] - public class TestSceneDrawableDrumRollKiai : TestSceneDrawableDrumRoll - { - [SetUp] - public void SetUp() => Schedule(() => - { - var controlPointInfo = new ControlPointInfo(); - - controlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 }); - controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true }); - - Beatmap.Value = CreateWorkingBeatmap(new Beatmap - { - ControlPointInfo = controlPointInfo - }); - - // track needs to be playing for BeatSyncedContainer to work. - Beatmap.Value.Track.Start(); - }); - } -} diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs index eb2b6c1d74..3902a3123b 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs @@ -4,17 +4,23 @@ #nullable disable using NUnit.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects.Drawables; +using osu.Game.Screens.Play; +using osu.Game.Tests.Gameplay; namespace osu.Game.Rulesets.Taiko.Tests.Skinning { [TestFixture] public class TestSceneDrawableHit : TaikoSkinnableTestScene { + [Cached] + private GameplayState gameplayState = TestGameplayState.Create(new TaikoRuleset()); + [Test] public void TestHits() { @@ -43,6 +49,38 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning })); } + [Test] + public void TestHitKiai() + { + AddStep("Create beatmap", () => createBeatmap(true)); + + TestHits(); + } + + [Test] + public void TestHitAnimationSlow() + { + AddStep("Create beatmap", () => createBeatmap(false)); + + AddStep("Reset combo", () => gameplayState.ScoreProcessor.Combo.Value = 0); + + AddRepeatStep("Increase combo", () => gameplayState.ScoreProcessor.Combo.Value++, 50); + + TestHits(); + } + + [Test] + public void TestHitAnimationFast() + { + AddStep("Create beatmap", () => createBeatmap(false)); + + AddStep("Reset combo", () => gameplayState.ScoreProcessor.Combo.Value = 0); + + AddRepeatStep("Increase combo", () => gameplayState.ScoreProcessor.Combo.Value++, 150); + + TestHits(); + } + private Hit createHitAtCurrentTime(bool strong = false, bool rim = false) { var hit = new Hit @@ -56,5 +94,22 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning return hit; } + + private void createBeatmap(bool includeKiai) + { + var controlPointInfo = new ControlPointInfo(); + + controlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 }); + + if (includeKiai) + controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true }); + + Beatmap.Value = CreateWorkingBeatmap(new Beatmap + { + ControlPointInfo = controlPointInfo + }); + + Beatmap.Value.Track.Start(); + } } } diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHitKiai.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHitKiai.cs deleted file mode 100644 index fac0530749..0000000000 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHitKiai.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using NUnit.Framework; -using osu.Game.Beatmaps; -using osu.Game.Beatmaps.ControlPoints; - -namespace osu.Game.Rulesets.Taiko.Tests.Skinning -{ - [TestFixture] - public class TestSceneDrawableHitKiai : TestSceneDrawableHit - { - [SetUp] - public void SetUp() => Schedule(() => - { - var controlPointInfo = new ControlPointInfo(); - - controlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 }); - controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true }); - - Beatmap.Value = CreateWorkingBeatmap(new Beatmap - { - ControlPointInfo = controlPointInfo - }); - - // track needs to be playing for BeatSyncedContainer to work. - Beatmap.Value.Track.Start(); - }); - } -} From 39934bccd6f78d7d328b1b7b9901147304a9c08b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Nov 2022 19:47:41 +0100 Subject: [PATCH 50/57] Add back removed test coverage --- .../UserInterface/TestSceneHistoryTextBox.cs | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs index 78b1bdb18c..8ed5dd43cc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneHistoryTextBox.cs @@ -62,6 +62,68 @@ namespace osu.Game.Tests.Visual.UserInterface }); } + [Test] + public void TestEmptyHistory() + { + AddStep("Set text", () => box.Text = temp); + + AddStep("Move down", () => InputManager.Key(Key.Down)); + AddAssert("Text is unchanged", () => box.Text == temp); + + AddStep("Move up", () => InputManager.Key(Key.Up)); + AddAssert("Text is unchanged", () => box.Text == temp); + } + + [Test] + public void TestPartialHistory() + { + addMessages(3); + AddStep("Set text", () => box.Text = temp); + + AddStep("Move down", () => InputManager.Key(Key.Down)); + AddAssert("Text is unchanged", () => box.Text == temp); + + AddRepeatStep("Move up", () => InputManager.Key(Key.Up), 3); + AddAssert("Same as 1st message", () => box.Text == "Message 1"); + + AddStep("Move up", () => InputManager.Key(Key.Up)); + AddAssert("Same as 1st message", () => box.Text == "Message 1"); + + AddStep("Move down", () => InputManager.Key(Key.Down)); + AddAssert("Same as 2nd message", () => box.Text == "Message 2"); + + AddRepeatStep("Move down", () => InputManager.Key(Key.Down), 2); + AddAssert("Temporary message restored", () => box.Text == temp); + + AddStep("Move down", () => InputManager.Key(Key.Down)); + AddAssert("Text is unchanged", () => box.Text == temp); + } + + [Test] + public void TestFullHistory() + { + addMessages(7); + AddStep("Set text", () => box.Text = temp); + + AddStep("Move down", () => InputManager.Key(Key.Down)); + AddAssert("Text is unchanged", () => box.Text == temp); + + AddRepeatStep("Move up", () => InputManager.Key(Key.Up), 5); + AddAssert("Same as 3rd message", () => box.Text == "Message 3"); + + AddStep("Move up", () => InputManager.Key(Key.Up)); + AddAssert("Same as 3rd message", () => box.Text == "Message 3"); + + AddRepeatStep("Move down", () => InputManager.Key(Key.Down), 4); + AddAssert("Same as 7th message", () => box.Text == "Message 7"); + + AddStep("Move down", () => InputManager.Key(Key.Down)); + AddAssert("Temporary message restored", () => box.Text == temp); + + AddStep("Move down", () => InputManager.Key(Key.Down)); + AddAssert("Text is unchanged", () => box.Text == temp); + } + [Test] public void TestChangedHistory() { From 8dbc38e17a83e3737c17b0faa72dc047a5fd95f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Nov 2022 20:14:22 +0100 Subject: [PATCH 51/57] Remove unused setter --- osu.Game/Utils/LimitedCapacityQueue.cs | 7 ------- 1 file changed, 7 deletions(-) diff --git a/osu.Game/Utils/LimitedCapacityQueue.cs b/osu.Game/Utils/LimitedCapacityQueue.cs index 8f3b36a059..86a106a678 100644 --- a/osu.Game/Utils/LimitedCapacityQueue.cs +++ b/osu.Game/Utils/LimitedCapacityQueue.cs @@ -103,13 +103,6 @@ namespace osu.Game.Utils return array[(start + index) % capacity]; } - set - { - if (index < 0 || index >= Count) - throw new ArgumentOutOfRangeException(nameof(index)); - - array[(start + index) % capacity] = value; - } } /// From 5a5b0ed4ef67ded0211486baf887ac251645b931 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Nov 2022 20:28:41 +0100 Subject: [PATCH 52/57] Restructure tests not to call each other Bit weird to have tests call other tests. Private helper methods is better, if unavoidable. --- .../Skinning/TestSceneDrawableDrumRoll.cs | 18 ++--- .../Skinning/TestSceneDrawableHit.cs | 68 +++++++++---------- 2 files changed, 40 insertions(+), 46 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableDrumRoll.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableDrumRoll.cs index 7a3439d8eb..25cb3f7886 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableDrumRoll.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableDrumRoll.cs @@ -26,8 +26,10 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning }; [Test] - public void TestDrumroll() + public void TestDrumroll([Values] bool withKiai) { + AddStep("set up beatmap", () => setUpBeatmap(withKiai)); + AddStep("Drum roll", () => SetContents(_ => { var hoc = new ScrollingHitObjectContainer(); @@ -57,14 +59,6 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning })); } - [Test] - public void TestDrumrollKiai() - { - AddStep("Create beatmap", createBeatmap); - - TestDrumroll(); - } - private DrumRoll createDrumRollAtCurrentTime(bool strong = false) { var drumroll = new DrumRoll @@ -82,12 +76,14 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning return drumroll; } - private void createBeatmap() + private void setUpBeatmap(bool withKiai) { var controlPointInfo = new ControlPointInfo(); controlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 }); - controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true }); + + if (withKiai) + controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true }); Beatmap.Value = CreateWorkingBeatmap(new Beatmap { diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs index 3902a3123b..b13255b31c 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs @@ -22,7 +22,37 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning private GameplayState gameplayState = TestGameplayState.Create(new TaikoRuleset()); [Test] - public void TestHits() + public void TestHits([Values] bool withKiai) + { + AddStep("Create beatmap", () => setUpBeatmap(withKiai)); + addHitSteps(); + } + + [Test] + public void TestHitAnimationSlow() + { + AddStep("Create beatmap", () => setUpBeatmap(false)); + + AddStep("Reset combo", () => gameplayState.ScoreProcessor.Combo.Value = 0); + + AddRepeatStep("Increase combo", () => gameplayState.ScoreProcessor.Combo.Value++, 50); + + addHitSteps(); + } + + [Test] + public void TestHitAnimationFast() + { + AddStep("Create beatmap", () => setUpBeatmap(false)); + + AddStep("Reset combo", () => gameplayState.ScoreProcessor.Combo.Value = 0); + + AddRepeatStep("Increase combo", () => gameplayState.ScoreProcessor.Combo.Value++, 150); + + addHitSteps(); + } + + private void addHitSteps() { AddStep("Centre hit", () => SetContents(_ => new DrawableHit(createHitAtCurrentTime()) { @@ -49,38 +79,6 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning })); } - [Test] - public void TestHitKiai() - { - AddStep("Create beatmap", () => createBeatmap(true)); - - TestHits(); - } - - [Test] - public void TestHitAnimationSlow() - { - AddStep("Create beatmap", () => createBeatmap(false)); - - AddStep("Reset combo", () => gameplayState.ScoreProcessor.Combo.Value = 0); - - AddRepeatStep("Increase combo", () => gameplayState.ScoreProcessor.Combo.Value++, 50); - - TestHits(); - } - - [Test] - public void TestHitAnimationFast() - { - AddStep("Create beatmap", () => createBeatmap(false)); - - AddStep("Reset combo", () => gameplayState.ScoreProcessor.Combo.Value = 0); - - AddRepeatStep("Increase combo", () => gameplayState.ScoreProcessor.Combo.Value++, 150); - - TestHits(); - } - private Hit createHitAtCurrentTime(bool strong = false, bool rim = false) { var hit = new Hit @@ -95,13 +93,13 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning return hit; } - private void createBeatmap(bool includeKiai) + private void setUpBeatmap(bool withKiai) { var controlPointInfo = new ControlPointInfo(); controlPointInfo.Add(0, new TimingControlPoint { BeatLength = 500 }); - if (includeKiai) + if (withKiai) controlPointInfo.Add(0, new EffectControlPoint { KiaiMode = true }); Beatmap.Value = CreateWorkingBeatmap(new Beatmap From 8ac0a759f01b92c665d6108fc1271e5f70590475 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Nov 2022 20:30:27 +0100 Subject: [PATCH 53/57] Set combo immediately rather than via repeat steps Doesn't help anyone to be waiting literal minutes for combo to hit 50 or 150 in a test scene supposed to quickly visually demonstrate a component. Doesn't help for CI runtime, either. --- .../Skinning/TestSceneDrawableHit.cs | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs index b13255b31c..65a59be89b 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs @@ -33,10 +33,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning { AddStep("Create beatmap", () => setUpBeatmap(false)); - AddStep("Reset combo", () => gameplayState.ScoreProcessor.Combo.Value = 0); - - AddRepeatStep("Increase combo", () => gameplayState.ScoreProcessor.Combo.Value++, 50); - + AddStep("Set 50 combo", () => gameplayState.ScoreProcessor.Combo.Value = 50); addHitSteps(); } @@ -45,10 +42,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning { AddStep("Create beatmap", () => setUpBeatmap(false)); - AddStep("Reset combo", () => gameplayState.ScoreProcessor.Combo.Value = 0); - - AddRepeatStep("Increase combo", () => gameplayState.ScoreProcessor.Combo.Value++, 150); - + AddStep("Set 150 combo", () => gameplayState.ScoreProcessor.Combo.Value = 150); addHitSteps(); } From 38f2a27f537e12d9deef306e327715983afecb33 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Nov 2022 20:34:43 +0100 Subject: [PATCH 54/57] Split animation logic to its own method Also add a guard, to bypass all of it if the foreground layer is not in fact animatable. --- .../Skinning/Legacy/LegacyCirclePiece.cs | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs index 2a260b8cb3..c8dc29f444 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs @@ -26,7 +26,6 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy private Bindable currentCombo { get; } = new BindableInt(); private int animationFrame; - private int multiplier; private double beatLength; // required for editor blueprints (not sure why these circle pieces are zero size). @@ -94,6 +93,14 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy foreach (var c in InternalChildren) c.Scale = new Vector2(DrawHeight / 128); + if (foregroundLayer is IFramedAnimation animatableForegroundLayer) + animateForegroundLayer(animatableForegroundLayer); + } + + private void animateForegroundLayer(IFramedAnimation animatableForegroundLayer) + { + int multiplier; + if (currentCombo.Value >= 150) { multiplier = 2; @@ -104,7 +111,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy } else { - (foregroundLayer as IFramedAnimation)?.GotoFrame(0); + animatableForegroundLayer.GotoFrame(0); return; } @@ -114,7 +121,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy animationFrame = Time.Current % ((beatLength * 2) / multiplier) >= beatLength / multiplier ? 0 : 1; - (foregroundLayer as IFramedAnimation)?.GotoFrame(animationFrame); + animatableForegroundLayer.GotoFrame(animationFrame); } } From ce7af0df637b3098c8e5eca1d9d137bc5fe2ed94 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Nov 2022 20:39:22 +0100 Subject: [PATCH 55/57] Always use current timing point for circle piece animation Using `LifetimeStart` seemed arbitrary and wrong not only in a compatibility-with-stable sense, but also in a general sanity sense (why would each object potentially be using a different timing point to animate?) --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs index c8dc29f444..03d89d269b 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs @@ -117,7 +117,7 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy if (beatSyncProvider?.ControlPoints != null) { - beatLength = beatSyncProvider.ControlPoints.TimingPointAt(LifetimeStart).BeatLength; + beatLength = beatSyncProvider.ControlPoints.TimingPointAt(Time.Current).BeatLength; animationFrame = Time.Current % ((beatLength * 2) / multiplier) >= beatLength / multiplier ? 0 : 1; From 675e32df5761cd81c7365aa7aaac7116bcd6da9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Nov 2022 20:43:22 +0100 Subject: [PATCH 56/57] Add test steps for testing combo reset to 0 --- osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs index 65a59be89b..adfd27c5d6 100644 --- a/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs +++ b/osu.Game.Rulesets.Taiko.Tests/Skinning/TestSceneDrawableHit.cs @@ -35,6 +35,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning AddStep("Set 50 combo", () => gameplayState.ScoreProcessor.Combo.Value = 50); addHitSteps(); + AddStep("Reset combo", () => gameplayState.ScoreProcessor.Combo.Value = 0); } [Test] @@ -44,6 +45,7 @@ namespace osu.Game.Rulesets.Taiko.Tests.Skinning AddStep("Set 150 combo", () => gameplayState.ScoreProcessor.Combo.Value = 150); addHitSteps(); + AddStep("Reset combo", () => gameplayState.ScoreProcessor.Combo.Value = 0); } private void addHitSteps() From 6e9d163c7240eba2964e4043229fa5f7bdcacf0d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 22 Nov 2022 20:56:07 +0100 Subject: [PATCH 57/57] Specify `canBeNull: true` in `[Resolved]` for now --- osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs index 03d89d269b..a5867ff51c 100644 --- a/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs +++ b/osu.Game.Rulesets.Taiko/Skinning/Legacy/LegacyCirclePiece.cs @@ -36,10 +36,10 @@ namespace osu.Game.Rulesets.Taiko.Skinning.Legacy RelativeSizeAxes = Axes.Both; } - [Resolved] + [Resolved(canBeNull: true)] private GameplayState? gameplayState { get; set; } - [Resolved] + [Resolved(canBeNull: true)] private IBeatSyncProvider? beatSyncProvider { get; set; } [BackgroundDependencyLoader]