From ee2a247c6681e7b82a7e30b50d72ad991b27bebc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 25 Jun 2025 10:56:10 +0200 Subject: [PATCH 1/2] Add failing test case --- .../Visual/Ranking/TestSceneUserTagControl.cs | 55 ++++++++++++++++++- .../Ranking/UserTagControl_DrawableUserTag.cs | 2 +- 2 files changed, 55 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneUserTagControl.cs b/osu.Game.Tests/Visual/Ranking/TestSceneUserTagControl.cs index c546c9727c..b63f8ca31c 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneUserTagControl.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneUserTagControl.cs @@ -2,8 +2,10 @@ // 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; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Testing; using osu.Game.Beatmaps; @@ -14,10 +16,12 @@ using osu.Game.Overlays; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Taiko; using osu.Game.Screens.Ranking; +using osuTK; +using osuTK.Input; namespace osu.Game.Tests.Visual.Ranking { - public partial class TestSceneUserTagControl : OsuTestScene + public partial class TestSceneUserTagControl : OsuManualInputManagerTestScene { [Cached] private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); @@ -63,6 +67,8 @@ namespace osu.Game.Tests.Visual.Ranking beatmapSet.Beatmaps.Single().TopTags = [ new APIBeatmapTag { TagId = 3, VoteCount = 9 }, + new APIBeatmapTag { TagId = 2, VoteCount = 8 }, + new APIBeatmapTag { TagId = 0, VoteCount = 7 }, ]; Scheduler.AddDelayed(() => getBeatmapSetRequest.TriggerSuccess(beatmapSet), 500); return true; @@ -79,6 +85,11 @@ namespace osu.Game.Tests.Visual.Ranking return false; }; }); + } + + [Test] + public void TestRulesetSupport() + { AddStep("show for osu! beatmap", () => { var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); @@ -86,6 +97,7 @@ namespace osu.Game.Tests.Visual.Ranking Beatmap.Value = working; recreateControl(); }); + AddStep("show for taiko beatmap", () => { var working = CreateWorkingBeatmap(new TaikoRuleset().RulesetInfo); @@ -95,6 +107,47 @@ namespace osu.Game.Tests.Visual.Ranking }); } + [Test] + public void TestTagsDoNotMoveUntilMouseMovesAway() + { + AddStep("show", () => + { + var working = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + working.BeatmapInfo.OnlineID = 42; + Beatmap.Value = working; + recreateControl(); + }); + AddUntilStep("wait for ready", () => getTagFlow().Count, () => Is.EqualTo(4)); + AddAssert("tag 2 is second", () => getTagFlow().GetLayoutPosition(getDrawableTagById(2)), () => Is.EqualTo(1)); + AddStep("vote for tag 2", () => + { + InputManager.MoveMouseTo(getDrawableTagById(2)); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("tag 2 voted for", () => getDrawableTagById(2).UserTag.VoteCount.Value, () => Is.EqualTo(9)); + + AddStep("remove vote for tag 2", () => + { + InputManager.MoveMouseTo(getDrawableTagById(2)); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("tag 2 not voted for", () => getDrawableTagById(2).UserTag.VoteCount.Value, () => Is.EqualTo(8)); + AddAssert("tag 2 is still second", () => getTagFlow().GetLayoutPosition(getDrawableTagById(2)), () => Is.EqualTo(1)); + + AddStep("vote for tag 2", () => + { + InputManager.MoveMouseTo(getDrawableTagById(2)); + InputManager.Click(MouseButton.Left); + }); + AddUntilStep("tag 2 voted for", () => getDrawableTagById(2).UserTag.VoteCount.Value, () => Is.EqualTo(9)); + AddStep("move mouse away", () => InputManager.MoveMouseTo(Vector2.Zero)); + AddAssert("tag 2 reordered to first", () => getTagFlow().GetLayoutPosition(getDrawableTagById(2)), () => Is.EqualTo(0)); + + FillFlowContainer getTagFlow() => this.ChildrenOfType>().Single(); + + UserTagControl.DrawableUserTag getDrawableTagById(long id) => getTagFlow().Single(t => t.UserTag.Id == id); + } + private void recreateControl() { Child = new PopoverContainer diff --git a/osu.Game/Screens/Ranking/UserTagControl_DrawableUserTag.cs b/osu.Game/Screens/Ranking/UserTagControl_DrawableUserTag.cs index e54d88bca2..ff3c0711c0 100644 --- a/osu.Game/Screens/Ranking/UserTagControl_DrawableUserTag.cs +++ b/osu.Game/Screens/Ranking/UserTagControl_DrawableUserTag.cs @@ -18,7 +18,7 @@ namespace osu.Game.Screens.Ranking { public partial class UserTagControl { - private partial class DrawableUserTag : OsuAnimatedButton + public partial class DrawableUserTag : OsuAnimatedButton { public readonly UserTag UserTag; From 7975120d5d67c488dc4a75966098616abe20405e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Wed, 25 Jun 2025 11:02:16 +0200 Subject: [PATCH 2/2] Fix user tags moving in the control after voting Closes https://github.com/ppy/osu/issues/33877. Most likely regressed when the user tags were changed such that the loading spinner that shows on adding/removing a vote was introdiced to every individual tag separately. This in turn means that the `LoadingLayer` responsible for showing the spinner also briefly consumes all input when visible, which also means that the control briefly becomes unhovered, breaking the logic. This probably doesn't work on mobile because mobile input sucks. On iOS simulator it looks somewhat fine in that the tags don't move until you touch the screen anywhere else which seems okay if that's what actually what happens on device as well. And if it isn't I'm not sure I can do anything sane about it anyway. --- osu.Game/Screens/Ranking/UserTagControl.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/UserTagControl.cs b/osu.Game/Screens/Ranking/UserTagControl.cs index e323107783..5618dd2490 100644 --- a/osu.Game/Screens/Ranking/UserTagControl.cs +++ b/osu.Game/Screens/Ranking/UserTagControl.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Configuration; @@ -44,6 +45,8 @@ namespace osu.Game.Screens.Ranking private AddNewTagUserTag addNewTagUserTag = null!; + private InputManager inputManager = null!; + [Resolved] private IAPIProvider api { get; set; } = null!; @@ -124,6 +127,8 @@ namespace osu.Game.Screens.Ranking updateTags(); displayedTags.BindCollectionChanged(displayTags, true); + + inputManager = GetContainingInputManager()!; } private void updateTags() @@ -251,7 +256,7 @@ namespace osu.Game.Screens.Ranking { base.Update(); - if (!layout.IsValid && !IsHovered) + if (!layout.IsValid && !Contains(inputManager.CurrentState.Mouse.Position)) { var sortedTags = new Dictionary( displayedTags.OrderByDescending(t => t.VoteCount.Value)