From 9b9cfbc9c2fda9cbc62e9357b9384eaa3220b811 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 28 Mar 2025 13:24:26 +0100 Subject: [PATCH 01/23] Redesign vote buttons to better work with hierarchical tags --- .../Visual/Ranking/TestSceneUserTagControl.cs | 8 ++-- osu.Game/Screens/Ranking/UserTagControl.cs | 42 +++++++++++++++---- 2 files changed, 38 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneUserTagControl.cs b/osu.Game.Tests/Visual/Ranking/TestSceneUserTagControl.cs index d622df8d76..f05aa46054 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneUserTagControl.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneUserTagControl.cs @@ -36,10 +36,10 @@ namespace osu.Game.Tests.Visual.Ranking { Tags = [ - new APITag { Id = 1, Name = "tech", Description = "Tests uncommon skills.", }, - new APITag { Id = 2, Name = "alt", Description = "Colloquial term for maps which use rhythms that encourage the player to alternate notes. Typically distinct from burst or stream maps.", }, - new APITag { Id = 3, Name = "aim", Description = "Category for difficulty relating to cursor movement.", }, - new APITag { Id = 4, Name = "tap", Description = "Category for difficulty relating to tapping input.", }, + new APITag { Id = 1, Name = "song representation/simple", Description = "Accessible and straightforward map design.", }, + new APITag { Id = 2, Name = "style/clean", Description = "Visually uncluttered and organised patterns, often involving few overlaps and equal visual spacing between objects.", }, + new APITag { Id = 3, Name = "aim/aim control", Description = "Patterns with velocity or direction changes which strongly go against a player's natural movement pattern.", }, + new APITag { Id = 4, Name = "tap/bursts", Description = "Patterns requiring continuous movement and alternating, typically 9 notes or less.", }, ] }), 500); return true; diff --git a/osu.Game/Screens/Ranking/UserTagControl.cs b/osu.Game/Screens/Ranking/UserTagControl.cs index ae4a918ae5..f516a80cfb 100644 --- a/osu.Game/Screens/Ranking/UserTagControl.cs +++ b/osu.Game/Screens/Ranking/UserTagControl.cs @@ -249,6 +249,7 @@ namespace osu.Game.Screens.Ranking private Box mainBackground = null!; private Box voteBackground = null!; + private OsuSpriteText tagCategoryText = null!; private OsuSpriteText tagNameText = null!; private OsuSpriteText voteCountText = null!; private LoadingSpinner spinner = null!; @@ -276,6 +277,8 @@ namespace osu.Game.Screens.Ranking [BackgroundDependencyLoader] private void load() { + string[] tagParts = UserTag.Name.Split('/'); + Anchor = Anchor.Centre; Origin = Anchor.Centre; CornerRadius = 8; @@ -297,21 +300,42 @@ namespace osu.Game.Screens.Ranking { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Padding = new MarginPadding { Left = 6, Right = 3, Vertical = 3, }, - Spacing = new Vector2(5), Children = new Drawable[] { - tagNameText = new OsuSpriteText + tagCategoryText = new OsuSpriteText { - Text = UserTag.Name, + Alpha = tagParts.Length > 1 ? 0.6f : 0, + Text = tagParts[0], Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Horizontal = 6 } + }, + new Container + { + RelativeSizeAxes = Axes.Y, + AutoSizeAxes = Axes.X, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.1f, + Blending = BlendingParameters.Additive, + }, + tagNameText = new OsuSpriteText + { + Text = tagParts[^1], + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Horizontal = 6 } + }, + } }, new Container { AutoSizeAxes = Axes.Both, - CornerRadius = 5, - Masking = true, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Children = new Drawable[] @@ -353,7 +377,7 @@ namespace osu.Game.Screens.Ranking { if (v.NewValue) { - voteBackground.FadeColour(colours.Lime3, transition_duration, Easing.OutQuint); + voteBackground.FadeColour(colours.Lime2, transition_duration, Easing.OutQuint); voteCountText.FadeColour(Colour4.Black, transition_duration, Easing.OutQuint); } else @@ -366,13 +390,15 @@ namespace osu.Game.Screens.Ranking { if (c.NewValue) { - mainBackground.FadeColour(colours.Lime1, transition_duration, Easing.OutQuint); + mainBackground.FadeColour(colours.Lime2, transition_duration, Easing.OutQuint); + tagCategoryText.FadeColour(Colour4.Black, transition_duration, Easing.OutQuint); tagNameText.FadeColour(Colour4.Black, transition_duration, Easing.OutQuint); FadeEdgeEffectTo(0.5f, transition_duration, Easing.OutQuint); } else { mainBackground.FadeColour(colours.Gray4, transition_duration, Easing.OutQuint); + tagCategoryText.FadeColour(Colour4.White, transition_duration, Easing.OutQuint); tagNameText.FadeColour(Colour4.White, transition_duration, Easing.OutQuint); FadeEdgeEffectTo(0f, transition_duration, Easing.OutQuint); } From 1d44d5e10088f6d94ac45d3c0ca98df5ef40601f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 28 Mar 2025 14:05:35 +0100 Subject: [PATCH 02/23] Move the extra tag selector out of popover --- .../Ranking/TestSceneStatisticsPanel.cs | 12 +- osu.Game/Screens/Ranking/UserTagControl.cs | 200 ++++++++---------- 2 files changed, 93 insertions(+), 119 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index 814c0519a3..ea80f2c5b2 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -214,14 +214,10 @@ namespace osu.Game.Tests.Visual.Ranking { Tags = [ - new APITag { Id = 1, Name = "tech", Description = "Tests uncommon skills.", }, - new APITag - { - Id = 2, Name = "alt", - Description = "Colloquial term for maps which use rhythms that encourage the player to alternate notes. Typically distinct from burst or stream maps.", - }, - new APITag { Id = 3, Name = "aim", Description = "Category for difficulty relating to cursor movement.", }, - new APITag { Id = 4, Name = "tap", Description = "Category for difficulty relating to tapping input.", }, + new APITag { Id = 1, Name = "song representation/simple", Description = "Accessible and straightforward map design.", }, + new APITag { Id = 2, Name = "style/clean", Description = "Visually uncluttered and organised patterns, often involving few overlaps and equal visual spacing between objects.", }, + new APITag { Id = 3, Name = "aim/aim control", Description = "Patterns with velocity or direction changes which strongly go against a player's natural movement pattern.", }, + new APITag { Id = 4, Name = "tap/bursts", Description = "Patterns requiring continuous movement and alternating, typically 9 notes or less.", }, ] }), 500); return true; diff --git a/osu.Game/Screens/Ranking/UserTagControl.cs b/osu.Game/Screens/Ranking/UserTagControl.cs index f516a80cfb..3a71aaadd6 100644 --- a/osu.Game/Screens/Ranking/UserTagControl.cs +++ b/osu.Game/Screens/Ranking/UserTagControl.cs @@ -8,15 +8,11 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; -using osu.Framework.Extensions; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; @@ -26,12 +22,9 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osu.Game.Graphics.UserInterfaceV2; -using osu.Game.Input.Bindings; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; -using osu.Game.Overlays; using osuTK; using osuTK.Input; @@ -66,35 +59,53 @@ namespace osu.Game.Screens.Ranking private void load(SessionStatics sessionStatics) { AutoSizeAxes = Axes.Y; + InternalChildren = new Drawable[] { - new FillFlowContainer + new GridContainer { - Direction = FillDirection.Vertical, RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Spacing = new Vector2(8), - Children = new Drawable[] + Padding = new MarginPadding(10), + ColumnDimensions = + [ + new Dimension(GridSizeMode.Absolute, 300), + new Dimension() + ], + RowDimensions = [new Dimension(GridSizeMode.AutoSize, minSize: 250)], + Content = new[] { - tagFlow = new FillFlowContainer + new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Full, - LayoutDuration = 300, - LayoutEasing = Easing.OutQuint, - Spacing = new Vector2(4), - }, - new AddTagsButton - { - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre, - OnTagSelected = onExtraTagSelected, - AvailableTags = { BindTarget = extraTags }, - }, - }, + new FillFlowContainer + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(8), + Children = new Drawable[] + { + tagFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Full, + LayoutDuration = 300, + LayoutEasing = Easing.OutQuint, + Spacing = new Vector2(4), + }, + }, + }, + new TagList + { + RelativeSizeAxes = Axes.Both, + AvailableTags = { BindTarget = extraTags }, + OnSelected = onExtraTagSelected, + } + } + } }, - loadingLayer = new LoadingLayer + loadingLayer = new LoadingLayer(dimBackground: true) { RelativeSizeAxes = Axes.Both, State = { Value = Visibility.Visible } @@ -239,6 +250,8 @@ namespace osu.Game.Screens.Ranking } } + protected override bool OnClick(ClickEvent e) => true; + private partial class DrawableUserTag : OsuAnimatedButton { public readonly UserTag UserTag; @@ -279,8 +292,8 @@ namespace osu.Game.Screens.Ranking { string[] tagParts = UserTag.Name.Split('/'); - Anchor = Anchor.Centre; - Origin = Anchor.Centre; + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; CornerRadius = 8; Masking = true; EdgeEffect = new EdgeEffectParameters @@ -452,35 +465,7 @@ namespace osu.Game.Screens.Ranking } } - private partial class AddTagsButton : GrayButton, IHasPopover - { - public BindableList AvailableTags { get; } = new BindableList(); - - public Action? OnTagSelected { get; set; } - - public AddTagsButton() - : base(FontAwesome.Solid.Plus) - { - Size = new Vector2(30); - - Action = this.ShowPopover; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - AvailableTags.BindCollectionChanged((_, _) => Enabled.Value = AvailableTags.Count > 0, true); - } - - public Popover GetPopover() => new AddTagsPopover - { - AvailableTags = { BindTarget = AvailableTags }, - OnSelected = OnTagSelected, - }; - } - - private partial class AddTagsPopover : OsuPopover + private partial class TagList : CompositeDrawable { private SearchTextBox searchBox = null!; private SearchContainer searchContainer = null!; @@ -490,33 +475,44 @@ namespace osu.Game.Screens.Ranking public Action? OnSelected { get; set; } [BackgroundDependencyLoader] - private void load() + private void load(OsuColour colours) { - Child = new OsuScrollContainer + Masking = true; + CornerRadius = 5; + + InternalChildren = new Drawable[] { - Width = 250, - Height = 250, - ScrollbarOverlapsContent = false, - Children = new Drawable[] + new Box { - searchBox = new SearchTextBox - { - HoldFocus = true, - RelativeSizeAxes = Axes.X, - }, - searchContainer = new SearchContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Padding = new MarginPadding { Right = 5, Top = 50, }, - Spacing = new Vector2(10), - ChildrenEnumerable = AvailableTags.Select(tag => new DrawableAddableTag(tag) - { - Action = () => select(tag) - }) - } + RelativeSizeAxes = Axes.Both, + Alpha = 0.1f, }, + new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + searchBox = new SearchTextBox + { + HoldFocus = true, + RelativeSizeAxes = Axes.X, + Depth = float.MinValue, + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10) { Top = 45, }, + ScrollbarOverlapsContent = false, + Child = searchContainer = new SearchContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10), + } + } + }, + } }; } @@ -524,27 +520,15 @@ namespace osu.Game.Screens.Ranking { base.LoadComplete(); - searchBox.Current.BindValueChanged(_ => searchContainer.SearchTerm = searchBox.Current.Value, true); - } - - public override bool OnPressed(KeyBindingPressEvent e) - { - if (base.OnPressed(e)) - return true; - - if (e.Repeat) - return false; - - if (State.Value == Visibility.Hidden) - return false; - - if (e.Action == GlobalAction.Select) + AvailableTags.BindCollectionChanged((_, _) => { - attemptSelect(); - return true; - } - - return false; + searchContainer.Clear(); + searchContainer.ChildrenEnumerable = AvailableTags.Select(tag => new DrawableAddableTag(tag) + { + Action = () => OnSelected?.Invoke(tag) + }); + }, true); + searchBox.Current.BindValueChanged(_ => searchContainer.SearchTerm = searchBox.Current.Value, true); } protected override bool OnKeyDown(KeyDownEvent e) @@ -563,13 +547,7 @@ namespace osu.Game.Screens.Ranking var visibleItems = searchContainer.OfType().Where(d => d.IsPresent).ToArray(); if (visibleItems.Length == 1) - select(visibleItems.Single().Tag); - } - - private void select(UserTag tag) - { - OnSelected?.Invoke(tag); - this.HidePopover(); + OnSelected?.Invoke(visibleItems.Single().Tag); } private partial class DrawableAddableTag : OsuAnimatedButton, IFilterable @@ -586,14 +564,14 @@ namespace osu.Game.Screens.Ranking } [BackgroundDependencyLoader] - private void load(OsuColour colours, OverlayColourProvider? colourProvider) + private void load(OsuColour colours) { Content.AddRange(new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, - Colour = colourProvider?.Background3 ?? colours.GreySeaFoamDark, + Colour = colours.Gray6, Depth = float.MaxValue, }, new FillFlowContainer From dadb014a69d971bfc04392d67c7719958560be7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 31 Mar 2025 09:49:49 +0200 Subject: [PATCH 03/23] Adjust transform --- osu.Game/Screens/Ranking/UserTagControl.cs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/osu.Game/Screens/Ranking/UserTagControl.cs b/osu.Game/Screens/Ranking/UserTagControl.cs index 3a71aaadd6..a8f6f2fcb1 100644 --- a/osu.Game/Screens/Ranking/UserTagControl.cs +++ b/osu.Game/Screens/Ranking/UserTagControl.cs @@ -604,6 +604,15 @@ namespace osu.Game.Screens.Ranking public bool MatchingFilter { set => Alpha = value ? 1 : 0; } public bool FilteringActive { set { } } + + protected override bool OnMouseDown(MouseDownEvent e) + { + bool result = base.OnMouseDown(e); + // slightly dodgy way of overriding the amount of scale-on-click (the default is way too much in this case) + ClearTransforms(targetMember: nameof(Scale)); + Content.ScaleTo(0.95f, 2000, Easing.OutQuint); + return result; + } } } } From 624948f0826ad5ae2f1548295f12778508a8dd3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 31 Mar 2025 10:39:34 +0200 Subject: [PATCH 04/23] Add groupings to all tags list --- .../Visual/Ranking/TestSceneUserTagControl.cs | 14 ++-- .../Statistics/StatisticItemContainer.cs | 39 +---------- .../Ranking/Statistics/StatisticItemHeader.cs | 68 +++++++++++++++++++ osu.Game/Screens/Ranking/UserTag.cs | 10 ++- osu.Game/Screens/Ranking/UserTagControl.cs | 59 ++++++++++++---- 5 files changed, 131 insertions(+), 59 deletions(-) create mode 100644 osu.Game/Screens/Ranking/Statistics/StatisticItemHeader.cs diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneUserTagControl.cs b/osu.Game.Tests/Visual/Ranking/TestSceneUserTagControl.cs index f05aa46054..cfedd89b12 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneUserTagControl.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneUserTagControl.cs @@ -3,7 +3,6 @@ using System.Linq; using osu.Framework.Graphics; -using osu.Framework.Graphics.Cursor; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Online.API; @@ -36,6 +35,7 @@ namespace osu.Game.Tests.Visual.Ranking { Tags = [ + new APITag { Id = 0, Name = "uncategorised tag", Description = "This probably isn't real but could be and should be handled.", }, new APITag { Id = 1, Name = "song representation/simple", Description = "Accessible and straightforward map design.", }, new APITag { Id = 2, Name = "style/clean", Description = "Visually uncluttered and organised patterns, often involving few overlaps and equal visual spacing between objects.", }, new APITag { Id = 3, Name = "aim/aim control", Description = "Patterns with velocity or direction changes which strongly go against a player's natural movement pattern.", }, @@ -69,15 +69,11 @@ namespace osu.Game.Tests.Visual.Ranking }); AddStep("create control", () => { - Child = new PopoverContainer + Child = new UserTagControl(Beatmap.Value.BeatmapInfo) { - RelativeSizeAxes = Axes.Both, - Child = new UserTagControl(Beatmap.Value.BeatmapInfo) - { - Width = 500, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - } + Width = 700, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, }; }); } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticItemContainer.cs b/osu.Game/Screens/Ranking/Statistics/StatisticItemContainer.cs index 6e18ae1fe4..8caf8d66b5 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticItemContainer.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticItemContainer.cs @@ -1,15 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Localisation; using osu.Game.Graphics; -using osu.Game.Graphics.Sprites; -using osuTK; namespace osu.Game.Screens.Ranking.Statistics { @@ -53,7 +50,9 @@ namespace osu.Game.Screens.Ranking.Statistics Padding = new MarginPadding(5), Children = new[] { - createHeader(item), + LocalisableString.IsNullOrEmpty(item.Name) + ? Empty() + : new StatisticItemHeader { Text = item.Name }, new Container { RelativeSizeAxes = Axes.X, @@ -66,37 +65,5 @@ namespace osu.Game.Screens.Ranking.Statistics } }; } - - private static Drawable createHeader(StatisticItem item) - { - if (LocalisableString.IsNullOrEmpty(item.Name)) - return Empty(); - - return new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - Height = 20, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(5, 0), - Children = new Drawable[] - { - new Circle - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Height = 9, - Width = 4, - Colour = Color4Extensions.FromHex("#00FFAA") - }, - new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Text = item.Name, - Font = OsuFont.GetFont(size: StatisticItem.FONT_SIZE, weight: FontWeight.SemiBold), - } - } - }; - } } } diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticItemHeader.cs b/osu.Game/Screens/Ranking/Statistics/StatisticItemHeader.cs new file mode 100644 index 0000000000..6b496e10dd --- /dev/null +++ b/osu.Game/Screens/Ranking/Statistics/StatisticItemHeader.cs @@ -0,0 +1,68 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Localisation; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Screens.Ranking.Statistics +{ + public partial class StatisticItemHeader : CompositeDrawable, IHasText + { + public LocalisableString Text + { + get => text; + set + { + if (text == value) return; + + text = value; + if (IsLoaded) + spriteText.Text = value; + } + } + + private LocalisableString text; + private OsuSpriteText spriteText = null!; + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChild = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + Height = 20, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5, 0), + Children = new Drawable[] + { + new Circle + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Height = 9, + Width = 4, + Colour = Color4Extensions.FromHex("#00FFAA") + }, + spriteText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = text, + Font = OsuFont.GetFont(size: StatisticItem.FONT_SIZE, weight: FontWeight.SemiBold), + } + } + }; + } + } +} diff --git a/osu.Game/Screens/Ranking/UserTag.cs b/osu.Game/Screens/Ranking/UserTag.cs index d44e531330..983f585931 100644 --- a/osu.Game/Screens/Ranking/UserTag.cs +++ b/osu.Game/Screens/Ranking/UserTag.cs @@ -9,7 +9,9 @@ namespace osu.Game.Screens.Ranking public record UserTag { public long Id { get; } - public string Name { get; } + public string FullName { get; } + public string? GroupName { get; } + public string DisplayName { get; } public string Description { get; } public BindableInt VoteCount { get; } = new BindableInt(); @@ -18,8 +20,12 @@ namespace osu.Game.Screens.Ranking public UserTag(APITag tag) { Id = tag.Id; - Name = tag.Name; + FullName = tag.Name; Description = tag.Description; + + string[] splitName = FullName.Split('/'); + GroupName = splitName.Length > 1 ? splitName[0] : null; + DisplayName = splitName[^1]; } } } diff --git a/osu.Game/Screens/Ranking/UserTagControl.cs b/osu.Game/Screens/Ranking/UserTagControl.cs index a8f6f2fcb1..95a72f2142 100644 --- a/osu.Game/Screens/Ranking/UserTagControl.cs +++ b/osu.Game/Screens/Ranking/UserTagControl.cs @@ -25,6 +25,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Screens.Ranking.Statistics; using osuTK; using osuTK.Input; @@ -290,8 +291,6 @@ namespace osu.Game.Screens.Ranking [BackgroundDependencyLoader] private void load() { - string[] tagParts = UserTag.Name.Split('/'); - Anchor = Anchor.CentreLeft; Origin = Anchor.CentreLeft; CornerRadius = 8; @@ -317,8 +316,8 @@ namespace osu.Game.Screens.Ranking { tagCategoryText = new OsuSpriteText { - Alpha = tagParts.Length > 1 ? 0.6f : 0, - Text = tagParts[0], + Alpha = UserTag.GroupName != null ? 0.6f : 0, + Text = UserTag.GroupName ?? default(LocalisableString), Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Margin = new MarginPadding { Horizontal = 6 } @@ -339,7 +338,7 @@ namespace osu.Game.Screens.Ranking }, tagNameText = new OsuSpriteText { - Text = tagParts[^1], + Text = UserTag.DisplayName, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Margin = new MarginPadding { Horizontal = 6 } @@ -497,6 +496,7 @@ namespace osu.Game.Screens.Ranking HoldFocus = true, RelativeSizeAxes = Axes.X, Depth = float.MinValue, + Y = -2, // hacky compensation for masking issues }, new OsuScrollContainer { @@ -523,14 +523,26 @@ namespace osu.Game.Screens.Ranking AvailableTags.BindCollectionChanged((_, _) => { searchContainer.Clear(); - searchContainer.ChildrenEnumerable = AvailableTags.Select(tag => new DrawableAddableTag(tag) - { - Action = () => OnSelected?.Invoke(tag) - }); + searchContainer.ChildrenEnumerable = createItems(AvailableTags); }, true); searchBox.Current.BindValueChanged(_ => searchContainer.SearchTerm = searchBox.Current.Value, true); } + private IEnumerable createItems(IEnumerable tags) + { + var grouped = tags.GroupBy(tag => tag.GroupName).OrderBy(group => group.Key); + + foreach (var group in grouped) + { + var drawableGroup = new GroupFlow(group.Key); + + foreach (var tag in group.OrderBy(t => t.FullName)) + drawableGroup.Add(new DrawableAddableTag(tag) { Action = () => OnSelected?.Invoke(tag) }); + + yield return drawableGroup; + } + } + protected override bool OnKeyDown(KeyDownEvent e) { if (e.Key == Key.Enter) @@ -550,6 +562,30 @@ namespace osu.Game.Screens.Ranking OnSelected?.Invoke(visibleItems.Single().Tag); } + private partial class GroupFlow : FillFlowContainer, IFilterable + { + public IEnumerable FilterTerms { get; } + + public bool MatchingFilter + { + set => Alpha = value ? 1 : 0; + } + + public bool FilteringActive { set { } } + + public GroupFlow(string? name) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Direction = FillDirection.Vertical; + Spacing = new Vector2(5); + + Add(new StatisticItemHeader { Text = name ?? "uncategorised" }); + + FilterTerms = name == null ? [] : [name]; + } + } + private partial class DrawableAddableTag : OsuAnimatedButton, IFilterable { public readonly UserTag Tag; @@ -560,7 +596,6 @@ namespace osu.Game.Screens.Ranking RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - Anchor = Origin = Anchor.Centre; } [BackgroundDependencyLoader] @@ -587,7 +622,7 @@ namespace osu.Game.Screens.Ranking { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Text = Tag.Name, + Text = Tag.DisplayName, }, new OsuTextFlowContainer(t => t.Font = OsuFont.Default.With(size: 14)) { @@ -600,7 +635,7 @@ namespace osu.Game.Screens.Ranking }); } - public IEnumerable FilterTerms => [Tag.Name, Tag.Description]; + public IEnumerable FilterTerms => [Tag.FullName, Tag.Description]; public bool MatchingFilter { set => Alpha = value ? 1 : 0; } public bool FilteringActive { set { } } From 8e562c1ded68c43e9e86ff21bd416818b2d2a782 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 31 Mar 2025 11:03:18 +0200 Subject: [PATCH 05/23] Adjust flow direction --- osu.Game/Screens/Ranking/UserTagControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/UserTagControl.cs b/osu.Game/Screens/Ranking/UserTagControl.cs index 95a72f2142..1bc0062c10 100644 --- a/osu.Game/Screens/Ranking/UserTagControl.cs +++ b/osu.Game/Screens/Ranking/UserTagControl.cs @@ -90,7 +90,7 @@ namespace osu.Game.Screens.Ranking { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Direction = FillDirection.Full, + Direction = FillDirection.Vertical, LayoutDuration = 300, LayoutEasing = Easing.OutQuint, Spacing = new Vector2(4), From a9ba61595933c20a647747ec7a0adca3bd7a92cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 31 Mar 2025 11:41:46 +0200 Subject: [PATCH 06/23] Always show all tags in full list & allow voting from both views --- osu.Game/Screens/Ranking/UserTagControl.cs | 225 ++++++++++++--------- 1 file changed, 124 insertions(+), 101 deletions(-) diff --git a/osu.Game/Screens/Ranking/UserTagControl.cs b/osu.Game/Screens/Ranking/UserTagControl.cs index 1bc0062c10..1bffbc699e 100644 --- a/osu.Game/Screens/Ranking/UserTagControl.cs +++ b/osu.Game/Screens/Ranking/UserTagControl.cs @@ -8,11 +8,14 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; +using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps; @@ -43,11 +46,14 @@ namespace osu.Game.Screens.Ranking private LoadingLayer loadingLayer = null!; private BindableList displayedTags { get; } = new BindableList(); - private BindableList extraTags { get; } = new BindableList(); - private Bindable allTags = null!; + private Bindable apiTags = null!; + private BindableDictionary allTagsById { get; } = new BindableDictionary(); + private readonly Bindable apiBeatmap = new Bindable(); + private APIRequest? requestInFlight; + [Resolved] private IAPIProvider api { get; set; } = null!; @@ -100,8 +106,8 @@ namespace osu.Game.Screens.Ranking new TagList { RelativeSizeAxes = Axes.Both, - AvailableTags = { BindTarget = extraTags }, - OnSelected = onExtraTagSelected, + AvailableTags = { BindTarget = allTagsById }, + OnSelected = toggleVote, } } } @@ -113,12 +119,12 @@ namespace osu.Game.Screens.Ranking }, }; - allTags = sessionStatics.GetBindable(Static.AllBeatmapTags); + apiTags = sessionStatics.GetBindable(Static.AllBeatmapTags); - if (allTags.Value == null) + if (apiTags.Value == null) { var listTagsRequest = new ListTagsRequest(); - listTagsRequest.Success += tags => allTags.Value = tags.Tags.ToArray(); + listTagsRequest.Success += tags => apiTags.Value = tags.Tags.ToArray(); api.Queue(listTagsRequest); } @@ -127,28 +133,11 @@ namespace osu.Game.Screens.Ranking api.Queue(getBeatmapSetRequest); } - private void onExtraTagSelected(UserTag tag) - { - loadingLayer.Show(); - extraTags.Remove(tag); - - var req = new AddBeatmapTagRequest(beatmapInfo.OnlineID, tag.Id); - req.Success += () => - { - tag.Voted.Value = true; - tag.VoteCount.Value += 1; - displayedTags.Add(tag); - loadingLayer.Hide(); - }; - req.Failure += _ => extraTags.Add(tag); - api.Queue(req); - } - protected override void LoadComplete() { base.LoadComplete(); - allTags.BindValueChanged(_ => updateTags()); + apiTags.BindValueChanged(_ => updateTags()); apiBeatmap.BindValueChanged(_ => updateTags()); updateTags(); @@ -157,25 +146,26 @@ namespace osu.Game.Screens.Ranking private void updateTags() { - if (allTags.Value == null || apiBeatmap.Value?.TopTags == null) + if (apiTags.Value == null || apiBeatmap.Value == null) return; - var allTagsById = allTags.Value.ToDictionary(t => t.Id); - var ownTagIds = apiBeatmap.Value.OwnTagIds?.ToHashSet() ?? new HashSet(); + allTagsById.Clear(); + allTagsById.AddRange(apiTags.Value.Select(t => new KeyValuePair(t.Id, new UserTag(t)))); - foreach (var topTag in apiBeatmap.Value.TopTags) + foreach (var topTag in apiBeatmap.Value.TopTags ?? []) { - if (allTagsById.Remove(topTag.TagId, out var tag)) + if (allTagsById.TryGetValue(topTag.TagId, out var tag)) { - displayedTags.Add(new UserTag(tag) - { - VoteCount = { Value = topTag.VoteCount }, - Voted = { Value = ownTagIds.Contains(tag.Id) } - }); + tag.VoteCount.Value = topTag.VoteCount; + displayedTags.Add(tag); } } - extraTags.AddRange(allTagsById.Select(t => new UserTag(t.Value))); + foreach (long ownTagId in apiBeatmap.Value.OwnTagIds ?? []) + { + if (allTagsById.TryGetValue(ownTagId, out var tag)) + tag.Voted.Value = true; + } loadingLayer.Hide(); } @@ -191,7 +181,7 @@ namespace osu.Game.Screens.Ranking for (int i = 0; i < e.NewItems!.Count; i++) { var tag = (UserTag)e.NewItems[i]!; - var drawableTag = new DrawableUserTag(tag); + var drawableTag = new DrawableUserTag(tag) { OnSelected = toggleVote }; tagFlow.Insert(tagFlow.Count, drawableTag); tag.VoteCount.BindValueChanged(voteCountChanged, true); layout.Invalidate(); @@ -220,15 +210,59 @@ namespace osu.Game.Screens.Ranking } } + private void toggleVote(UserTag tag) + { + if (requestInFlight != null) + return; + + loadingLayer.Show(); + + APIRequest request; + + switch (tag.Voted.Value) + { + case true: + var removeReq = new RemoveBeatmapTagRequest(beatmapInfo.OnlineID, tag.Id); + removeReq.Success += () => + { + tag.VoteCount.Value -= 1; + tag.Voted.Value = false; + }; + request = removeReq; + break; + + case false: + var addReq = new AddBeatmapTagRequest(beatmapInfo.OnlineID, tag.Id); + addReq.Success += () => + { + tag.VoteCount.Value += 1; + tag.Voted.Value = true; + if (!displayedTags.Contains(tag)) + displayedTags.Add(tag); + }; + request = addReq; + break; + } + + request.Success += () => + { + loadingLayer.Hide(); + requestInFlight = null; + }; + request.Failure += _ => + { + loadingLayer.Hide(); + requestInFlight = null; + }; + api.Queue(requestInFlight = request); + } + private void voteCountChanged(ValueChangedEvent _) { var tagsWithNoVotes = displayedTags.Where(t => t.VoteCount.Value == 0).ToArray(); foreach (var tag in tagsWithNoVotes) - { displayedTags.Remove(tag); - extraTags.Add(tag); - } layout.Invalidate(); } @@ -257,6 +291,8 @@ namespace osu.Game.Screens.Ranking { public readonly UserTag UserTag; + public Action? OnSelected { get; set; } + private readonly Bindable voteCount = new Bindable(); private readonly BindableBool voted = new BindableBool(); private readonly Bindable confirmed = new BindableBool(); @@ -266,19 +302,10 @@ namespace osu.Game.Screens.Ranking private OsuSpriteText tagCategoryText = null!; private OsuSpriteText tagNameText = null!; private OsuSpriteText voteCountText = null!; - private LoadingSpinner spinner = null!; [Resolved] private OsuColour colours { get; set; } = null!; - [Resolved] - private Bindable beatmap { get; set; } = null!; - - [Resolved] - private IAPIProvider api { get; set; } = null!; - - private APIRequest? requestInFlight; - public DrawableUserTag(UserTag userTag) { UserTag = userTag; @@ -360,11 +387,6 @@ namespace osu.Game.Screens.Ranking { Margin = new MarginPadding { Horizontal = 6, Vertical = 3, }, }, - spinner = new LoadingSpinner(withBox: true) - { - Alpha = 0, - Size = new Vector2(18), - } } } } @@ -417,50 +439,7 @@ namespace osu.Game.Screens.Ranking }, true); FinishTransforms(true); - Action = () => - { - if (requestInFlight != null) - return; - - spinner.Show(); - - APIRequest request; - - switch (voted.Value) - { - case true: - var removeReq = new RemoveBeatmapTagRequest(beatmap.Value.BeatmapInfo.OnlineID, UserTag.Id); - removeReq.Success += () => - { - voteCount.Value -= 1; - voted.Value = false; - }; - request = removeReq; - break; - - case false: - var addReq = new AddBeatmapTagRequest(beatmap.Value.BeatmapInfo.OnlineID, UserTag.Id); - addReq.Success += () => - { - voteCount.Value += 1; - voted.Value = true; - }; - request = addReq; - break; - } - - request.Success += () => - { - spinner.Hide(); - requestInFlight = null; - }; - request.Failure += _ => - { - spinner.Hide(); - requestInFlight = null; - }; - api.Queue(requestInFlight = request); - }; + Action = () => OnSelected?.Invoke(UserTag); } } @@ -469,7 +448,7 @@ namespace osu.Game.Screens.Ranking private SearchTextBox searchBox = null!; private SearchContainer searchContainer = null!; - public BindableList AvailableTags { get; } = new BindableList(); + public BindableDictionary AvailableTags { get; } = new BindableDictionary(); public Action? OnSelected { get; set; } @@ -501,12 +480,13 @@ namespace osu.Game.Screens.Ranking new OsuScrollContainer { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(10) { Top = 45, }, + Padding = new MarginPadding { Top = 40, }, ScrollbarOverlapsContent = false, Child = searchContainer = new SearchContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = 10, Bottom = 10 }, Direction = FillDirection.Vertical, Spacing = new Vector2(10), } @@ -523,7 +503,7 @@ namespace osu.Game.Screens.Ranking AvailableTags.BindCollectionChanged((_, _) => { searchContainer.Clear(); - searchContainer.ChildrenEnumerable = createItems(AvailableTags); + searchContainer.ChildrenEnumerable = createItems(AvailableTags.Values); }, true); searchBox.Current.BindValueChanged(_ => searchContainer.SearchTerm = searchBox.Current.Value, true); } @@ -590,6 +570,10 @@ namespace osu.Game.Screens.Ranking { public readonly UserTag Tag; + private Container votedIndicator = null!; + + private readonly Bindable voted = new Bindable(); + public DrawableAddableTag(UserTag tag) { Tag = tag; @@ -609,10 +593,36 @@ namespace osu.Game.Screens.Ranking Colour = colours.Gray6, Depth = float.MaxValue, }, + votedIndicator = new Container + { + RelativeSizeAxes = Axes.Both, + Width = 0.1f, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Depth = float.MaxValue, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = ColourInfo.GradientHorizontal(colours.Lime1.Opacity(0), colours.Lime1.Opacity(0.4f)), + }, + new SpriteIcon + { + Size = new Vector2(16), + Icon = FontAwesome.Solid.ThumbsUp, + Colour = colours.Lime1, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + Margin = new MarginPadding { Right = 5 }, + } + } + }, new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, + Width = 0.9f, Direction = FillDirection.Vertical, Spacing = new Vector2(2), Padding = new MarginPadding(5), @@ -633,6 +643,8 @@ namespace osu.Game.Screens.Ranking } } }); + + voted.BindTo(Tag.Voted); } public IEnumerable FilterTerms => [Tag.FullName, Tag.Description]; @@ -640,6 +652,17 @@ namespace osu.Game.Screens.Ranking public bool MatchingFilter { set => Alpha = value ? 1 : 0; } public bool FilteringActive { set { } } + protected override void LoadComplete() + { + base.LoadComplete(); + + voted.BindValueChanged(_ => + { + votedIndicator.FadeTo(voted.Value ? 1 : 0, 250, Easing.OutQuint); + }, true); + FinishTransforms(true); + } + protected override bool OnMouseDown(MouseDownEvent e) { bool result = base.OnMouseDown(e); From 9d75bb43a30984caf474063ca01b944024916e2c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 31 Mar 2025 11:50:46 +0200 Subject: [PATCH 07/23] Final sizing adjustments --- osu.Game/Screens/Ranking/UserTagControl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/UserTagControl.cs b/osu.Game/Screens/Ranking/UserTagControl.cs index 1bffbc699e..506b34fe85 100644 --- a/osu.Game/Screens/Ranking/UserTagControl.cs +++ b/osu.Game/Screens/Ranking/UserTagControl.cs @@ -76,7 +76,7 @@ namespace osu.Game.Screens.Ranking Padding = new MarginPadding(10), ColumnDimensions = [ - new Dimension(GridSizeMode.Absolute, 300), + new Dimension(GridSizeMode.Absolute, 350), new Dimension() ], RowDimensions = [new Dimension(GridSizeMode.AutoSize, minSize: 250)], @@ -614,7 +614,7 @@ namespace osu.Game.Screens.Ranking Colour = colours.Lime1, Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, - Margin = new MarginPadding { Right = 5 }, + Margin = new MarginPadding { Right = 10 }, } } }, From 8ff63e158c3e549573714b5677e32e85bbd9dd3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 31 Mar 2025 11:54:22 +0200 Subject: [PATCH 08/23] Async load panels to avoid update thread hitching --- osu.Game/Screens/Ranking/UserTagControl.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/UserTagControl.cs b/osu.Game/Screens/Ranking/UserTagControl.cs index 506b34fe85..cd5d2486fd 100644 --- a/osu.Game/Screens/Ranking/UserTagControl.cs +++ b/osu.Game/Screens/Ranking/UserTagControl.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; +using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; @@ -452,6 +453,8 @@ namespace osu.Game.Screens.Ranking public Action? OnSelected { get; set; } + private CancellationTokenSource? loadCancellationTokenSource; + [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -502,8 +505,14 @@ namespace osu.Game.Screens.Ranking AvailableTags.BindCollectionChanged((_, _) => { - searchContainer.Clear(); - searchContainer.ChildrenEnumerable = createItems(AvailableTags.Values); + loadCancellationTokenSource?.Cancel(); + loadCancellationTokenSource = new CancellationTokenSource(); + + LoadComponentsAsync(createItems(AvailableTags.Values), loaded => + { + searchContainer.Clear(); + searchContainer.AddRange(loaded); + }, loadCancellationTokenSource.Token); }, true); searchBox.Current.BindValueChanged(_ => searchContainer.SearchTerm = searchBox.Current.Value, true); } From 6cc48eb976de65d71ed2b4f89936adff7122ea3e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 31 Mar 2025 12:13:50 +0200 Subject: [PATCH 09/23] Fix enter-to-select-tag interaction --- osu.Game/Screens/Ranking/UserTagControl.cs | 22 ++++++++++++++++------ 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Ranking/UserTagControl.cs b/osu.Game/Screens/Ranking/UserTagControl.cs index cd5d2486fd..727e2b5f4a 100644 --- a/osu.Game/Screens/Ranking/UserTagControl.cs +++ b/osu.Game/Screens/Ranking/UserTagControl.cs @@ -17,8 +17,10 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Framework.Localisation; +using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Configuration; using osu.Game.Extensions; @@ -26,12 +28,12 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Input.Bindings; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; using osu.Game.Screens.Ranking.Statistics; using osuTK; -using osuTK.Input; namespace osu.Game.Screens.Ranking { @@ -288,6 +290,10 @@ namespace osu.Game.Screens.Ranking protected override bool OnClick(ClickEvent e) => true; + public void OnReleased(KeyBindingReleaseEvent e) + { + } + private partial class DrawableUserTag : OsuAnimatedButton { public readonly UserTag UserTag; @@ -444,7 +450,7 @@ namespace osu.Game.Screens.Ranking } } - private partial class TagList : CompositeDrawable + private partial class TagList : CompositeDrawable, IKeyBindingHandler { private SearchTextBox searchBox = null!; private SearchContainer searchContainer = null!; @@ -532,20 +538,24 @@ namespace osu.Game.Screens.Ranking } } - protected override bool OnKeyDown(KeyDownEvent e) + public bool OnPressed(KeyBindingPressEvent e) { - if (e.Key == Key.Enter) + if (e.Action == GlobalAction.Select && !e.Repeat) { attemptSelect(); return true; } - return base.OnKeyDown(e); + return false; + } + + public void OnReleased(KeyBindingReleaseEvent e) + { } private void attemptSelect() { - var visibleItems = searchContainer.OfType().Where(d => d.IsPresent).ToArray(); + var visibleItems = searchContainer.ChildrenOfType().Where(d => d.IsPresent).ToArray(); if (visibleItems.Length == 1) OnSelected?.Invoke(visibleItems.Single().Tag); From dcf35ff1042d1c912aef5c5d889b9de0acab198d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Apr 2025 13:47:10 +0200 Subject: [PATCH 10/23] Unify voted displays --- osu.Game/Screens/Ranking/UserTagControl.cs | 33 +++++++++++----------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/osu.Game/Screens/Ranking/UserTagControl.cs b/osu.Game/Screens/Ranking/UserTagControl.cs index 727e2b5f4a..da3059aaf4 100644 --- a/osu.Game/Screens/Ranking/UserTagControl.cs +++ b/osu.Game/Screens/Ranking/UserTagControl.cs @@ -9,10 +9,8 @@ using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; @@ -589,7 +587,8 @@ namespace osu.Game.Screens.Ranking { public readonly UserTag Tag; - private Container votedIndicator = null!; + private Box votedBackground = null!; + private SpriteIcon votedIcon = null!; private readonly Bindable voted = new Bindable(); @@ -601,8 +600,11 @@ namespace osu.Game.Screens.Ranking AutoSizeAxes = Axes.Y; } + [Resolved] + private OsuColour colours { get; set; } = null!; + [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { Content.AddRange(new Drawable[] { @@ -612,28 +614,25 @@ namespace osu.Game.Screens.Ranking Colour = colours.Gray6, Depth = float.MaxValue, }, - votedIndicator = new Container + new Container { - RelativeSizeAxes = Axes.Both, - Width = 0.1f, + RelativeSizeAxes = Axes.Y, + Width = 30, Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, Depth = float.MaxValue, Children = new Drawable[] { - new Box + votedBackground = new Box { RelativeSizeAxes = Axes.Both, - Colour = ColourInfo.GradientHorizontal(colours.Lime1.Opacity(0), colours.Lime1.Opacity(0.4f)), }, - new SpriteIcon + votedIcon = new SpriteIcon { Size = new Vector2(16), Icon = FontAwesome.Solid.ThumbsUp, - Colour = colours.Lime1, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - Margin = new MarginPadding { Right = 10 }, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, } } }, @@ -641,10 +640,9 @@ namespace osu.Game.Screens.Ranking { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Width = 0.9f, Direction = FillDirection.Vertical, Spacing = new Vector2(2), - Padding = new MarginPadding(5), + Padding = new MarginPadding(5) { Right = 35 }, Children = new Drawable[] { new OsuTextFlowContainer(t => t.Font = OsuFont.Default.With(weight: FontWeight.Bold)) @@ -677,7 +675,8 @@ namespace osu.Game.Screens.Ranking voted.BindValueChanged(_ => { - votedIndicator.FadeTo(voted.Value ? 1 : 0, 250, Easing.OutQuint); + votedBackground.FadeColour(voted.Value ? colours.Lime2 : colours.Gray2, 250, Easing.OutQuint); + votedIcon.FadeColour(voted.Value ? Colour4.Black : Colour4.White, 250, Easing.OutQuint); }, true); FinishTransforms(true); } From ffed666b97387ecdd8aaa78ceef32c27ea735c4e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Apr 2025 14:29:50 +0200 Subject: [PATCH 11/23] Compromise between popover and persistent view with a slideout --- osu.Game/Screens/Ranking/UserTagControl.cs | 117 +++++++++++++++------ 1 file changed, 85 insertions(+), 32 deletions(-) diff --git a/osu.Game/Screens/Ranking/UserTagControl.cs b/osu.Game/Screens/Ranking/UserTagControl.cs index da3059aaf4..d95238807a 100644 --- a/osu.Game/Screens/Ranking/UserTagControl.cs +++ b/osu.Game/Screens/Ranking/UserTagControl.cs @@ -77,10 +77,10 @@ namespace osu.Game.Screens.Ranking Padding = new MarginPadding(10), ColumnDimensions = [ - new Dimension(GridSizeMode.Absolute, 350), - new Dimension() + new Dimension(), + new Dimension(GridSizeMode.AutoSize) ], - RowDimensions = [new Dimension(GridSizeMode.AutoSize, minSize: 250)], + RowDimensions = [new Dimension(GridSizeMode.AutoSize, minSize: 40)], Content = new[] { new Drawable[] @@ -97,7 +97,7 @@ namespace osu.Game.Screens.Ranking { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, + Direction = FillDirection.Full, LayoutDuration = 300, LayoutEasing = Easing.OutQuint, Spacing = new Vector2(4), @@ -106,7 +106,6 @@ namespace osu.Game.Screens.Ranking }, new TagList { - RelativeSizeAxes = Axes.Both, AvailableTags = { BindTarget = allTagsById }, OnSelected = toggleVote, } @@ -436,7 +435,7 @@ namespace osu.Game.Screens.Ranking } else { - mainBackground.FadeColour(colours.Gray4, transition_duration, Easing.OutQuint); + mainBackground.FadeColour(colours.Gray6, transition_duration, Easing.OutQuint); tagCategoryText.FadeColour(Colour4.White, transition_duration, Easing.OutQuint); tagNameText.FadeColour(Colour4.White, transition_duration, Easing.OutQuint); FadeEdgeEffectTo(0f, transition_duration, Easing.OutQuint); @@ -452,6 +451,7 @@ namespace osu.Game.Screens.Ranking { private SearchTextBox searchBox = null!; private SearchContainer searchContainer = null!; + private Container content = null!; public BindableDictionary AvailableTags { get; } = new BindableDictionary(); @@ -459,47 +459,81 @@ namespace osu.Game.Screens.Ranking private CancellationTokenSource? loadCancellationTokenSource; + private readonly BindableBool expanded = new BindableBool(); + [BackgroundDependencyLoader] private void load(OsuColour colours) { - Masking = true; - CornerRadius = 5; - + Margin = new MarginPadding { Left = 30 }; InternalChildren = new Drawable[] { - new Box + new OsuClickableContainer { - RelativeSizeAxes = Axes.Both, - Alpha = 0.1f, + AutoSizeAxes = Axes.Both, + Anchor = Anchor.TopLeft, + Origin = Anchor.TopRight, + X = 10, + Masking = true, + CornerRadius = 5, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colours.Gray5, + }, + new SpriteIcon + { + Size = new Vector2(16), + Icon = FontAwesome.Solid.Plus, + Margin = new MarginPadding(10), + } + }, + Action = expanded.Toggle, }, new Container { RelativeSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 10, Children = new Drawable[] { - searchBox = new SearchTextBox - { - HoldFocus = true, - RelativeSizeAxes = Axes.X, - Depth = float.MinValue, - Y = -2, // hacky compensation for masking issues - }, - new OsuScrollContainer + new Box { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 40, }, - ScrollbarOverlapsContent = false, - Child = searchContainer = new SearchContainer + Colour = colours.Gray5, + }, + content = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding(10) { Top = 12 }, + Children = new Drawable[] { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Horizontal = 10, Bottom = 10 }, - Direction = FillDirection.Vertical, - Spacing = new Vector2(10), - } - } + searchBox = new SearchTextBox + { + HoldFocus = true, + RelativeSizeAxes = Axes.X, + Depth = float.MinValue, + Y = -2, // hacky compensation for masking issues + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 42, }, + ScrollbarOverlapsContent = false, + Child = searchContainer = new SearchContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Right = 5, Bottom = 10 }, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10), + } + } + }, + }, }, - } + }, }; } @@ -519,6 +553,25 @@ namespace osu.Game.Screens.Ranking }, loadCancellationTokenSource.Token); }, true); searchBox.Current.BindValueChanged(_ => searchContainer.SearchTerm = searchBox.Current.Value, true); + expanded.BindValueChanged(_ => + { + const float transition_duration = 250; + + if (expanded.Value) + { + this.ResizeWidthTo(400, transition_duration, Easing.OutQuint); + content.FadeIn(250, Easing.OutQuint); + RelativeSizeAxes = Axes.None; + this.ResizeHeightTo(300, transition_duration, Easing.OutQuint); + } + else + { + this.ResizeWidthTo(10, transition_duration, Easing.OutQuint); + content.FadeOut(250, Easing.OutQuint); + RelativeSizeAxes = Axes.Y; + this.ResizeHeightTo(1, transition_duration, Easing.OutQuint); + } + }, true); } private IEnumerable createItems(IEnumerable tags) @@ -611,7 +664,7 @@ namespace osu.Game.Screens.Ranking new Box { RelativeSizeAxes = Axes.Both, - Colour = colours.Gray6, + Colour = colours.Gray7, Depth = float.MaxValue, }, new Container From b614887e268b43e0a26254ff4604f234b8a81b2b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Apr 2025 14:30:30 +0200 Subject: [PATCH 12/23] Fix corner radii not matching --- osu.Game/Screens/Ranking/UserTagControl.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/UserTagControl.cs b/osu.Game/Screens/Ranking/UserTagControl.cs index d95238807a..ac30482687 100644 --- a/osu.Game/Screens/Ranking/UserTagControl.cs +++ b/osu.Game/Screens/Ranking/UserTagControl.cs @@ -324,7 +324,7 @@ namespace osu.Game.Screens.Ranking { Anchor = Anchor.CentreLeft; Origin = Anchor.CentreLeft; - CornerRadius = 8; + CornerRadius = 5; Masking = true; EdgeEffect = new EdgeEffectParameters { From 33c4c142b776f83817e6ed4b4e1a3b4b23242f28 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Apr 2025 14:34:24 +0200 Subject: [PATCH 13/23] Adjust font weights --- osu.Game/Screens/Ranking/UserTagControl.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/UserTagControl.cs b/osu.Game/Screens/Ranking/UserTagControl.cs index ac30482687..ab8443f69c 100644 --- a/osu.Game/Screens/Ranking/UserTagControl.cs +++ b/osu.Game/Screens/Ranking/UserTagControl.cs @@ -370,6 +370,7 @@ namespace osu.Game.Screens.Ranking tagNameText = new OsuSpriteText { Text = UserTag.DisplayName, + Font = OsuFont.Default.With(weight: FontWeight.SemiBold), Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Margin = new MarginPadding { Horizontal = 6 } @@ -698,7 +699,7 @@ namespace osu.Game.Screens.Ranking Padding = new MarginPadding(5) { Right = 35 }, Children = new Drawable[] { - new OsuTextFlowContainer(t => t.Font = OsuFont.Default.With(weight: FontWeight.Bold)) + new OsuTextFlowContainer(t => t.Font = OsuFont.Default.With(weight: FontWeight.SemiBold)) { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, From e4147f4f0b911458256ed048b46797ad5e62f9e3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 1 Apr 2025 14:35:28 +0200 Subject: [PATCH 14/23] Adjust glow --- osu.Game/Screens/Ranking/UserTagControl.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Ranking/UserTagControl.cs b/osu.Game/Screens/Ranking/UserTagControl.cs index ab8443f69c..289b7b3ecd 100644 --- a/osu.Game/Screens/Ranking/UserTagControl.cs +++ b/osu.Game/Screens/Ranking/UserTagControl.cs @@ -329,7 +329,7 @@ namespace osu.Game.Screens.Ranking EdgeEffect = new EdgeEffectParameters { Colour = colours.Lime1, - Radius = 5, + Radius = 6, Type = EdgeEffectType.Glow, }; Content.AddRange(new Drawable[] @@ -432,7 +432,7 @@ namespace osu.Game.Screens.Ranking mainBackground.FadeColour(colours.Lime2, transition_duration, Easing.OutQuint); tagCategoryText.FadeColour(Colour4.Black, transition_duration, Easing.OutQuint); tagNameText.FadeColour(Colour4.Black, transition_duration, Easing.OutQuint); - FadeEdgeEffectTo(0.5f, transition_duration, Easing.OutQuint); + FadeEdgeEffectTo(0.3f, transition_duration, Easing.OutQuint); } else { From 2a3241fd487e27bdd1153d2edff8ec76d6e3bae7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Apr 2025 19:19:06 +0900 Subject: [PATCH 15/23] Adjust mouse down animation in a better way (and apply to small tag buttons too) --- .../Graphics/UserInterface/OsuAnimatedButton.cs | 4 +++- osu.Game/Screens/Ranking/UserTagControl.cs | 13 ++++--------- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs index 0eec04541c..48d225de41 100644 --- a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs @@ -25,6 +25,8 @@ namespace osu.Game.Graphics.UserInterface private Color4 hoverColour = Color4.White.Opacity(0.1f); + protected float ScaleOnMouseDown { get; init; } = 0.75f; + /// /// The background colour of the while it is hovered. /// @@ -119,7 +121,7 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnMouseDown(MouseDownEvent e) { - Content.ScaleTo(0.75f, 2000, Easing.OutQuint); + Content.ScaleTo(ScaleOnMouseDown, 2000, Easing.OutQuint); return base.OnMouseDown(e); } diff --git a/osu.Game/Screens/Ranking/UserTagControl.cs b/osu.Game/Screens/Ranking/UserTagControl.cs index 7b36077bb3..84774e8ad8 100644 --- a/osu.Game/Screens/Ranking/UserTagControl.cs +++ b/osu.Game/Screens/Ranking/UserTagControl.cs @@ -319,6 +319,8 @@ namespace osu.Game.Screens.Ranking voted.BindTo(userTag.Voted); AutoSizeAxes = Axes.Both; + + ScaleOnMouseDown = 0.95f; } [BackgroundDependencyLoader] @@ -654,6 +656,8 @@ namespace osu.Game.Screens.Ranking RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; + + ScaleOnMouseDown = 0.95f; } [Resolved] @@ -736,15 +740,6 @@ namespace osu.Game.Screens.Ranking }, true); FinishTransforms(true); } - - protected override bool OnMouseDown(MouseDownEvent e) - { - bool result = base.OnMouseDown(e); - // slightly dodgy way of overriding the amount of scale-on-click (the default is way too much in this case) - ClearTransforms(targetMember: nameof(Scale)); - Content.ScaleTo(0.95f, 2000, Easing.OutQuint); - return result; - } } } } From 769e0bd4ffc68677f6e17601c77ebeab711a5cde Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Apr 2025 19:19:34 +0900 Subject: [PATCH 16/23] Remove flow animations They don't look great, so let's just get something out which doesn't involve things flying everywhere to start with. --- osu.Game/Screens/Ranking/UserTagControl.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Screens/Ranking/UserTagControl.cs b/osu.Game/Screens/Ranking/UserTagControl.cs index 84774e8ad8..bfc54e8423 100644 --- a/osu.Game/Screens/Ranking/UserTagControl.cs +++ b/osu.Game/Screens/Ranking/UserTagControl.cs @@ -98,8 +98,6 @@ namespace osu.Game.Screens.Ranking RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Full, - LayoutDuration = 300, - LayoutEasing = Easing.OutQuint, Spacing = new Vector2(4), }, }, @@ -326,8 +324,6 @@ namespace osu.Game.Screens.Ranking [BackgroundDependencyLoader] private void load() { - Anchor = Anchor.CentreLeft; - Origin = Anchor.CentreLeft; CornerRadius = 5; Masking = true; EdgeEffect = new EdgeEffectParameters From 43ee43c9e28c57f7f774c24d46fb3642ce045824 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 3 Apr 2025 20:36:02 +0900 Subject: [PATCH 17/23] Switch to using a popover again --- .../Visual/Ranking/TestSceneUserTagControl.cs | 29 +- osu.Game/Screens/Ranking/UserTagControl.cs | 311 +++++++++--------- 2 files changed, 174 insertions(+), 166 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneUserTagControl.cs b/osu.Game.Tests/Visual/Ranking/TestSceneUserTagControl.cs index 958eacfd56..c546c9727c 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneUserTagControl.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneUserTagControl.cs @@ -2,12 +2,15 @@ // See the LICENCE file in the repository root for full licence text. using System.Linq; +using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Cursor; using osu.Framework.Testing; using osu.Game.Beatmaps; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; using osu.Game.Rulesets.Osu; using osu.Game.Rulesets.Taiko; using osu.Game.Screens.Ranking; @@ -16,6 +19,9 @@ namespace osu.Game.Tests.Visual.Ranking { public partial class TestSceneUserTagControl : OsuTestScene { + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; [SetUpSteps] @@ -35,8 +41,15 @@ namespace osu.Game.Tests.Visual.Ranking [ new APITag { Id = 0, Name = "uncategorised tag", Description = "This probably isn't real but could be and should be handled.", }, new APITag { Id = 1, Name = "song representation/simple", Description = "Accessible and straightforward map design.", }, - new APITag { Id = 2, Name = "style/clean", Description = "Visually uncluttered and organised patterns, often involving few overlaps and equal visual spacing between objects.", }, - new APITag { Id = 3, Name = "aim/aim control", Description = "Patterns with velocity or direction changes which strongly go against a player's natural movement pattern.", }, + new APITag + { + Id = 2, Name = "style/clean", + Description = "Visually uncluttered and organised patterns, often involving few overlaps and equal visual spacing between objects.", + }, + new APITag + { + Id = 3, Name = "aim/aim control", Description = "Patterns with velocity or direction changes which strongly go against a player's natural movement pattern.", + }, new APITag { Id = 4, Name = "tap/bursts", Description = "Patterns requiring continuous movement and alternating, typically 9 notes or less.", }, new APITag { Id = 5, Name = "style/mono-heavy", Description = "Features monos used in large amounts.", RulesetId = 1, }, ] @@ -84,11 +97,15 @@ namespace osu.Game.Tests.Visual.Ranking private void recreateControl() { - Child = new UserTagControl(Beatmap.Value.BeatmapInfo) + Child = new PopoverContainer { - Width = 700, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Child = new UserTagControl(Beatmap.Value.BeatmapInfo) + { + Width = 700, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + } }; } } diff --git a/osu.Game/Screens/Ranking/UserTagControl.cs b/osu.Game/Screens/Ranking/UserTagControl.cs index bfc54e8423..5692c844ed 100644 --- a/osu.Game/Screens/Ranking/UserTagControl.cs +++ b/osu.Game/Screens/Ranking/UserTagControl.cs @@ -9,13 +9,15 @@ using System.Threading; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Caching; +using osu.Framework.Extensions; using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Effects; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Bindings; +using osu.Framework.Graphics.UserInterface; using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Framework.Testing; @@ -26,10 +28,12 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Graphics.UserInterfaceV2; using osu.Game.Input.Bindings; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; using osu.Game.Screens.Ranking.Statistics; using osuTK; @@ -55,6 +59,8 @@ namespace osu.Game.Screens.Ranking private APIRequest? requestInFlight; + private AddNewTagUserTag addNewTagUserTag = null!; + [Resolved] private IAPIProvider api { get; set; } = null!; @@ -99,14 +105,14 @@ namespace osu.Game.Screens.Ranking AutoSizeAxes = Axes.Y, Direction = FillDirection.Full, Spacing = new Vector2(4), + Child = addNewTagUserTag = new AddNewTagUserTag + { + AvailableTags = { BindTarget = relevantTagsById }, + OnTagSelected = toggleVote, + }, }, }, }, - new TagList - { - AvailableTags = { BindTarget = relevantTagsById }, - OnSelected = toggleVote, - } } } }, @@ -196,7 +202,7 @@ namespace osu.Game.Screens.Ranking { var tag = (UserTag)e.OldItems[i]!; tag.VoteCount.ValueChanged -= voteCountChanged; - tagFlow.Remove(oldItems[e.OldStartingIndex + i], true); + tagFlow.Remove(oldItems[1 + e.OldStartingIndex + i], true); } break; @@ -205,6 +211,7 @@ namespace osu.Game.Screens.Ranking case NotifyCollectionChangedAction.Reset: { tagFlow.Clear(); + tagFlow.Add(addNewTagUserTag); break; } } @@ -279,7 +286,12 @@ namespace osu.Game.Screens.Ranking .Select((tag, index) => new KeyValuePair(tag, index))); foreach (var drawableTag in tagFlow) - tagFlow.SetLayoutPosition(drawableTag, sortedTags[drawableTag.UserTag]); + { + if (drawableTag == addNewTagUserTag) + tagFlow.SetLayoutPosition(drawableTag, float.MinValue); + else + tagFlow.SetLayoutPosition(drawableTag, sortedTags[drawableTag.UserTag]); + } layout.Validate(); } @@ -287,10 +299,6 @@ namespace osu.Game.Screens.Ranking protected override bool OnClick(ClickEvent e) => true; - public void OnReleased(KeyBindingReleaseEvent e) - { - } - private partial class DrawableUserTag : OsuAnimatedButton { public readonly UserTag UserTag; @@ -301,18 +309,22 @@ namespace osu.Game.Screens.Ranking private readonly BindableBool voted = new BindableBool(); private readonly Bindable confirmed = new BindableBool(); - private Box mainBackground = null!; + protected Box MainBackground { get; private set; } = null!; private Box voteBackground = null!; - private OsuSpriteText tagCategoryText = null!; - private OsuSpriteText tagNameText = null!; - private OsuSpriteText voteCountText = null!; + + protected OsuSpriteText TagCategoryText { get; private set; } = null!; + protected OsuSpriteText TagNameText { get; private set; } = null!; + protected OsuSpriteText VoteCountText { get; private set; } = null!; + + private readonly bool showVoteCount; [Resolved] private OsuColour colours { get; set; } = null!; - public DrawableUserTag(UserTag userTag) + public DrawableUserTag(UserTag userTag, bool showVoteCount = true) { UserTag = userTag; + this.showVoteCount = showVoteCount; voteCount.BindTo(userTag.VoteCount); voted.BindTo(userTag.Voted); @@ -334,7 +346,7 @@ namespace osu.Game.Screens.Ranking }; Content.AddRange(new Drawable[] { - mainBackground = new Box + MainBackground = new Box { RelativeSizeAxes = Axes.Both, Depth = float.MaxValue, @@ -343,9 +355,9 @@ namespace osu.Game.Screens.Ranking { AutoSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - Children = new Drawable[] + Children = new[] { - tagCategoryText = new OsuSpriteText + TagCategoryText = new OsuSpriteText { Alpha = UserTag.GroupName != null ? 0.6f : 0, Text = UserTag.GroupName ?? default(LocalisableString), @@ -355,8 +367,7 @@ namespace osu.Game.Screens.Ranking }, new Container { - RelativeSizeAxes = Axes.Y, - AutoSizeAxes = Axes.X, + AutoSizeAxes = Axes.Both, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Children = new Drawable[] @@ -367,33 +378,35 @@ namespace osu.Game.Screens.Ranking Alpha = 0.1f, Blending = BlendingParameters.Additive, }, - tagNameText = new OsuSpriteText + TagNameText = new OsuSpriteText { Text = UserTag.DisplayName, Font = OsuFont.Default.With(weight: FontWeight.SemiBold), Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, - Margin = new MarginPadding { Horizontal = 6 } - }, - } - }, - new Container - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Children = new Drawable[] - { - voteBackground = new Box - { - RelativeSizeAxes = Axes.Both, - }, - voteCountText = new OsuSpriteText - { Margin = new MarginPadding { Horizontal = 6, Vertical = 3, }, }, } - } + }, + showVoteCount + ? new Container + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Children = new Drawable[] + { + voteBackground = new Box + { + RelativeSizeAxes = Axes.Both, + }, + VoteCountText = new OsuSpriteText + { + Margin = new MarginPadding { Horizontal = 6, Vertical = 3, }, + }, + } + } + : Empty(), } } }); @@ -407,52 +420,90 @@ namespace osu.Game.Screens.Ranking const double transition_duration = 300; - voteCount.BindValueChanged(_ => + if (showVoteCount) { - voteCountText.Text = voteCount.Value.ToLocalisableString(); - confirmed.Value = voteCount.Value >= 10; - }, true); - voted.BindValueChanged(v => - { - if (v.NewValue) + voteCount.BindValueChanged(_ => { - voteBackground.FadeColour(colours.Lime2, transition_duration, Easing.OutQuint); - voteCountText.FadeColour(Colour4.Black, transition_duration, Easing.OutQuint); - } - else + VoteCountText.Text = voteCount.Value.ToLocalisableString(); + confirmed.Value = voteCount.Value >= 10; + }, true); + voted.BindValueChanged(v => { - voteBackground.FadeColour(colours.Gray2, transition_duration, Easing.OutQuint); - voteCountText.FadeColour(Colour4.White, transition_duration, Easing.OutQuint); - } - }, true); - confirmed.BindValueChanged(c => - { - if (c.NewValue) + if (v.NewValue) + { + voteBackground.FadeColour(colours.Lime2, transition_duration, Easing.OutQuint); + VoteCountText.FadeColour(Colour4.Black, transition_duration, Easing.OutQuint); + } + else + { + voteBackground.FadeColour(colours.Gray2, transition_duration, Easing.OutQuint); + VoteCountText.FadeColour(Colour4.White, transition_duration, Easing.OutQuint); + } + }, true); + + confirmed.BindValueChanged(c => { - mainBackground.FadeColour(colours.Lime2, transition_duration, Easing.OutQuint); - tagCategoryText.FadeColour(Colour4.Black, transition_duration, Easing.OutQuint); - tagNameText.FadeColour(Colour4.Black, transition_duration, Easing.OutQuint); - FadeEdgeEffectTo(0.3f, transition_duration, Easing.OutQuint); - } - else - { - mainBackground.FadeColour(colours.Gray6, transition_duration, Easing.OutQuint); - tagCategoryText.FadeColour(Colour4.White, transition_duration, Easing.OutQuint); - tagNameText.FadeColour(Colour4.White, transition_duration, Easing.OutQuint); - FadeEdgeEffectTo(0f, transition_duration, Easing.OutQuint); - } - }, true); + if (c.NewValue) + { + MainBackground.FadeColour(colours.Lime2, transition_duration, Easing.OutQuint); + TagCategoryText.FadeColour(Colour4.Black, transition_duration, Easing.OutQuint); + TagNameText.FadeColour(Colour4.Black, transition_duration, Easing.OutQuint); + FadeEdgeEffectTo(0.3f, transition_duration, Easing.OutQuint); + } + else + { + MainBackground.FadeColour(colours.Gray6, transition_duration, Easing.OutQuint); + TagCategoryText.FadeColour(Colour4.White, transition_duration, Easing.OutQuint); + TagNameText.FadeColour(Colour4.White, transition_duration, Easing.OutQuint); + FadeEdgeEffectTo(0f, transition_duration, Easing.OutQuint); + } + }, true); + } + FinishTransforms(true); Action = () => OnSelected?.Invoke(UserTag); } } - private partial class TagList : CompositeDrawable, IKeyBindingHandler + private partial class AddNewTagUserTag : DrawableUserTag, IHasPopover + { + public BindableDictionary AvailableTags { get; } = new BindableDictionary(); + + public Action? OnTagSelected { get; set; } + + [Resolved] + private OverlayColourProvider overlayColourProvider { get; set; } = null!; + + public AddNewTagUserTag() + : base(new UserTag(new APITag { Name = "+/add" }), false) + { + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + AvailableTags.BindCollectionChanged((_, _) => Enabled.Value = AvailableTags.Count > 0, true); + Action = this.ShowPopover; + + MainBackground.FadeColour(overlayColourProvider.Background2); + TagCategoryText.FadeColour(overlayColourProvider.Colour0); + TagNameText.FadeColour(overlayColourProvider.Colour0); + FadeEdgeEffectTo(0); + } + + public Popover GetPopover() => new AddTagsPopover + { + AvailableTags = { BindTarget = AvailableTags }, + OnSelected = OnTagSelected, + }; + } + + private partial class AddTagsPopover : OsuPopover { private SearchTextBox searchBox = null!; private SearchContainer searchContainer = null!; - private Container content = null!; public BindableDictionary AvailableTags { get; } = new BindableDictionary(); @@ -460,79 +511,42 @@ namespace osu.Game.Screens.Ranking private CancellationTokenSource? loadCancellationTokenSource; - private readonly BindableBool expanded = new BindableBool(); - [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { - Margin = new MarginPadding { Left = 30 }; - InternalChildren = new Drawable[] + AllowableAnchors = new[] + { + Anchor.TopCentre, + }; + + Children = new Drawable[] { - new OsuClickableContainer - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.TopLeft, - Origin = Anchor.TopRight, - X = 10, - Masking = true, - CornerRadius = 5, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = colours.Gray5, - }, - new SpriteIcon - { - Size = new Vector2(16), - Icon = FontAwesome.Solid.Plus, - Margin = new MarginPadding(10), - } - }, - Action = expanded.Toggle, - }, new Container { - RelativeSizeAxes = Axes.Both, - Masking = true, - CornerRadius = 10, + Size = new Vector2(400, 300), Children = new Drawable[] { - new Box + searchBox = new SearchTextBox { - RelativeSizeAxes = Axes.Both, - Colour = colours.Gray5, + HoldFocus = true, + RelativeSizeAxes = Axes.X, + Depth = float.MinValue, }, - content = new Container + new OsuScrollContainer { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding(10) { Top = 12 }, - Children = new Drawable[] + RelativeSizeAxes = Axes.X, + Y = 40, + Height = 260, + ScrollbarOverlapsContent = false, + Child = searchContainer = new SearchContainer { - searchBox = new SearchTextBox - { - HoldFocus = true, - RelativeSizeAxes = Axes.X, - Depth = float.MinValue, - Y = -2, // hacky compensation for masking issues - }, - new OsuScrollContainer - { - RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = 42, }, - ScrollbarOverlapsContent = false, - Child = searchContainer = new SearchContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Right = 5, Bottom = 10 }, - Direction = FillDirection.Vertical, - Spacing = new Vector2(10), - } - } - }, - }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Right = 5, Bottom = 10 }, + Direction = FillDirection.Vertical, + Spacing = new Vector2(10), + } + } }, }, }; @@ -554,25 +568,6 @@ namespace osu.Game.Screens.Ranking }, loadCancellationTokenSource.Token); }, true); searchBox.Current.BindValueChanged(_ => searchContainer.SearchTerm = searchBox.Current.Value, true); - expanded.BindValueChanged(_ => - { - const float transition_duration = 250; - - if (expanded.Value) - { - this.ResizeWidthTo(400, transition_duration, Easing.OutQuint); - content.FadeIn(250, Easing.OutQuint); - RelativeSizeAxes = Axes.None; - this.ResizeHeightTo(300, transition_duration, Easing.OutQuint); - } - else - { - this.ResizeWidthTo(10, transition_duration, Easing.OutQuint); - content.FadeOut(250, Easing.OutQuint); - RelativeSizeAxes = Axes.Y; - this.ResizeHeightTo(1, transition_duration, Easing.OutQuint); - } - }, true); } private IEnumerable createItems(IEnumerable tags) @@ -590,7 +585,7 @@ namespace osu.Game.Screens.Ranking } } - public bool OnPressed(KeyBindingPressEvent e) + public override bool OnPressed(KeyBindingPressEvent e) { if (e.Action == GlobalAction.Select && !e.Repeat) { @@ -601,10 +596,6 @@ namespace osu.Game.Screens.Ranking return false; } - public void OnReleased(KeyBindingReleaseEvent e) - { - } - private void attemptSelect() { var visibleItems = searchContainer.ChildrenOfType().Where(d => d.IsPresent).ToArray(); From f8d063a39424cebd9f63bd4742cf3f6c61d74b0a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Apr 2025 20:00:36 +0900 Subject: [PATCH 18/23] Move loading inline to individual tags --- osu.Game/Screens/Ranking/UserTag.cs | 1 + osu.Game/Screens/Ranking/UserTagControl.cs | 40 ++++++++++++++-------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/osu.Game/Screens/Ranking/UserTag.cs b/osu.Game/Screens/Ranking/UserTag.cs index 983f585931..9a93df91b5 100644 --- a/osu.Game/Screens/Ranking/UserTag.cs +++ b/osu.Game/Screens/Ranking/UserTag.cs @@ -16,6 +16,7 @@ namespace osu.Game.Screens.Ranking public BindableInt VoteCount { get; } = new BindableInt(); public BindableBool Voted { get; } = new BindableBool(); + public BindableBool Updating { get; } = new BindableBool(); public UserTag(APITag tag) { diff --git a/osu.Game/Screens/Ranking/UserTagControl.cs b/osu.Game/Screens/Ranking/UserTagControl.cs index 5692c844ed..e88ffd507e 100644 --- a/osu.Game/Screens/Ranking/UserTagControl.cs +++ b/osu.Game/Screens/Ranking/UserTagControl.cs @@ -48,7 +48,6 @@ namespace osu.Game.Screens.Ranking private readonly Cached layout = new Cached(); private FillFlowContainer tagFlow = null!; - private LoadingLayer loadingLayer = null!; private BindableList displayedTags { get; } = new BindableList(); @@ -116,11 +115,6 @@ namespace osu.Game.Screens.Ranking } } }, - loadingLayer = new LoadingLayer(dimBackground: true) - { - RelativeSizeAxes = Axes.Both, - State = { Value = Visibility.Visible } - }, }; apiTags = sessionStatics.GetBindable(Static.AllBeatmapTags); @@ -163,6 +157,7 @@ namespace osu.Game.Screens.Ranking if (relevantTagsById.TryGetValue(topTag.TagId, out var tag)) { tag.VoteCount.Value = topTag.VoteCount; + tag.Updating.Value = false; displayedTags.Add(tag); } } @@ -170,10 +165,11 @@ namespace osu.Game.Screens.Ranking foreach (long ownTagId in apiBeatmap.Value.OwnTagIds ?? []) { if (relevantTagsById.TryGetValue(ownTagId, out var tag)) + { tag.Voted.Value = true; + tag.Updating.Value = false; + } } - - loadingLayer.Hide(); } private void displayTags(object? sender, NotifyCollectionChangedEventArgs e) @@ -222,7 +218,7 @@ namespace osu.Game.Screens.Ranking if (requestInFlight != null) return; - loadingLayer.Show(); + tag.Updating.Value = true; APIRequest request; @@ -253,12 +249,12 @@ namespace osu.Game.Screens.Ranking request.Success += () => { - loadingLayer.Hide(); + tag.Updating.Value = false; requestInFlight = null; }; request.Failure += _ => { - loadingLayer.Hide(); + tag.Updating.Value = false; requestInFlight = null; }; api.Queue(requestInFlight = request); @@ -308,6 +304,7 @@ namespace osu.Game.Screens.Ranking private readonly Bindable voteCount = new Bindable(); private readonly BindableBool voted = new BindableBool(); private readonly Bindable confirmed = new BindableBool(); + private readonly BindableBool updating = new BindableBool(); protected Box MainBackground { get; private set; } = null!; private Box voteBackground = null!; @@ -318,6 +315,8 @@ namespace osu.Game.Screens.Ranking private readonly bool showVoteCount; + private LoadingLayer loadingLayer = null!; + [Resolved] private OsuColour colours { get; set; } = null!; @@ -326,6 +325,7 @@ namespace osu.Game.Screens.Ranking UserTag = userTag; this.showVoteCount = showVoteCount; voteCount.BindTo(userTag.VoteCount); + updating.BindTo(userTag.Updating); voted.BindTo(userTag.Voted); AutoSizeAxes = Axes.Both; @@ -408,7 +408,8 @@ namespace osu.Game.Screens.Ranking } : Empty(), } - } + }, + loadingLayer = new LoadingLayer(dimBackground: true), }); TooltipText = UserTag.Description; @@ -420,6 +421,8 @@ namespace osu.Game.Screens.Ranking const double transition_duration = 300; + updating.BindValueChanged(u => loadingLayer.State.Value = u.NewValue ? Visibility.Visible : Visibility.Hidden); + if (showVoteCount) { voteCount.BindValueChanged(_ => @@ -636,6 +639,9 @@ namespace osu.Game.Screens.Ranking private SpriteIcon votedIcon = null!; private readonly Bindable voted = new Bindable(); + private readonly BindableBool updating = new BindableBool(); + + private LoadingLayer loadingLayer = null!; public DrawableAddableTag(UserTag tag) { @@ -645,6 +651,9 @@ namespace osu.Game.Screens.Ranking AutoSizeAxes = Axes.Y; ScaleOnMouseDown = 0.95f; + + voted.BindTo(Tag.Voted); + updating.BindTo(Tag.Updating); } [Resolved] @@ -705,10 +714,9 @@ namespace osu.Game.Screens.Ranking Text = Tag.Description, } } - } + }, + loadingLayer = new LoadingLayer(dimBackground: true), }); - - voted.BindTo(Tag.Voted); } public IEnumerable FilterTerms => [Tag.FullName, Tag.Description]; @@ -726,6 +734,8 @@ namespace osu.Game.Screens.Ranking votedIcon.FadeColour(voted.Value ? Colour4.Black : Colour4.White, 250, Easing.OutQuint); }, true); FinishTransforms(true); + + updating.BindValueChanged(u => loadingLayer.State.Value = u.NewValue ? Visibility.Visible : Visibility.Hidden); } } } From b874eea0c5482d5aa421abd3296115a6b6f9582e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 7 Apr 2025 20:28:45 +0900 Subject: [PATCH 19/23] Allow multiple in-flight requests --- osu.Game/Screens/Ranking/UserTagControl.cs | 19 +++++-------------- 1 file changed, 5 insertions(+), 14 deletions(-) diff --git a/osu.Game/Screens/Ranking/UserTagControl.cs b/osu.Game/Screens/Ranking/UserTagControl.cs index e88ffd507e..797d66b5c5 100644 --- a/osu.Game/Screens/Ranking/UserTagControl.cs +++ b/osu.Game/Screens/Ranking/UserTagControl.cs @@ -56,8 +56,6 @@ namespace osu.Game.Screens.Ranking private readonly Bindable apiBeatmap = new Bindable(); - private APIRequest? requestInFlight; - private AddNewTagUserTag addNewTagUserTag = null!; [Resolved] @@ -215,7 +213,7 @@ namespace osu.Game.Screens.Ranking private void toggleVote(UserTag tag) { - if (requestInFlight != null) + if (tag.Updating.Value) return; tag.Updating.Value = true; @@ -247,17 +245,10 @@ namespace osu.Game.Screens.Ranking break; } - request.Success += () => - { - tag.Updating.Value = false; - requestInFlight = null; - }; - request.Failure += _ => - { - tag.Updating.Value = false; - requestInFlight = null; - }; - api.Queue(requestInFlight = request); + request.Success += () => tag.Updating.Value = false; + request.Failure += _ => tag.Updating.Value = false; + + api.Queue(request); } private void voteCountChanged(ValueChangedEvent _) From 23c711d1bc54ac660a94cc0491568f6a969c02d6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 7 Apr 2025 14:39:37 +0200 Subject: [PATCH 20/23] Cache aquamarine colour provider at results screen To fix the user tag control. I would have done it locally to the user tag control, but it was pissing me off because I wanted the add button to be aquamarine (as it's closer to the accents the screen is already using), but the popover on open was for whatever reason purple and I just want consistency where possible. --- osu.Game/Screens/Ranking/ResultsScreen.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Ranking/ResultsScreen.cs b/osu.Game/Screens/Ranking/ResultsScreen.cs index 6da731588f..8d5e6c05c3 100644 --- a/osu.Game/Screens/Ranking/ResultsScreen.cs +++ b/osu.Game/Screens/Ranking/ResultsScreen.cs @@ -86,6 +86,9 @@ namespace osu.Game.Screens.Ranking private Sample? popInSample; + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + protected ResultsScreen(ScoreInfo? score) { Score = score; From cded1311c8a70ddf9381c92677d08f352fb5c65c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 7 Apr 2025 14:45:38 +0200 Subject: [PATCH 21/23] Allow tag control popover to attach to bottom too Looks bad when trying to tag on a replay otherwise. --- osu.Game/Screens/Ranking/UserTagControl.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Ranking/UserTagControl.cs b/osu.Game/Screens/Ranking/UserTagControl.cs index 797d66b5c5..789e2cce9f 100644 --- a/osu.Game/Screens/Ranking/UserTagControl.cs +++ b/osu.Game/Screens/Ranking/UserTagControl.cs @@ -511,6 +511,7 @@ namespace osu.Game.Screens.Ranking AllowableAnchors = new[] { Anchor.TopCentre, + Anchor.BottomCentre, }; Children = new Drawable[] From 11e48f9f8e805ffbaffeac41fded9cf29f5721d5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 7 Apr 2025 14:48:26 +0200 Subject: [PATCH 22/23] Close popovers on hiding the statistics view Now that the tagging popover is back, https://github.com/ppy/osu/issues/32630 actually needs fixing. --- osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs index ad868e58f0..c33514e343 100644 --- a/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs +++ b/osu.Game/Screens/Ranking/Statistics/StatisticsPanel.cs @@ -304,7 +304,10 @@ namespace osu.Game.Screens.Ranking.Statistics this.FadeOut(250, Easing.OutQuint); if (wasOpened) + { popOutSample?.Play(); + this.HidePopover(); // targeted at the user tag control + } } protected override void Dispose(bool isDisposing) From 9643beafa7cae1ad041844f3fcd6a590a075d3a4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 7 Apr 2025 14:49:55 +0200 Subject: [PATCH 23/23] Fix crashes in statistics panel test scene --- .../Visual/Ranking/TestSceneStatisticsPanel.cs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs index ea80f2c5b2..f92dc0313e 100644 --- a/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs +++ b/osu.Game.Tests/Visual/Ranking/TestSceneStatisticsPanel.cs @@ -26,6 +26,7 @@ using osu.Game.Online; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; using osu.Game.Rulesets; using osu.Game.Rulesets.Difficulty; using osu.Game.Rulesets.Mania; @@ -52,6 +53,9 @@ namespace osu.Game.Tests.Visual.Ranking private RulesetStore rulesetStore = null!; private BeatmapManager beatmapManager = null!; + [Cached] + private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine); + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) { var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); @@ -364,12 +368,16 @@ namespace osu.Game.Tests.Visual.Ranking private void loadPanel(ScoreInfo score) => AddStep("load panel", () => { - Child = new StatisticsPanel + Child = new PopoverContainer { RelativeSizeAxes = Axes.Both, - State = { Value = Visibility.Visible }, - Score = { Value = score }, - AchievedScore = score, + Child = new StatisticsPanel + { + RelativeSizeAxes = Axes.Both, + State = { Value = Visibility.Visible }, + Score = { Value = score }, + AchievedScore = score, + }, }; });