Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement new components for song select wedge redesign #28063

Closed
wants to merge 39 commits into from

Conversation

Joehuu
Copy link
Member

@Joehuu Joehuu commented May 2, 2024

Where's the individual component PRs?

Considered PRing individual components (and replacing old ones) more effort because:

  • you would have to remove the old component with the new one that's potentially in a completely different spot
  • new and old wedge components have to coexist short-term (song select wedge will be wonky until everything is completed, and can't promise everything will be done before next release)
  • probably conflicts along the way if I were to multiple component PRs all at once, and probably slow if I leave at most 1 open at a time

My original proposal for the process was just implementing the component and not replacing the old one, but also may be harder to review as it'll just be a single component (content) in a test with no context.

The goal of this PR is preparation for just replacing the old wedge. It does the bare minimum: no skinning (e.g. collapsing), no new density graph, old details tab w/ metadata and online stuff is still a thing (newest design iteration may not have it displayed by default). Also had beatmap info overlay / online in mind when I did this long before, so there are APIBeatmap cases with some testing.

Code follows the Content terminology like results screen components when the component contains text and has no background. The LOC is probably daunting, but If you don't count the tests / header and usings, it is probably under 1000 with some reusing of old components' code.

The interim design is following the very first figma iteration for the difficulty name / length and bpm content, and for object count / difficulty settings content, it follows the current layout but has three columns because the left side expanded. The header is not implemented as I believe that takes too much height room (it's still in multi though). Also ignores the latest figma design linked above that removes bars entirely.

The old-to-new wedge replacement diff is below. Didn't apply it for now to ease LOC.

There are some TODOs, and I wrote this after midnight, so opening as draft for general feedback for now:

TODOs (some todos in code not listed):
  • Right of wedge looks wrong at first song select start (was an issue before this PR)
    Screenshot 2024-05-02 at 12 32 49 AM
  • object count bars' max values are hardcoded to 1000 (should be hardest difficulty iirc)
Before After
osu_2024-05-02_00-06-19 osu_2024-05-02_00-07-21
diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs
index 20dd7c55d8..e95fa4f40d 100644
--- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs
+++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs
@@ -200,6 +200,8 @@ protected override void LoadComplete()
             }, true);
 
             beatmap.BindValueChanged(_ => updateDisplay(), true);
+
+            Show();
         }
 
         private const double animation_duration = 600;
diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs
index 73c122dda6..6416cdcaf5 100644
--- a/osu.Game/Screens/Select/FilterControl.cs
+++ b/osu.Game/Screens/Select/FilterControl.cs
@@ -10,7 +10,9 @@
 using JetBrains.Annotations;
 using osu.Framework.Allocation;
 using osu.Framework.Bindables;
+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.Input;
@@ -97,8 +99,7 @@ private void load(OsuColour colours, OsuConfigManager config)
             {
                 new Box
                 {
-                    Colour = Color4.Black,
-                    Alpha = 0.8f,
+                    Colour = ColourInfo.GradientVertical(Color4.Black, Color4.Black.Opacity(0)),
                     Width = 2,
                     RelativeSizeAxes = Axes.Both,
                 },
@@ -106,7 +107,7 @@ private void load(OsuColour colours, OsuConfigManager config)
                 {
                     Padding = new MarginPadding(side_margin),
                     RelativeSizeAxes = Axes.Both,
-                    Width = 0.5f,
+                    Width = 0.4f,
                     Anchor = Anchor.TopRight,
                     Origin = Anchor.TopRight,
                     // Reverse ChildID so that dropdowns in the top section appear on top of the bottom section.
diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs
index 52f49ba56a..aec6aeee82 100644
--- a/osu.Game/Screens/Select/PlaySongSelect.cs
+++ b/osu.Game/Screens/Select/PlaySongSelect.cs
@@ -28,6 +28,9 @@ namespace osu.Game.Screens.Select
 {
     public partial class PlaySongSelect : SongSelect
     {
+        [Cached]
+        private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
+
         private OsuScreen? playerLoader;
 
         [Resolved]
diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs
index 2580f4fd7a..de3f774eed 100644
--- a/osu.Game/Screens/Select/SongSelect.cs
+++ b/osu.Game/Screens/Select/SongSelect.cs
@@ -13,7 +13,6 @@
 using osu.Framework.Extensions.ObjectExtensions;
 using osu.Framework.Graphics;
 using osu.Framework.Graphics.Containers;
-using osu.Framework.Graphics.Shapes;
 using osu.Framework.Graphics.Sprites;
 using osu.Framework.Graphics.UserInterface;
 using osu.Framework.Input.Bindings;
@@ -36,7 +35,6 @@
 using osu.Game.Screens.Edit;
 using osu.Game.Screens.Menu;
 using osu.Game.Screens.Play;
-using osu.Game.Screens.Select.Details;
 using osu.Game.Screens.Select.Options;
 using osu.Game.Skinning;
 using osuTK;
@@ -108,9 +106,7 @@ public abstract partial class SongSelect : ScreenWithBeatmapBackground, IKeyBind
 
         private ParallaxContainer wedgeBackground = null!;
 
-        protected Container LeftArea { get; private set; } = null!;
-
-        private BeatmapInfoWedge beatmapInfoWedge = null!;
+        protected GridContainer LeftArea { get; private set; } = null!;
 
         [Resolved]
         private IDialogOverlay? dialogOverlay { get; set; }
@@ -137,8 +133,6 @@ public abstract partial class SongSelect : ScreenWithBeatmapBackground, IKeyBind
 
         private IDisposable? modSelectOverlayRegistration;
 
-        private AdvancedStats advancedStats = null!;
-
         [Resolved]
         private MusicController music { get; set; } = null!;
 
@@ -147,6 +141,12 @@ public abstract partial class SongSelect : ScreenWithBeatmapBackground, IKeyBind
 
         private Bindable<bool> configBackgroundBlur = null!;
 
+        [Cached(typeof(IBindable<IBeatmapInfo?>))]
+        private readonly Bindable<IBeatmapInfo?> beatmapInfo = new Bindable<IBeatmapInfo?>();
+
+        [Cached(typeof(IBindable<IBeatmapSetInfo?>))]
+        private readonly Bindable<IBeatmapSetInfo?> beatmapSetInfo = new Bindable<IBeatmapSetInfo?>();
+
         [BackgroundDependencyLoader(true)]
         private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog? manageCollectionsDialog, DifficultyRecommender? recommender, OsuConfigManager config)
         {
@@ -188,7 +188,7 @@ private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog
                             ColumnDimensions = new[]
                             {
                                 new Dimension(),
-                                new Dimension(GridSizeMode.Relative, 0.5f, maxSize: 850),
+                                new Dimension(GridSizeMode.Relative, 0.4f, maxSize: 650),
                             },
                             Content = new[]
                             {
@@ -231,86 +231,54 @@ private void load(AudioManager audio, OsuColour colours, ManageCollectionsDialog
                             RelativeSizeAxes = Axes.Both,
                             ColumnDimensions = new[]
                             {
-                                new Dimension(GridSizeMode.Relative, 0.5f, maxSize: 650),
+                                new Dimension(GridSizeMode.Relative, 0.6f, maxSize: 850),
                             },
                             Content = new[]
                             {
                                 new Drawable[]
                                 {
-                                    LeftArea = new Container
+                                    LeftArea = new LeftSideInteractionContainer(() => Carousel.ScrollToSelected())
                                     {
                                         Origin = Anchor.BottomLeft,
                                         Anchor = Anchor.BottomLeft,
                                         RelativeSizeAxes = Axes.Both,
-                                        Padding = new MarginPadding { Top = 5 },
-                                        Children = new Drawable[]
+                                        RowDimensions = new[]
                                         {
-                                            new LeftSideInteractionContainer(() => Carousel.ScrollToSelected())
-                                            {
-                                                RelativeSizeAxes = Axes.Both,
-                                            },
-                                            beatmapInfoWedge = new BeatmapInfoWedge
+                                            new Dimension(GridSizeMode.AutoSize),
+                                        },
+                                        Content = new[]
+                                        {
+                                            new Drawable[]
                                             {
-                                                Height = WEDGE_HEIGHT,
-                                                RelativeSizeAxes = Axes.X,
-                                                Margin = new MarginPadding
+                                                new Container
                                                 {
-                                                    Right = left_area_padding,
-                                                    Left = -BeatmapInfoWedge.BORDER_THICKNESS, // Hide the left border
+                                                    RelativeSizeAxes = Axes.X,
+                                                    AutoSizeAxes = Axes.Y,
+                                                    Padding = new MarginPadding { Top = 10 },
+                                                    Child = new BeatmapInfoWedgeV2
+                                                    {
+                                                        RelativeSizeAxes = Axes.X,
+                                                    },
                                                 },
                                             },
-                                            new Container
+                                            new Drawable[]
                                             {
-                                                RelativeSizeAxes = Axes.X,
-                                                Height = 90,
-                                                Padding = new MarginPadding(10)
-                                                {
-                                                    Left = left_area_padding,
-                                                    Right = left_area_padding * 2 + 5,
-                                                },
-                                                Y = WEDGE_HEIGHT,
-                                                Children = new Drawable[]
+                                                new Container
                                                 {
-                                                    new Container
+                                                    RelativeSizeAxes = Axes.Both,
+                                                    Padding = new MarginPadding
                                                     {
-                                                        RelativeSizeAxes = Axes.Both,
-                                                        Masking = true,
-                                                        CornerRadius = 10,
-                                                        Children = new Drawable[]
-                                                        {
-                                                            new Box
-                                                            {
-                                                                RelativeSizeAxes = Axes.Both,
-                                                                Colour = Colour4.Black.Opacity(0.3f),
-                                                            },
-                                                            advancedStats = new AdvancedStats(2)
-                                                            {
-                                                                RelativeSizeAxes = Axes.X,
-                                                                AutoSizeAxes = Axes.Y,
-                                                                Anchor = Anchor.Centre,
-                                                                Origin = Anchor.Centre,
-                                                                Padding = new MarginPadding(10),
-                                                            },
-                                                        }
+                                                        Bottom = Footer.HEIGHT,
+                                                        Left = left_area_padding,
+                                                        Right = left_area_padding * 2,
                                                     },
-                                                }
-                                            },
-                                            new Container
-                                            {
-                                                RelativeSizeAxes = Axes.Both,
-                                                Padding = new MarginPadding
-                                                {
-                                                    Bottom = Footer.HEIGHT,
-                                                    Top = WEDGE_HEIGHT + 70,
-                                                    Left = left_area_padding,
-                                                    Right = left_area_padding * 2,
+                                                    Child = BeatmapDetails = CreateBeatmapDetailArea().With(d =>
+                                                    {
+                                                        d.RelativeSizeAxes = Axes.Both;
+                                                        d.Padding = new MarginPadding { Top = 10, Right = 5 };
+                                                    })
                                                 },
-                                                Child = BeatmapDetails = CreateBeatmapDetailArea().With(d =>
-                                                {
-                                                    d.RelativeSizeAxes = Axes.Both;
-                                                    d.Padding = new MarginPadding { Top = 10, Right = 5 };
-                                                })
-                                            },
+                                            }
                                         }
                                     },
                                 },
@@ -589,11 +557,6 @@ private void performUpdateSelected()
                 beatmapInfoPrevious = beatmap;
             }
 
-            // we can't run this in the debounced run due to the selected mods bindable not being debounced,
-            // since mods could be updated to the new ruleset instances while the decoupled bindable is held behind,
-            // therefore resulting in performing difficulty calculation with invalid states.
-            advancedStats.Ruleset.Value = ruleset;
-
             void run()
             {
                 // clear pending task immediately to track any potential nested debounce operation.
@@ -844,14 +807,10 @@ private void updateComponentFromBeatmap(WorkingBeatmap beatmap)
                 });
             }
 
-            beatmapInfoWedge.Beatmap = beatmap;
-
             BeatmapDetails.Beatmap = beatmap;
 
             ModSelect.Beatmap = beatmap;
 
-            advancedStats.BeatmapInfo = beatmap.BeatmapInfo;
-
             bool beatmapSelected = beatmap is not DummyWorkingBeatmap;
 
             if (beatmapSelected)
@@ -961,7 +920,13 @@ private void bindBindables()
             };
             decoupledRuleset.DisabledChanged += r => Ruleset.Disabled = r;
 
-            Beatmap.BindValueChanged(updateCarouselSelection);
+            Beatmap.BindValueChanged(b =>
+            {
+                updateCarouselSelection();
+
+                beatmapInfo.Value = b.NewValue.BeatmapInfo;
+                beatmapSetInfo.Value = b.NewValue.BeatmapSetInfo;
+            }, true);
 
             boundLocalBindables = true;
         }
@@ -1075,7 +1040,7 @@ public VerticalMaskingContainer()
         /// <summary>
         /// Handles mouse interactions required when moving away from the carousel.
         /// </summary>
-        internal partial class LeftSideInteractionContainer : Container
+        internal partial class LeftSideInteractionContainer : GridContainer
         {
             private readonly Action? resetCarouselPosition;
 

@bdach
Copy link
Collaborator

bdach commented May 2, 2024

Considered PRing individual components (and replacing old ones) more effort because

I wanna stop reading already at this point honestly.

Do you realise what reviewing of a 1.7k line diffstat feels like? Do you realise the bikeshed fiasco you're opening yourself up here for by getting this entire code blob stunlocked on minute design changes?

I dunno, this may be "less work" for you, but it is more work for me / other reviewers, and I see there being a 95% chance of this getting stuck in review hell again.

This is especially important for me because I would like to pay very close attention to how individual components are structured to avoid similar fiascos that plague the current song select and make implementing stuff like correct beatmap invalidation basically infeasible.

Please take a look at how I replaced the mod overlay for this. It was not a giant 5k line blob.

@peppy
Copy link
Sponsor Member

peppy commented May 2, 2024

Maybe I'm biased by wanting to make quick forward progress, but even with this many lines diff, if it's mostly drawable code and hardly any modified lines (all new lines) I'm willing to take it on – at least an initial pass to see how things read and feel.

Will loop back after doing a pass.

@bdach
Copy link
Collaborator

bdach commented May 2, 2024

Sure if you want to try and stomach it I guess. But I will want to have a look too before merge even if you end up being okay with it.

An important code design constraint is that every drawable component in here must support the beatmap changing under it for the invalidation flow. Preferably with a way to force a refresh manually too. If that is not the case, then I'm gonna have a problem with accepting this.

@peppy
Copy link
Sponsor Member

peppy commented May 2, 2024

@Joehuu what is the significance of the diff in the OP? It doesn't look to cleanly apply.

@peppy peppy self-requested a review May 2, 2024 14:44
@peppy
Copy link
Sponsor Member

peppy commented May 2, 2024

@arflyte how much of this change is going to be thrown away in the "next" iteration?

@frenzibyte
Copy link
Member

@Joehuu is this ready for a full code review and/or should it be taken out of draft?

@Joehuu
Copy link
Member Author

Joehuu commented May 13, 2024

This is ready for review, yes. Forgot to take this out of draft.

@Joehuu Joehuu marked this pull request as ready for review May 13, 2024 22:07
@Joehuu Joehuu mentioned this pull request Jun 11, 2024
@frenzibyte frenzibyte self-requested a review July 1, 2024 06:18

namespace osu.Game.Screens.Select
{
public partial class DifficultySettingsContent : FillFlowContainer, IHasCustomTooltip<AdjustedAttributesTooltip.Data>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Settings is a weird term to use here? I think you're looking for Attributes.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Comment on lines 51 to 53
public const float WEDGE_CORNER_RADIUS = 10;
public const float SHEAR_X = 0.175f;
public static readonly Vector2 WEDGED_CONTAINER_SHEAR = new Vector2(SHEAR_X, 0);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would probably just place this in BeatmapInfoWedgeV2, or at the very least in SongSelectV2 if they really need to be in the main screen class.

{
InternalChildren = new Drawable[]
{
// TODO: add metadata column
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suppose this will come in a follow-up PR? Probably a better idea since it's just additional details that don't necessarily have to exist in this diff.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I initially had the details tab implemented in the diff but it had too many LOC. Also the design was cluttered with it / gonna be redone anyway.

Comment on lines +77 to +81
// the cached ruleset bindable might be a decoupled bindable provided by SongSelect,
// which we can't rely on in combination with the game-wide selected mods list,
// since mods could be updated to the new ruleset instances while the decoupled bindable is held behind,
// therefore resulting in performing difficulty calculation with invalid states.
gameRuleset = game.Ruleset.GetBoundCopy();
Copy link
Member

@frenzibyte frenzibyte Jul 2, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As far as I'm concerned, the goal of the song select redesign task isn't only to update the visuals of the existing song select screen, but rewrite the entirety of the screen into a state where workarounds like these don't have to exist (since the current code in SongSelect is too convoluted/fragile to touch). cc @peppy to confirm on this direction.

Bindable decoupling is not implemented in SongSelectV2 yet, so I would suggest at least commenting workarounds like these until a point where we have SongSelectV2 in a finalised state and revisit if these workarounds are still necessary.

Copy link
Member Author

@Joehuu Joehuu Jul 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That was copied verbatim in AdvancedStats but was removed 5c049fe#diff-7537e2f2818506dce8cc62f117ec907f9cb69ca88513b5224cc524474447e654. Will see if this workaround is needed and will try to make extensive testing on rulesets whenever I get the chance next time.

Comment on lines +114 to +122
switch (beatmapInfo.Value)
{
case BeatmapInfo:
// TODO: consider mods which apply variable rates.
double rate = 1;
foreach (var mod in mods.Value.OfType<IApplicableToRate>())
rate = mod.ApplyToRate(0, rate);

var beatmap = workingBeatmap.Value.Beatmap;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't really know about this flow. Having this method being triggered by a change in beatmapInfo but then read from workingBeatmap inside the callback sounds like a recipe for disaster to me.

Reiterating what I mentioned in the flow presented by TestSceneSongSelectComponents, for UI components that are meant to be shared between song select and beatmap info overlay, let them accept the direct values that they require, and move this code to a general component that's tied directly to song select (i.e. BeatmapInfoWedgeV2).

This also means removing the APIBeatmap handling entirely. They will be handled in the new beatmap info overlay efforts.

Child = starCounter = new StarCounter
RelativeSizeAxes = Axes.X,
Height = WEDGE_HEIGHT,
Shear = SongSelect.WEDGED_CONTAINER_SHEAR,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Use OsuGame.SHEAR

Comment on lines 155 to 166
new InfoWedgeBackground
{
Child = new BasicBeatmapInfoContent
{
Padding = new MarginPadding
{
Left = text_margin,
Right = 20,
Vertical = 10
}
},
},
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

diff --git a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs
index fd3b6e73cc..88690ecc7f 100644
--- a/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs
+++ b/osu.Game/Screens/Select/BeatmapInfoWedgeV2.cs
@@ -165,6 +165,12 @@ private void load()
                     },
                     new InfoWedgeBackground
                     {
+                        Padding = new MarginPadding
+                        {
+                            Top = 10,
+                            Left = -WEDGE_CORNER_RADIUS,
+                            Right = SHEAR_WIDTH + COLOUR_BAR_WIDTH
+                        },
                         Child = new BasicBeatmapInfoContent
                         {
                             Padding = new MarginPadding
@@ -177,6 +183,12 @@ private void load()
                     },
                     new InfoWedgeBackground
                     {
+                        Padding = new MarginPadding
+                        {
+                            Top = 5,
+                            Left = -WEDGE_CORNER_RADIUS,
+                            Right = SHEAR_WIDTH + COLOUR_BAR_WIDTH + 8f,
+                        },
                         Child = new ExtendedBeatmapInfoContent
                         {
                             Padding = new MarginPadding
diff --git a/osu.Game/Screens/Select/InfoWedgeBackground.cs b/osu.Game/Screens/Select/InfoWedgeBackground.cs
index 6f505f99b4..8af0c8f9da 100644
--- a/osu.Game/Screens/Select/InfoWedgeBackground.cs
+++ b/osu.Game/Screens/Select/InfoWedgeBackground.cs
@@ -23,15 +23,6 @@ public InfoWedgeBackground()
         {
             RelativeSizeAxes = Axes.X;
             AutoSizeAxes = Axes.Y;
-
-            Padding = new MarginPadding
-            {
-                Top = 10,
-                Left = -SongSelect.WEDGE_CORNER_RADIUS,
-
-                // TODO: should account top wedge's shear width for alignment (hard to do as this auto-sizes height right now)
-                Right = BeatmapInfoWedgeV2.SHEAR_WIDTH + BeatmapInfoWedgeV2.COLOUR_BAR_WIDTH
-            };
         }
 
         [BackgroundDependencyLoader]

Pushes the second InfoWedgeBackground further to the left for overall alignment, and also decreases the spacing between the two wedges to match design.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I had this hardcoded like you do (the 8, but different), but I guess it's fine as it should have a fixed height.


namespace osu.Game.Screens.Select
{
public partial class LengthAndBPMStatisticPill : CompositeDrawable
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In response to the last point in #28063 (comment), I would like to know how everyone feels about having this instead:

CleanShot 2024-07-02 at 09 23 28

CleanShot.2024-07-02.at.09.22.00.mp4

cc @peppy @arflyte

@frenzibyte
Copy link
Member

@Joehuu Are you able to look into the points raised above?

@Joehuu
Copy link
Member Author

Joehuu commented Jul 17, 2024

Will get a chance today.

@peppy
Copy link
Sponsor Member

peppy commented Aug 15, 2024

For reference what's the blocking part here?

@Joehuu
Copy link
Member Author

Joehuu commented Aug 15, 2024

This being too big to review, and the bindable flow would need to be rewritten like the recent PR. This merely serves as a reference for me now, so closing.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Hit object icons are wrong for non-osu! rulesets
4 participants