From 7560d3de0435408c62d7e8ad9fbedfcb1e211747 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Mon, 22 Nov 2021 10:52:04 +1100 Subject: [PATCH 01/76] Remove decay factor in Flashlight skill --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 466f0556ab..68434fd3d0 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills { } - private double skillMultiplier => 0.15; + private double skillMultiplier => 0.07; private double strainDecayBase => 0.15; protected override double DecayWeight => 1.0; protected override int HistoryLength => 10; // Look back for 10 notes is added for the sake of flashlight calculations. @@ -58,7 +58,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills // We also want to nerf stacks so that only the first object of the stack is accounted for. double stackNerf = Math.Min(1.0, (osuPrevious.JumpDistance / scalingFactor) / 25.0); - result += Math.Pow(0.8, i) * stackNerf * scalingFactor * jumpDistance / cumulativeStrainTime; + result += stackNerf * scalingFactor * jumpDistance / cumulativeStrainTime; } } From 43546992586fcefc8f072deb091004edcf418175 Mon Sep 17 00:00:00 2001 From: MBmasher Date: Tue, 30 Nov 2021 12:51:23 +1100 Subject: [PATCH 02/76] Fix cumulative strain time calculation in Flashlight skill --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 466f0556ab..9c539d5e4b 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -45,11 +45,17 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills var osuPrevious = (OsuDifficultyHitObject)Previous[i]; var osuPreviousHitObject = (OsuHitObject)(osuPrevious.BaseObject); + OsuDifficultyHitObject osuLastPrevious; + if (i == 0) + osuLastPrevious = osuCurrent; + else + osuLastPrevious = (OsuDifficultyHitObject)Previous[i - 1]; + if (!(osuPrevious.BaseObject is Spinner)) { double jumpDistance = (osuHitObject.StackedPosition - osuPreviousHitObject.EndPosition).Length; - cumulativeStrainTime += osuPrevious.StrainTime; + cumulativeStrainTime += osuLastPrevious.StrainTime; // We want to nerf objects that can be easily seen within the Flashlight circle radius. if (i == 0) From a32492cdd5614f9d88c4d3e04b5222a10002b0ff Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 27 Nov 2021 15:27:41 +0100 Subject: [PATCH 03/76] Duplicate `BeatmapCard{-> Extra}` as blueprint for extra card size --- .../Visual/Beatmaps/TestSceneBeatmapCard.cs | 14 + .../Drawables/Cards/BeatmapCardExtra.cs | 430 ++++++++++++++++++ 2 files changed, 444 insertions(+) create mode 100644 osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs index f835d21603..04aea4ac9c 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs @@ -262,5 +262,19 @@ namespace osu.Game.Tests.Visual.Beatmaps }); AddToggleStep("disable/enable expansion", disabled => this.ChildrenOfType().ForEach(card => card.Expanded.Disabled = disabled)); } + + [Test] + public void TestExtra() + { + createTestCase(beatmapSetInfo => new BeatmapCardExtra(beatmapSetInfo)); + + AddToggleStep("toggle expanded state", expanded => + { + var card = this.ChildrenOfType().Last(); + if (!card.Expanded.Disabled) + card.Expanded.Value = expanded; + }); + AddToggleStep("disable/enable expansion", disabled => this.ChildrenOfType().ForEach(card => card.Expanded.Disabled = disabled)); + } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs new file mode 100644 index 0000000000..f244e912a1 --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs @@ -0,0 +1,430 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System.Collections.Generic; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Framework.Localisation; +using osu.Game.Beatmaps.Drawables.Cards.Buttons; +using osu.Game.Beatmaps.Drawables.Cards.Statistics; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; +using osu.Game.Overlays.BeatmapSet; +using osuTK; +using osu.Game.Resources.Localisation.Web; +using DownloadButton = osu.Game.Beatmaps.Drawables.Cards.Buttons.DownloadButton; + +namespace osu.Game.Beatmaps.Drawables.Cards +{ + public class BeatmapCardExtra : OsuClickableContainer + { + private const float width = 408; + private const float height = 100; + private const float icon_area_width = 30; + + public Bindable Expanded { get; } = new BindableBool(); + + private readonly APIBeatmapSet beatmapSet; + private readonly Bindable favouriteState; + + private readonly BeatmapDownloadTracker downloadTracker; + + private BeatmapCardContent content = null!; + + private BeatmapCardThumbnail thumbnail = null!; + + private Container rightAreaBackground = null!; + private Container rightAreaButtons = null!; + + private Container mainContent = null!; + private BeatmapCardContentBackground mainContentBackground = null!; + private FillFlowContainer statisticsContainer = null!; + + private FillFlowContainer idleBottomContent = null!; + private BeatmapCardDownloadProgressBar downloadProgressBar = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + public BeatmapCardExtra(APIBeatmapSet beatmapSet) + : base(HoverSampleSet.Submit) + { + this.beatmapSet = beatmapSet; + favouriteState = new Bindable(new BeatmapSetFavouriteState(beatmapSet.HasFavourited, beatmapSet.FavouriteCount)); + downloadTracker = new BeatmapDownloadTracker(beatmapSet); + } + + [BackgroundDependencyLoader(true)] + private void load(BeatmapSetOverlay? beatmapSetOverlay) + { + Width = width; + Height = height; + + FillFlowContainer leftIconArea; + GridContainer titleContainer; + GridContainer artistContainer; + + InternalChild = content = new BeatmapCardContent(height) + { + MainContent = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + downloadTracker, + rightAreaBackground = new Container + { + RelativeSizeAxes = Axes.Y, + Width = icon_area_width + 2 * BeatmapCard.CORNER_RADIUS, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + // workaround for masking artifacts at the top & bottom of card, + // which become especially visible on downloaded beatmaps (when the icon area has a lime background). + Padding = new MarginPadding { Vertical = 1 }, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.White + }, + }, + thumbnail = new BeatmapCardThumbnail(beatmapSet) + { + Name = @"Left (icon) area", + Size = new Vector2(height), + Padding = new MarginPadding { Right = BeatmapCard.CORNER_RADIUS }, + Child = leftIconArea = new FillFlowContainer + { + Margin = new MarginPadding(5), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(1) + } + }, + new Container + { + Name = @"Right (button) area", + Width = 30, + RelativeSizeAxes = Axes.Y, + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + Padding = new MarginPadding { Vertical = 17.5f }, + Child = rightAreaButtons = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new BeatmapCardIconButton[] + { + new FavouriteButton(beatmapSet) + { + Current = favouriteState, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + }, + new DownloadButton(beatmapSet) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + State = { BindTarget = downloadTracker.State } + }, + new GoToBeatmapButton(beatmapSet) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + State = { BindTarget = downloadTracker.State } + } + } + } + }, + mainContent = new Container + { + Name = @"Main content", + X = height - BeatmapCard.CORNER_RADIUS, + Height = height, + CornerRadius = BeatmapCard.CORNER_RADIUS, + Masking = true, + Children = new Drawable[] + { + mainContentBackground = new BeatmapCardContentBackground(beatmapSet) + { + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + Horizontal = 10, + Vertical = 4 + }, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + titleContainer = new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new[] + { + new OsuSpriteText + { + Text = new RomanisableString(beatmapSet.TitleUnicode, beatmapSet.Title), + Font = OsuFont.Default.With(size: 22.5f, weight: FontWeight.SemiBold), + RelativeSizeAxes = Axes.X, + Truncate = true + }, + Empty() + } + } + }, + artistContainer = new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize) + }, + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize) + }, + Content = new[] + { + new[] + { + new OsuSpriteText + { + Text = createArtistText(), + Font = OsuFont.Default.With(size: 17.5f, weight: FontWeight.SemiBold), + RelativeSizeAxes = Axes.X, + Truncate = true + }, + Empty() + }, + } + }, + new LinkFlowContainer(s => + { + s.Shadow = false; + s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold); + }).With(d => + { + d.AutoSizeAxes = Axes.Both; + d.Margin = new MarginPadding { Top = 2 }; + d.AddText("mapped by ", t => t.Colour = colourProvider.Content2); + d.AddUserLink(beatmapSet.Author); + }), + } + }, + new Container + { + Name = @"Bottom content", + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Padding = new MarginPadding + { + Horizontal = 10, + Vertical = 4 + }, + Children = new Drawable[] + { + idleBottomContent = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0, 3), + AlwaysPresent = true, + Children = new Drawable[] + { + statisticsContainer = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Alpha = 0, + AlwaysPresent = true, + ChildrenEnumerable = createStatistics() + }, + new BeatmapCardExtraInfoRow(beatmapSet) + { + Hovered = _ => + { + content.ScheduleShow(); + return false; + }, + Unhovered = _ => + { + // This hide should only trigger if the expanded content has not shown yet. + // ie. if the user has not shown intent to want to see it (quickly moved over the info row area). + if (!Expanded.Value) + content.ScheduleHide(); + } + } + } + }, + downloadProgressBar = new BeatmapCardDownloadProgressBar + { + RelativeSizeAxes = Axes.X, + Height = 6, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + State = { BindTarget = downloadTracker.State }, + Progress = { BindTarget = downloadTracker.Progress } + } + } + } + } + } + } + }, + ExpandedContent = new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Horizontal = 10, Vertical = 13 }, + Child = new BeatmapCardDifficultyList(beatmapSet) + }, + Expanded = { BindTarget = Expanded } + }; + + if (beatmapSet.HasVideo) + leftIconArea.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) }); + + if (beatmapSet.HasStoryboard) + leftIconArea.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) }); + + if (beatmapSet.HasExplicitContent) + { + titleContainer.Content[0][1] = new ExplicitContentBeatmapPill + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Margin = new MarginPadding { Left = 5 } + }; + } + + if (beatmapSet.TrackId != null) + { + artistContainer.Content[0][1] = new FeaturedArtistBeatmapPill + { + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Margin = new MarginPadding { Left = 5 } + }; + } + + Action = () => beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmapSet.OnlineID); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + downloadTracker.State.BindValueChanged(_ => updateState()); + Expanded.BindValueChanged(_ => updateState(), true); + FinishTransforms(true); + } + + protected override bool OnHover(HoverEvent e) + { + updateState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + content.ScheduleHide(); + + updateState(); + base.OnHoverLost(e); + } + + private LocalisableString createArtistText() + { + var romanisableArtist = new RomanisableString(beatmapSet.ArtistUnicode, beatmapSet.Artist); + return BeatmapsetsStrings.ShowDetailsByArtist(romanisableArtist); + } + + private IEnumerable createStatistics() + { + if (beatmapSet.HypeStatus != null) + yield return new HypesStatistic(beatmapSet.HypeStatus); + + // web does not show nominations unless hypes are also present. + // see: https://github.com/ppy/osu-web/blob/8ed7d071fd1d3eaa7e43cf0e4ff55ca2fef9c07c/resources/assets/lib/beatmapset-panel.tsx#L443 + if (beatmapSet.HypeStatus != null && beatmapSet.NominationStatus != null) + yield return new NominationsStatistic(beatmapSet.NominationStatus); + + yield return new FavouritesStatistic(beatmapSet) { Current = favouriteState }; + yield return new PlayCountStatistic(beatmapSet); + + var dateStatistic = BeatmapCardDateStatistic.CreateFor(beatmapSet); + if (dateStatistic != null) + yield return dateStatistic; + } + + private void updateState() + { + bool showDetails = IsHovered || Expanded.Value; + + float targetWidth = width - height; + if (showDetails) + targetWidth = targetWidth - icon_area_width + BeatmapCard.CORNER_RADIUS; + + thumbnail.Dimmed.Value = showDetails; + + // Scale value is intentionally chosen to fit in the spacing of listing displays, as to not overlap horizontally with adjacent cards. + // This avoids depth issues where a hovered (scaled) card to the right of another card would be beneath the card to the left. + content.ScaleTo(Expanded.Value ? 1.03f : 1, 500, Easing.OutQuint); + + mainContent.ResizeWidthTo(targetWidth, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + mainContentBackground.Dimmed.Value = showDetails; + + statisticsContainer.FadeTo(showDetails ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + + rightAreaBackground.FadeColour(downloadTracker.State.Value == DownloadState.LocallyAvailable ? colours.Lime0 : colourProvider.Background3, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + rightAreaButtons.FadeTo(showDetails ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + + foreach (var button in rightAreaButtons) + { + button.IdleColour = downloadTracker.State.Value != DownloadState.LocallyAvailable ? colourProvider.Light1 : colourProvider.Background3; + button.HoverColour = downloadTracker.State.Value != DownloadState.LocallyAvailable ? colourProvider.Content1 : colourProvider.Foreground1; + } + + bool showProgress = downloadTracker.State.Value == DownloadState.Downloading || downloadTracker.State.Value == DownloadState.Importing; + + idleBottomContent.FadeTo(showProgress ? 0 : 1, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + downloadProgressBar.FadeTo(showProgress ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + } + } +} From 61e04f75cc7ad4c5966e605525679e7b28f23ebb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 27 Nov 2021 15:41:09 +0100 Subject: [PATCH 04/76] Resize extra card to design size --- osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs index f244e912a1..7e79bded5d 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs @@ -30,8 +30,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards { public class BeatmapCardExtra : OsuClickableContainer { - private const float width = 408; - private const float height = 100; + private const float width = 475; + private const float height = 140; private const float icon_area_width = 30; public Bindable Expanded { get; } = new BindableBool(); From 419fee1380ad94e068b417e7ea3fc1a5244c078e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 27 Nov 2021 15:46:01 +0100 Subject: [PATCH 05/76] Move mapper link to bottom content --- .../Drawables/Cards/BeatmapCardExtra.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs index 7e79bded5d..f42c96be7c 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs @@ -229,17 +229,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards }, } }, - new LinkFlowContainer(s => - { - s.Shadow = false; - s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold); - }).With(d => - { - d.AutoSizeAxes = Axes.Both; - d.Margin = new MarginPadding { Top = 2 }; - d.AddText("mapped by ", t => t.Colour = colourProvider.Content2); - d.AddUserLink(beatmapSet.Author); - }), } }, new Container @@ -265,6 +254,17 @@ namespace osu.Game.Beatmaps.Drawables.Cards AlwaysPresent = true, Children = new Drawable[] { + new LinkFlowContainer(s => + { + s.Shadow = false; + s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold); + }).With(d => + { + d.AutoSizeAxes = Axes.Both; + d.Margin = new MarginPadding { Top = 2 }; + d.AddText("mapped by ", t => t.Colour = colourProvider.Content2); + d.AddUserLink(beatmapSet.Author); + }), statisticsContainer = new FillFlowContainer { RelativeSizeAxes = Axes.X, From 3ecfaa532cb270e36e96344b7f88488105e6b15c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 27 Nov 2021 15:47:52 +0100 Subject: [PATCH 06/76] Add source field to extra beatmap card --- osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs | 1 + osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs | 9 +++++++++ 2 files changed, 10 insertions(+) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs index 04aea4ac9c..2a308dd0d4 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs @@ -97,6 +97,7 @@ namespace osu.Game.Tests.Visual.Beatmaps var longName = CreateAPIBeatmapSet(Ruleset.Value); longName.Title = longName.TitleUnicode = "this track has an incredibly and implausibly long title"; longName.Artist = longName.ArtistUnicode = "and this artist! who would have thunk it. it's really such a long name."; + longName.Source = "wow. even the source field has an impossibly long string in it. this really takes the cake, doesn't it?"; longName.HasExplicitContent = true; longName.TrackId = 444; diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs index f42c96be7c..1926614c8f 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs @@ -229,6 +229,15 @@ namespace osu.Game.Beatmaps.Drawables.Cards }, } }, + new OsuSpriteText + { + RelativeSizeAxes = Axes.X, + Truncate = true, + Text = beatmapSet.Source, + Shadow = false, + Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold), + Colour = colourProvider.Content2 + }, } }, new Container From 2d739c95ea133a9b0068a283a7296700a6377f54 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 27 Nov 2021 16:01:35 +0100 Subject: [PATCH 07/76] Lay out extra card statistics in grid as per design --- .../Drawables/Cards/BeatmapCardExtra.cs | 54 +++++++++++++------ 1 file changed, 37 insertions(+), 17 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs index 1926614c8f..663bb26919 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs @@ -3,7 +3,6 @@ #nullable enable -using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -50,7 +49,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards private Container mainContent = null!; private BeatmapCardContentBackground mainContentBackground = null!; - private FillFlowContainer statisticsContainer = null!; + private GridContainer statisticsContainer = null!; private FillFlowContainer idleBottomContent = null!; private BeatmapCardDownloadProgressBar downloadProgressBar = null!; @@ -274,15 +273,26 @@ namespace osu.Game.Beatmaps.Drawables.Cards d.AddText("mapped by ", t => t.Colour = colourProvider.Content2); d.AddUserLink(beatmapSet.Author); }), - statisticsContainer = new FillFlowContainer + statisticsContainer = new GridContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - Direction = FillDirection.Horizontal, - Spacing = new Vector2(10, 0), - Alpha = 0, - AlwaysPresent = true, - ChildrenEnumerable = createStatistics() + RowDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize) + }, + ColumnDimensions = new[] + { + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.AutoSize), + new Dimension() + }, + Content = new[] + { + new Drawable[3], + new Drawable[3] + } }, new BeatmapCardExtraInfoRow(beatmapSet) { @@ -352,6 +362,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards }; } + createStatistics(); + Action = () => beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmapSet.OnlineID); } @@ -384,22 +396,32 @@ namespace osu.Game.Beatmaps.Drawables.Cards return BeatmapsetsStrings.ShowDetailsByArtist(romanisableArtist); } - private IEnumerable createStatistics() + private void createStatistics() { + BeatmapCardStatistic withMargin(BeatmapCardStatistic original) + { + original.Margin = new MarginPadding { Right = 10 }; + return original; + } + + statisticsContainer.Content[0][0] = withMargin(new FavouritesStatistic(beatmapSet) + { + Current = favouriteState, + }); + + statisticsContainer.Content[1][0] = withMargin(new PlayCountStatistic(beatmapSet)); + if (beatmapSet.HypeStatus != null) - yield return new HypesStatistic(beatmapSet.HypeStatus); + statisticsContainer.Content[0][1] = withMargin(new HypesStatistic(beatmapSet.HypeStatus)); // web does not show nominations unless hypes are also present. // see: https://github.com/ppy/osu-web/blob/8ed7d071fd1d3eaa7e43cf0e4ff55ca2fef9c07c/resources/assets/lib/beatmapset-panel.tsx#L443 if (beatmapSet.HypeStatus != null && beatmapSet.NominationStatus != null) - yield return new NominationsStatistic(beatmapSet.NominationStatus); - - yield return new FavouritesStatistic(beatmapSet) { Current = favouriteState }; - yield return new PlayCountStatistic(beatmapSet); + statisticsContainer.Content[1][1] = withMargin(new NominationsStatistic(beatmapSet.NominationStatus)); var dateStatistic = BeatmapCardDateStatistic.CreateFor(beatmapSet); if (dateStatistic != null) - yield return dateStatistic; + statisticsContainer.Content[0][2] = withMargin(dateStatistic); } private void updateState() @@ -419,8 +441,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards mainContent.ResizeWidthTo(targetWidth, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); mainContentBackground.Dimmed.Value = showDetails; - statisticsContainer.FadeTo(showDetails ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - rightAreaBackground.FadeColour(downloadTracker.State.Value == DownloadState.LocallyAvailable ? colours.Lime0 : colourProvider.Background3, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); rightAreaButtons.FadeTo(showDetails ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); From 083ee92deea8f018032fcaa9d111f2192dd76a43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Sat, 27 Nov 2021 16:09:00 +0100 Subject: [PATCH 08/76] Adjust button vertical padding --- osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs index 663bb26919..f47e1a7eab 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs @@ -121,7 +121,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards RelativeSizeAxes = Axes.Y, Origin = Anchor.TopRight, Anchor = Anchor.TopRight, - Padding = new MarginPadding { Vertical = 17.5f }, + Padding = new MarginPadding { Vertical = 35 }, Child = rightAreaButtons = new Container { RelativeSizeAxes = Axes.Both, From 883fcf26044bef614e4d6d08d9e4719acb537ea9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 17 Dec 2021 11:44:36 +0100 Subject: [PATCH 09/76] Fix tests --- .../Visual/Beatmaps/TestSceneBeatmapCard.cs | 20 ++++++------------- 1 file changed, 6 insertions(+), 14 deletions(-) diff --git a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs index 55dbf89334..2b98c61e9b 100644 --- a/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs +++ b/osu.Game.Tests/Visual/Beatmaps/TestSceneBeatmapCard.cs @@ -255,6 +255,12 @@ namespace osu.Game.Tests.Visual.Beatmaps createTestCase(beatmapSetInfo => new BeatmapCard(beatmapSetInfo)); } + [Test] + public void TestExtra() + { + createTestCase(beatmapSetInfo => new BeatmapCardExtra(beatmapSetInfo)); + } + [Test] public void TestHoverState() { @@ -280,19 +286,5 @@ namespace osu.Game.Tests.Visual.Beatmaps BeatmapCard firstCard() => this.ChildrenOfType().First(); } - - [Test] - public void TestExtra() - { - createTestCase(beatmapSetInfo => new BeatmapCardExtra(beatmapSetInfo)); - - AddToggleStep("toggle expanded state", expanded => - { - var card = this.ChildrenOfType().Last(); - if (!card.Expanded.Disabled) - card.Expanded.Value = expanded; - }); - AddToggleStep("disable/enable expansion", disabled => this.ChildrenOfType().ForEach(card => card.Expanded.Disabled = disabled)); - } } } From 3fa45479b0cc3c5d0dbd9e024f22cf5b954d29a6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 17 Dec 2021 11:13:07 +0100 Subject: [PATCH 10/76] Share hype/nomination statistic show logic --- osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs | 12 ++++++------ .../Beatmaps/Drawables/Cards/BeatmapCardExtra.cs | 12 ++++++------ .../Drawables/Cards/Statistics/HypesStatistic.cs | 7 ++++++- .../Cards/Statistics/NominationsStatistic.cs | 9 ++++++++- 4 files changed, 26 insertions(+), 14 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 1e24501426..14c29f8e99 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -380,13 +380,13 @@ namespace osu.Game.Beatmaps.Drawables.Cards private IEnumerable createStatistics() { - if (beatmapSet.HypeStatus != null) - yield return new HypesStatistic(beatmapSet.HypeStatus); + var hypesStatistic = HypesStatistic.CreateFor(beatmapSet); + if (hypesStatistic != null) + yield return hypesStatistic; - // web does not show nominations unless hypes are also present. - // see: https://github.com/ppy/osu-web/blob/8ed7d071fd1d3eaa7e43cf0e4ff55ca2fef9c07c/resources/assets/lib/beatmapset-panel.tsx#L443 - if (beatmapSet.HypeStatus != null && beatmapSet.NominationStatus != null) - yield return new NominationsStatistic(beatmapSet.NominationStatus); + var nominationsStatistic = NominationsStatistic.CreateFor(beatmapSet); + if (nominationsStatistic != null) + yield return nominationsStatistic; yield return new FavouritesStatistic(beatmapSet) { Current = favouriteState }; yield return new PlayCountStatistic(beatmapSet); diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs index 8d3c606d76..2ccc732119 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs @@ -409,13 +409,13 @@ namespace osu.Game.Beatmaps.Drawables.Cards statisticsContainer.Content[1][0] = withMargin(new PlayCountStatistic(beatmapSet)); - if (beatmapSet.HypeStatus != null) - statisticsContainer.Content[0][1] = withMargin(new HypesStatistic(beatmapSet.HypeStatus)); + var hypesStatistic = HypesStatistic.CreateFor(beatmapSet); + if (hypesStatistic != null) + statisticsContainer.Content[0][1] = withMargin(hypesStatistic); - // web does not show nominations unless hypes are also present. - // see: https://github.com/ppy/osu-web/blob/8ed7d071fd1d3eaa7e43cf0e4ff55ca2fef9c07c/resources/assets/lib/beatmapset-panel.tsx#L443 - if (beatmapSet.HypeStatus != null && beatmapSet.NominationStatus != null) - statisticsContainer.Content[1][1] = withMargin(new NominationsStatistic(beatmapSet.NominationStatus)); + var nominationsStatistic = NominationsStatistic.CreateFor(beatmapSet); + if (nominationsStatistic != null) + statisticsContainer.Content[1][1] = withMargin(nominationsStatistic); var dateStatistic = BeatmapCardDateStatistic.CreateFor(beatmapSet); if (dateStatistic != null) diff --git a/osu.Game/Beatmaps/Drawables/Cards/Statistics/HypesStatistic.cs b/osu.Game/Beatmaps/Drawables/Cards/Statistics/HypesStatistic.cs index 3fe31c7a41..521d1a5f21 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Statistics/HypesStatistic.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Statistics/HypesStatistic.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics.Sprites; using osu.Game.Resources.Localisation.Web; @@ -12,11 +14,14 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Statistics /// public class HypesStatistic : BeatmapCardStatistic { - public HypesStatistic(BeatmapSetHypeStatus hypeStatus) + private HypesStatistic(BeatmapSetHypeStatus hypeStatus) { Icon = FontAwesome.Solid.Bullhorn; Text = hypeStatus.Current.ToLocalisableString(); TooltipText = BeatmapsStrings.HypeRequiredText(hypeStatus.Current.ToLocalisableString(), hypeStatus.Required.ToLocalisableString()); } + + public static HypesStatistic? CreateFor(IBeatmapSetOnlineInfo beatmapSetOnlineInfo) + => beatmapSetOnlineInfo.HypeStatus == null ? null : new HypesStatistic(beatmapSetOnlineInfo.HypeStatus); } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/Statistics/NominationsStatistic.cs b/osu.Game/Beatmaps/Drawables/Cards/Statistics/NominationsStatistic.cs index f09269a615..23bd6ef0a9 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Statistics/NominationsStatistic.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Statistics/NominationsStatistic.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + using osu.Framework.Extensions.LocalisationExtensions; using osu.Framework.Graphics.Sprites; using osu.Game.Resources.Localisation.Web; @@ -12,11 +14,16 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Statistics /// public class NominationsStatistic : BeatmapCardStatistic { - public NominationsStatistic(BeatmapSetNominationStatus nominationStatus) + private NominationsStatistic(BeatmapSetNominationStatus nominationStatus) { Icon = FontAwesome.Solid.ThumbsUp; Text = nominationStatus.Current.ToLocalisableString(); TooltipText = BeatmapsStrings.NominationsRequiredText(nominationStatus.Current.ToLocalisableString(), nominationStatus.Required.ToLocalisableString()); } + + public static NominationsStatistic? CreateFor(IBeatmapSetOnlineInfo beatmapSetOnlineInfo) + // web does not show nominations unless hypes are also present. + // see: https://github.com/ppy/osu-web/blob/8ed7d071fd1d3eaa7e43cf0e4ff55ca2fef9c07c/resources/assets/lib/beatmapset-panel.tsx#L443 + => beatmapSetOnlineInfo.HypeStatus == null || beatmapSetOnlineInfo.NominationStatus == null ? null : new NominationsStatistic(beatmapSetOnlineInfo.NominationStatus); } } From 7aab12d4b061d6621e5c3b5e5bc36be8a75e8c06 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 17 Dec 2021 11:27:38 +0100 Subject: [PATCH 11/76] Share extra row dropdown show/cancel show logic --- .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 38 +++++++------------ .../Drawables/Cards/BeatmapCardExtra.cs | 38 +++++++------------ .../Cards/BeatmapCardExtraInfoRow.cs | 25 +++++++++++- 3 files changed, 49 insertions(+), 52 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 14c29f8e99..4892abc846 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -44,7 +44,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards private readonly BeatmapDownloadTracker downloadTracker; - private BeatmapCardContent content = null!; + [Cached] + private readonly BeatmapCardContent content; private BeatmapCardThumbnail thumbnail = null!; @@ -72,6 +73,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards this.beatmapSet = beatmapSet; favouriteState = new Bindable(new BeatmapSetFavouriteState(beatmapSet.HasFavourited, beatmapSet.FavouriteCount)); downloadTracker = new BeatmapDownloadTracker(beatmapSet); + content = new BeatmapCardContent(height); } [BackgroundDependencyLoader(true)] @@ -80,13 +82,13 @@ namespace osu.Game.Beatmaps.Drawables.Cards Width = width; Height = height; - FillFlowContainer leftIconArea; - GridContainer titleContainer; - GridContainer artistContainer; + FillFlowContainer leftIconArea = null!; + GridContainer titleContainer = null!; + GridContainer artistContainer = null!; - InternalChild = content = new BeatmapCardContent(height) + InternalChild = content.With(c => { - MainContent = new Container + c.MainContent = new Container { RelativeSizeAxes = Axes.Both, Children = new Drawable[] @@ -281,20 +283,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards ChildrenEnumerable = createStatistics() }, new BeatmapCardExtraInfoRow(beatmapSet) - { - Hovered = _ => - { - content.ExpandAfterDelay(); - return false; - }, - Unhovered = _ => - { - // Handles the case where a user has not shown explicit intent to view expanded info. - // ie. quickly moved over the info row area but didn't remain within it. - if (!Expanded.Value) - content.CancelExpand(); - } - } } }, downloadProgressBar = new BeatmapCardDownloadProgressBar @@ -311,16 +299,16 @@ namespace osu.Game.Beatmaps.Drawables.Cards } } } - }, - ExpandedContent = new Container + }; + c.ExpandedContent = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Horizontal = 10, Vertical = 13 }, Child = new BeatmapCardDifficultyList(beatmapSet) - }, - Expanded = { BindTarget = Expanded } - }; + }; + c.Expanded.BindTarget = Expanded; + }); if (beatmapSet.HasVideo) leftIconArea.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) }); diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs index 2ccc732119..328294a323 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs @@ -40,7 +40,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards private readonly BeatmapDownloadTracker downloadTracker; - private BeatmapCardContent content = null!; + [Cached] + private readonly BeatmapCardContent content; private BeatmapCardThumbnail thumbnail = null!; @@ -66,6 +67,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards this.beatmapSet = beatmapSet; favouriteState = new Bindable(new BeatmapSetFavouriteState(beatmapSet.HasFavourited, beatmapSet.FavouriteCount)); downloadTracker = new BeatmapDownloadTracker(beatmapSet); + content = new BeatmapCardContent(height); } [BackgroundDependencyLoader(true)] @@ -74,13 +76,13 @@ namespace osu.Game.Beatmaps.Drawables.Cards Width = width; Height = height; - FillFlowContainer leftIconArea; - GridContainer titleContainer; - GridContainer artistContainer; + FillFlowContainer leftIconArea = null!; + GridContainer titleContainer = null!; + GridContainer artistContainer = null!; - InternalChild = content = new BeatmapCardContent(height) + InternalChild = content.With(c => { - MainContent = new Container + c.MainContent = new Container { RelativeSizeAxes = Axes.Both, Children = new Drawable[] @@ -295,20 +297,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards } }, new BeatmapCardExtraInfoRow(beatmapSet) - { - Hovered = _ => - { - content.ExpandAfterDelay(); - return false; - }, - Unhovered = _ => - { - // This hide should only trigger if the expanded content has not shown yet. - // ie. if the user has not shown intent to want to see it (quickly moved over the info row area). - if (!Expanded.Value) - content.CancelExpand(); - } - } } }, downloadProgressBar = new BeatmapCardDownloadProgressBar @@ -325,16 +313,16 @@ namespace osu.Game.Beatmaps.Drawables.Cards } } } - }, - ExpandedContent = new Container + }; + c.ExpandedContent = new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Horizontal = 10, Vertical = 13 }, Child = new BeatmapCardDifficultyList(beatmapSet) - }, - Expanded = { BindTarget = Expanded } - }; + }; + c.Expanded.BindTarget = Expanded; + }); if (beatmapSet.HasVideo) leftIconArea.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) }); diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtraInfoRow.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtraInfoRow.cs index 0a9d98e621..2d411ad344 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtraInfoRow.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtraInfoRow.cs @@ -1,21 +1,28 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; using osu.Game.Online.API.Requests.Responses; using osuTK; namespace osu.Game.Beatmaps.Drawables.Cards { - public class BeatmapCardExtraInfoRow : HoverHandlingContainer + public class BeatmapCardExtraInfoRow : CompositeDrawable { + [Resolved(CanBeNull = true)] + private BeatmapCardContent? content { get; set; } + public BeatmapCardExtraInfoRow(APIBeatmapSet beatmapSet) { RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - Child = new FillFlowContainer + InternalChild = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -39,5 +46,19 @@ namespace osu.Game.Beatmaps.Drawables.Cards } }; } + + protected override bool OnHover(HoverEvent e) + { + content?.ExpandAfterDelay(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + if (content?.Expanded.Value == false) + content.CancelExpand(); + + base.OnHoverLost(e); + } } } From f052b47d873cbe41277295a6ac75525a72fe996e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 17 Dec 2021 12:58:05 +0100 Subject: [PATCH 12/76] Extract collapsible button container for card usage --- .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 104 +--------- .../Drawables/Cards/BeatmapCardExtra.cs | 104 +--------- .../Cards/CollapsibleButtonContainer.cs | 184 ++++++++++++++++++ 3 files changed, 200 insertions(+), 192 deletions(-) create mode 100644 osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 4892abc846..13c4cfe207 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -8,11 +8,9 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Localisation; -using osu.Game.Beatmaps.Drawables.Cards.Buttons; using osu.Game.Beatmaps.Drawables.Cards.Statistics; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -24,7 +22,6 @@ using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet; using osuTK; using osu.Game.Resources.Localisation.Web; -using DownloadButton = osu.Game.Beatmaps.Drawables.Cards.Buttons.DownloadButton; namespace osu.Game.Beatmaps.Drawables.Cards { @@ -37,7 +34,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards private const float width = 408; private const float height = 100; - private const float icon_area_width = 30; private readonly APIBeatmapSet beatmapSet; private readonly Bindable favouriteState; @@ -48,20 +44,13 @@ namespace osu.Game.Beatmaps.Drawables.Cards private readonly BeatmapCardContent content; private BeatmapCardThumbnail thumbnail = null!; + private CollapsibleButtonContainer buttonContainer = null!; - private Container rightAreaBackground = null!; - private Container rightAreaButtons = null!; - - private Container mainContent = null!; - private BeatmapCardContentBackground mainContentBackground = null!; private FillFlowContainer statisticsContainer = null!; private FillFlowContainer idleBottomContent = null!; private BeatmapCardDownloadProgressBar downloadProgressBar = null!; - [Resolved] - private OsuColour colours { get; set; } = null!; - [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; @@ -94,21 +83,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards Children = new Drawable[] { downloadTracker, - rightAreaBackground = new Container - { - RelativeSizeAxes = Axes.Y, - Width = icon_area_width + 2 * CORNER_RADIUS, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - // workaround for masking artifacts at the top & bottom of card, - // which become especially visible on downloaded beatmaps (when the icon area has a lime background). - Padding = new MarginPadding { Vertical = 1 }, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Colour4.White - }, - }, thumbnail = new BeatmapCardThumbnail(beatmapSet) { Name = @"Left (icon) area", @@ -122,61 +96,19 @@ namespace osu.Game.Beatmaps.Drawables.Cards Spacing = new Vector2(1) } }, - new Container + buttonContainer = new CollapsibleButtonContainer(beatmapSet) { - Name = @"Right (button) area", - Width = 30, - RelativeSizeAxes = Axes.Y, - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, - Padding = new MarginPadding { Vertical = 17.5f }, - Child = rightAreaButtons = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new BeatmapCardIconButton[] - { - new FavouriteButton(beatmapSet) - { - Current = favouriteState, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre - }, - new DownloadButton(beatmapSet) - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - State = { BindTarget = downloadTracker.State } - }, - new GoToBeatmapButton(beatmapSet) - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - State = { BindTarget = downloadTracker.State } - } - } - } - }, - mainContent = new Container - { - Name = @"Main content", X = height - CORNER_RADIUS, - Height = height, - CornerRadius = CORNER_RADIUS, - Masking = true, + Width = width - height + CORNER_RADIUS, + FavouriteState = { BindTarget = favouriteState }, + ButtonsCollapsedWidth = CORNER_RADIUS, + ButtonsExpandedWidth = 30, + ButtonsPadding = new MarginPadding { Vertical = 17.5f }, Children = new Drawable[] { - mainContentBackground = new BeatmapCardContentBackground(beatmapSet) - { - RelativeSizeAxes = Axes.Both, - }, new FillFlowContainer { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding - { - Horizontal = 10, - Vertical = 4 - }, Direction = FillDirection.Vertical, Children = new Drawable[] { @@ -256,11 +188,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards AutoSizeAxes = Axes.Y, Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Padding = new MarginPadding - { - Horizontal = 10, - Vertical = 4 - }, Children = new Drawable[] { idleBottomContent = new FillFlowContainer @@ -388,30 +315,15 @@ namespace osu.Game.Beatmaps.Drawables.Cards { bool showDetails = IsHovered || Expanded.Value; - float targetWidth = width - height; - if (showDetails) - targetWidth = targetWidth - icon_area_width + CORNER_RADIUS; - + buttonContainer.ShowDetails.Value = showDetails; thumbnail.Dimmed.Value = showDetails; // Scale value is intentionally chosen to fit in the spacing of listing displays, as to not overlap horizontally with adjacent cards. // This avoids depth issues where a hovered (scaled) card to the right of another card would be beneath the card to the left. content.ScaleTo(Expanded.Value ? 1.03f : 1, 500, Easing.OutQuint); - mainContent.ResizeWidthTo(targetWidth, TRANSITION_DURATION, Easing.OutQuint); - mainContentBackground.Dimmed.Value = showDetails; - statisticsContainer.FadeTo(showDetails ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); - rightAreaBackground.FadeColour(downloadTracker.State.Value == DownloadState.LocallyAvailable ? colours.Lime0 : colourProvider.Background3, TRANSITION_DURATION, Easing.OutQuint); - rightAreaButtons.FadeTo(showDetails ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); - - foreach (var button in rightAreaButtons) - { - button.IdleColour = downloadTracker.State.Value != DownloadState.LocallyAvailable ? colourProvider.Light1 : colourProvider.Background3; - button.HoverColour = downloadTracker.State.Value != DownloadState.LocallyAvailable ? colourProvider.Content1 : colourProvider.Foreground1; - } - bool showProgress = downloadTracker.State.Value == DownloadState.Downloading || downloadTracker.State.Value == DownloadState.Importing; idleBottomContent.FadeTo(showProgress ? 0 : 1, TRANSITION_DURATION, Easing.OutQuint); diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs index 328294a323..fb498d643a 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs @@ -7,11 +7,9 @@ using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Input.Events; using osu.Framework.Localisation; -using osu.Game.Beatmaps.Drawables.Cards.Buttons; using osu.Game.Beatmaps.Drawables.Cards.Statistics; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -23,7 +21,6 @@ using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet; using osuTK; using osu.Game.Resources.Localisation.Web; -using DownloadButton = osu.Game.Beatmaps.Drawables.Cards.Buttons.DownloadButton; namespace osu.Game.Beatmaps.Drawables.Cards { @@ -31,7 +28,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards { private const float width = 475; private const float height = 140; - private const float icon_area_width = 30; public Bindable Expanded { get; } = new BindableBool(); @@ -44,20 +40,13 @@ namespace osu.Game.Beatmaps.Drawables.Cards private readonly BeatmapCardContent content; private BeatmapCardThumbnail thumbnail = null!; + private CollapsibleButtonContainer buttonContainer = null!; - private Container rightAreaBackground = null!; - private Container rightAreaButtons = null!; - - private Container mainContent = null!; - private BeatmapCardContentBackground mainContentBackground = null!; private GridContainer statisticsContainer = null!; private FillFlowContainer idleBottomContent = null!; private BeatmapCardDownloadProgressBar downloadProgressBar = null!; - [Resolved] - private OsuColour colours { get; set; } = null!; - [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; @@ -88,21 +77,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards Children = new Drawable[] { downloadTracker, - rightAreaBackground = new Container - { - RelativeSizeAxes = Axes.Y, - Width = icon_area_width + 2 * BeatmapCard.CORNER_RADIUS, - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - // workaround for masking artifacts at the top & bottom of card, - // which become especially visible on downloaded beatmaps (when the icon area has a lime background). - Padding = new MarginPadding { Vertical = 1 }, - Child = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Colour4.White - }, - }, thumbnail = new BeatmapCardThumbnail(beatmapSet) { Name = @"Left (icon) area", @@ -116,61 +90,19 @@ namespace osu.Game.Beatmaps.Drawables.Cards Spacing = new Vector2(1) } }, - new Container + buttonContainer = new CollapsibleButtonContainer(beatmapSet) { - Name = @"Right (button) area", - Width = 30, - RelativeSizeAxes = Axes.Y, - Origin = Anchor.TopRight, - Anchor = Anchor.TopRight, - Padding = new MarginPadding { Vertical = 35 }, - Child = rightAreaButtons = new Container - { - RelativeSizeAxes = Axes.Both, - Children = new BeatmapCardIconButton[] - { - new FavouriteButton(beatmapSet) - { - Current = favouriteState, - Anchor = Anchor.TopCentre, - Origin = Anchor.TopCentre - }, - new DownloadButton(beatmapSet) - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - State = { BindTarget = downloadTracker.State } - }, - new GoToBeatmapButton(beatmapSet) - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - State = { BindTarget = downloadTracker.State } - } - } - } - }, - mainContent = new Container - { - Name = @"Main content", X = height - BeatmapCard.CORNER_RADIUS, - Height = height, - CornerRadius = BeatmapCard.CORNER_RADIUS, - Masking = true, + Width = width - height + BeatmapCard.CORNER_RADIUS, + FavouriteState = { BindTarget = favouriteState }, + ButtonsCollapsedWidth = BeatmapCard.CORNER_RADIUS, + ButtonsExpandedWidth = 30, + ButtonsPadding = new MarginPadding { Vertical = 35 }, Children = new Drawable[] { - mainContentBackground = new BeatmapCardContentBackground(beatmapSet) - { - RelativeSizeAxes = Axes.Both, - }, new FillFlowContainer { RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding - { - Horizontal = 10, - Vertical = 4 - }, Direction = FillDirection.Vertical, Children = new Drawable[] { @@ -248,11 +180,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards AutoSizeAxes = Axes.Y, Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, - Padding = new MarginPadding - { - Horizontal = 10, - Vertical = 4 - }, Children = new Drawable[] { idleBottomContent = new FillFlowContainer @@ -414,28 +341,13 @@ namespace osu.Game.Beatmaps.Drawables.Cards { bool showDetails = IsHovered || Expanded.Value; - float targetWidth = width - height; - if (showDetails) - targetWidth = targetWidth - icon_area_width + BeatmapCard.CORNER_RADIUS; - + buttonContainer.ShowDetails.Value = showDetails; thumbnail.Dimmed.Value = showDetails; // Scale value is intentionally chosen to fit in the spacing of listing displays, as to not overlap horizontally with adjacent cards. // This avoids depth issues where a hovered (scaled) card to the right of another card would be beneath the card to the left. content.ScaleTo(Expanded.Value ? 1.03f : 1, 500, Easing.OutQuint); - mainContent.ResizeWidthTo(targetWidth, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - mainContentBackground.Dimmed.Value = showDetails; - - rightAreaBackground.FadeColour(downloadTracker.State.Value == DownloadState.LocallyAvailable ? colours.Lime0 : colourProvider.Background3, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - rightAreaButtons.FadeTo(showDetails ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - - foreach (var button in rightAreaButtons) - { - button.IdleColour = downloadTracker.State.Value != DownloadState.LocallyAvailable ? colourProvider.Light1 : colourProvider.Background3; - button.HoverColour = downloadTracker.State.Value != DownloadState.LocallyAvailable ? colourProvider.Content1 : colourProvider.Foreground1; - } - bool showProgress = downloadTracker.State.Value == DownloadState.Downloading || downloadTracker.State.Value == DownloadState.Importing; idleBottomContent.FadeTo(showProgress ? 0 : 1, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); diff --git a/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs b/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs new file mode 100644 index 0000000000..3a2cb80a8d --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs @@ -0,0 +1,184 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps.Drawables.Cards.Buttons; +using osu.Game.Graphics; +using osu.Game.Online; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; + +namespace osu.Game.Beatmaps.Drawables.Cards +{ + public class CollapsibleButtonContainer : Container + { + public Bindable ShowDetails = new Bindable(); + public Bindable FavouriteState = new Bindable(); + + private readonly BeatmapDownloadTracker downloadTracker; + + private float buttonsExpandedWidth; + + public float ButtonsExpandedWidth + { + get => buttonsExpandedWidth; + set + { + buttonsExpandedWidth = value; + buttonArea.Width = value; + if (IsLoaded) + updateState(); + } + } + + private float buttonsCollapsedWidth; + + public float ButtonsCollapsedWidth + { + get => buttonsCollapsedWidth; + set + { + buttonsCollapsedWidth = value; + if (IsLoaded) + updateState(); + } + } + + public MarginPadding ButtonsPadding + { + get => buttons.Padding; + set => buttons.Padding = value; + } + + protected override Container Content => mainContent; + + private readonly Container background; + + private readonly Container buttonArea; + private readonly Container buttons; + + private readonly Container mainArea; + private readonly Container mainContent; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + [Resolved] + private OverlayColourProvider colourProvider { get; set; } = null!; + + public CollapsibleButtonContainer(APIBeatmapSet beatmapSet) + { + downloadTracker = new BeatmapDownloadTracker(beatmapSet); + + RelativeSizeAxes = Axes.Y; + Masking = true; + CornerRadius = BeatmapCard.CORNER_RADIUS; + + InternalChildren = new Drawable[] + { + downloadTracker, + background = new Container + { + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + // workaround for masking artifacts at the top & bottom of card, + // which become especially visible on downloaded beatmaps (when the icon area has a lime background). + Padding = new MarginPadding { Vertical = 1 }, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Colour4.White + }, + }, + buttonArea = new Container + { + Name = @"Right (button) area", + RelativeSizeAxes = Axes.Y, + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + Child = buttons = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new BeatmapCardIconButton[] + { + new FavouriteButton(beatmapSet) + { + Current = FavouriteState, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre + }, + new DownloadButton(beatmapSet) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + State = { BindTarget = downloadTracker.State } + }, + new GoToBeatmapButton(beatmapSet) + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + State = { BindTarget = downloadTracker.State } + } + } + } + }, + mainArea = new Container + { + Name = @"Main content", + RelativeSizeAxes = Axes.Y, + CornerRadius = BeatmapCard.CORNER_RADIUS, + Masking = true, + Children = new Drawable[] + { + new BeatmapCardContentBackground(beatmapSet) + { + RelativeSizeAxes = Axes.Both, + Dimmed = { BindTarget = ShowDetails } + }, + mainContent = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + Horizontal = 10, + Vertical = 4 + }, + } + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + downloadTracker.State.BindValueChanged(_ => updateState()); + ShowDetails.BindValueChanged(_ => updateState(), true); + FinishTransforms(true); + } + + private void updateState() + { + float targetWidth = Width - (ShowDetails.Value ? ButtonsExpandedWidth : ButtonsCollapsedWidth); + + mainArea.ResizeWidthTo(targetWidth, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + + background.FadeColour(downloadTracker.State.Value == DownloadState.LocallyAvailable ? colours.Lime0 : colourProvider.Background3, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + buttons.FadeTo(ShowDetails.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + + foreach (var button in buttons) + { + button.IdleColour = downloadTracker.State.Value != DownloadState.LocallyAvailable ? colourProvider.Light1 : colourProvider.Background3; + button.HoverColour = downloadTracker.State.Value != DownloadState.LocallyAvailable ? colourProvider.Content1 : colourProvider.Foreground1; + } + } + } +} From d6f60399342512250fb0c92b90d0c8e13d853eb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 17 Dec 2021 13:27:11 +0100 Subject: [PATCH 13/76] Extract base class for beatmap cards --- .../Beatmaps/Drawables/Cards/BeatmapCard.cs | 101 +++++----------- .../Drawables/Cards/BeatmapCardBase.cs | 80 +++++++++++++ .../Drawables/Cards/BeatmapCardContent.cs | 14 +-- .../Cards/BeatmapCardContentBackground.cs | 4 +- .../Cards/BeatmapCardDownloadProgressBar.cs | 4 +- .../Drawables/Cards/BeatmapCardExtra.cs | 108 ++++++------------ .../Drawables/Cards/BeatmapCardThumbnail.cs | 4 +- .../Cards/Buttons/BeatmapCardIconButton.cs | 2 +- .../Drawables/Cards/Buttons/DownloadButton.cs | 4 +- .../Cards/Buttons/GoToBeatmapButton.cs | 2 +- .../Drawables/Cards/Buttons/PlayButton.cs | 2 +- .../Cards/CollapsibleButtonContainer.cs | 10 +- 12 files changed, 168 insertions(+), 167 deletions(-) create mode 100644 osu.Game/Beatmaps/Drawables/Cards/BeatmapCardBase.cs diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index 13c4cfe207..b0df2e40d3 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -5,18 +5,14 @@ using System.Collections.Generic; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps.Drawables.Cards.Statistics; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet; @@ -25,21 +21,14 @@ using osu.Game.Resources.Localisation.Web; namespace osu.Game.Beatmaps.Drawables.Cards { - public class BeatmapCard : OsuClickableContainer + public class BeatmapCard : BeatmapCardBase { - public const float TRANSITION_DURATION = 400; - public const float CORNER_RADIUS = 10; - - public IBindable Expanded { get; } + protected override Drawable IdleContent => idleBottomContent; + protected override Drawable DownloadInProgressContent => downloadProgressBar; private const float width = 408; private const float height = 100; - private readonly APIBeatmapSet beatmapSet; - private readonly Bindable favouriteState; - - private readonly BeatmapDownloadTracker downloadTracker; - [Cached] private readonly BeatmapCardContent content; @@ -55,18 +44,13 @@ namespace osu.Game.Beatmaps.Drawables.Cards private OverlayColourProvider colourProvider { get; set; } = null!; public BeatmapCard(APIBeatmapSet beatmapSet, bool allowExpansion = true) - : base(HoverSampleSet.Submit) + : base(beatmapSet, allowExpansion) { - Expanded = new BindableBool { Disabled = !allowExpansion }; - - this.beatmapSet = beatmapSet; - favouriteState = new Bindable(new BeatmapSetFavouriteState(beatmapSet.HasFavourited, beatmapSet.FavouriteCount)); - downloadTracker = new BeatmapDownloadTracker(beatmapSet); content = new BeatmapCardContent(height); } - [BackgroundDependencyLoader(true)] - private void load(BeatmapSetOverlay? beatmapSetOverlay) + [BackgroundDependencyLoader] + private void load() { Width = width; Height = height; @@ -75,15 +59,14 @@ namespace osu.Game.Beatmaps.Drawables.Cards GridContainer titleContainer = null!; GridContainer artistContainer = null!; - InternalChild = content.With(c => + Child = content.With(c => { c.MainContent = new Container { RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - downloadTracker, - thumbnail = new BeatmapCardThumbnail(beatmapSet) + thumbnail = new BeatmapCardThumbnail(BeatmapSet) { Name = @"Left (icon) area", Size = new Vector2(height), @@ -96,11 +79,11 @@ namespace osu.Game.Beatmaps.Drawables.Cards Spacing = new Vector2(1) } }, - buttonContainer = new CollapsibleButtonContainer(beatmapSet) + buttonContainer = new CollapsibleButtonContainer(BeatmapSet) { X = height - CORNER_RADIUS, Width = width - height + CORNER_RADIUS, - FavouriteState = { BindTarget = favouriteState }, + FavouriteState = { BindTarget = FavouriteState }, ButtonsCollapsedWidth = CORNER_RADIUS, ButtonsExpandedWidth = 30, ButtonsPadding = new MarginPadding { Vertical = 17.5f }, @@ -131,7 +114,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { new OsuSpriteText { - Text = new RomanisableString(beatmapSet.TitleUnicode, beatmapSet.Title), + Text = new RomanisableString(BeatmapSet.TitleUnicode, BeatmapSet.Title), Font = OsuFont.Default.With(size: 22.5f, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, Truncate = true @@ -177,7 +160,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards d.AutoSizeAxes = Axes.Both; d.Margin = new MarginPadding { Top = 2 }; d.AddText("mapped by ", t => t.Colour = colourProvider.Content2); - d.AddUserLink(beatmapSet.Author); + d.AddUserLink(BeatmapSet.Author); }), } }, @@ -209,7 +192,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards AlwaysPresent = true, ChildrenEnumerable = createStatistics() }, - new BeatmapCardExtraInfoRow(beatmapSet) + new BeatmapCardExtraInfoRow(BeatmapSet) } }, downloadProgressBar = new BeatmapCardDownloadProgressBar @@ -218,8 +201,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards Height = 6, Anchor = Anchor.Centre, Origin = Anchor.Centre, - State = { BindTarget = downloadTracker.State }, - Progress = { BindTarget = downloadTracker.Progress } + State = { BindTarget = DownloadTracker.State }, + Progress = { BindTarget = DownloadTracker.Progress } } } } @@ -232,18 +215,18 @@ namespace osu.Game.Beatmaps.Drawables.Cards RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Horizontal = 10, Vertical = 13 }, - Child = new BeatmapCardDifficultyList(beatmapSet) + Child = new BeatmapCardDifficultyList(BeatmapSet) }; c.Expanded.BindTarget = Expanded; }); - if (beatmapSet.HasVideo) + if (BeatmapSet.HasVideo) leftIconArea.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) }); - if (beatmapSet.HasStoryboard) + if (BeatmapSet.HasStoryboard) leftIconArea.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) }); - if (beatmapSet.HasExplicitContent) + if (BeatmapSet.HasExplicitContent) { titleContainer.Content[0][1] = new ExplicitContentBeatmapPill { @@ -253,7 +236,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards }; } - if (beatmapSet.TrackId != null) + if (BeatmapSet.TrackId != null) { artistContainer.Content[0][1] = new FeaturedArtistBeatmapPill { @@ -262,57 +245,36 @@ namespace osu.Game.Beatmaps.Drawables.Cards Margin = new MarginPadding { Left = 5 } }; } - - Action = () => beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmapSet.OnlineID); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - downloadTracker.State.BindValueChanged(_ => updateState()); - Expanded.BindValueChanged(_ => updateState(), true); - FinishTransforms(true); - } - - protected override bool OnHover(HoverEvent e) - { - updateState(); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - updateState(); - base.OnHoverLost(e); } private LocalisableString createArtistText() { - var romanisableArtist = new RomanisableString(beatmapSet.ArtistUnicode, beatmapSet.Artist); + var romanisableArtist = new RomanisableString(BeatmapSet.ArtistUnicode, BeatmapSet.Artist); return BeatmapsetsStrings.ShowDetailsByArtist(romanisableArtist); } private IEnumerable createStatistics() { - var hypesStatistic = HypesStatistic.CreateFor(beatmapSet); + var hypesStatistic = HypesStatistic.CreateFor(BeatmapSet); if (hypesStatistic != null) yield return hypesStatistic; - var nominationsStatistic = NominationsStatistic.CreateFor(beatmapSet); + var nominationsStatistic = NominationsStatistic.CreateFor(BeatmapSet); if (nominationsStatistic != null) yield return nominationsStatistic; - yield return new FavouritesStatistic(beatmapSet) { Current = favouriteState }; - yield return new PlayCountStatistic(beatmapSet); + yield return new FavouritesStatistic(BeatmapSet) { Current = FavouriteState }; + yield return new PlayCountStatistic(BeatmapSet); - var dateStatistic = BeatmapCardDateStatistic.CreateFor(beatmapSet); + var dateStatistic = BeatmapCardDateStatistic.CreateFor(BeatmapSet); if (dateStatistic != null) yield return dateStatistic; } - private void updateState() + protected override void UpdateState() { + base.UpdateState(); + bool showDetails = IsHovered || Expanded.Value; buttonContainer.ShowDetails.Value = showDetails; @@ -323,11 +285,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards content.ScaleTo(Expanded.Value ? 1.03f : 1, 500, Easing.OutQuint); statisticsContainer.FadeTo(showDetails ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); - - bool showProgress = downloadTracker.State.Value == DownloadState.Downloading || downloadTracker.State.Value == DownloadState.Importing; - - idleBottomContent.FadeTo(showProgress ? 0 : 1, TRANSITION_DURATION, Easing.OutQuint); - downloadProgressBar.FadeTo(showProgress ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardBase.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardBase.cs new file mode 100644 index 0000000000..77df0809bc --- /dev/null +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardBase.cs @@ -0,0 +1,80 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Input.Events; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays; + +namespace osu.Game.Beatmaps.Drawables.Cards +{ + public abstract class BeatmapCardBase : OsuClickableContainer + { + public const float TRANSITION_DURATION = 400; + public const float CORNER_RADIUS = 10; + + public IBindable Expanded { get; } + + protected readonly APIBeatmapSet BeatmapSet; + protected readonly Bindable FavouriteState; + + protected abstract Drawable IdleContent { get; } + protected abstract Drawable DownloadInProgressContent { get; } + + protected readonly BeatmapDownloadTracker DownloadTracker; + + protected BeatmapCardBase(APIBeatmapSet beatmapSet, bool allowExpansion = true) + : base(HoverSampleSet.Submit) + { + Expanded = new BindableBool { Disabled = !allowExpansion }; + + BeatmapSet = beatmapSet; + FavouriteState = new Bindable(new BeatmapSetFavouriteState(beatmapSet.HasFavourited, beatmapSet.FavouriteCount)); + DownloadTracker = new BeatmapDownloadTracker(beatmapSet); + } + + [BackgroundDependencyLoader(true)] + private void load(BeatmapSetOverlay? beatmapSetOverlay) + { + Action = () => beatmapSetOverlay?.FetchAndShowBeatmapSet(BeatmapSet.OnlineID); + + AddInternal(DownloadTracker); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + DownloadTracker.State.BindValueChanged(_ => UpdateState()); + Expanded.BindValueChanged(_ => UpdateState(), true); + FinishTransforms(true); + } + + protected override bool OnHover(HoverEvent e) + { + UpdateState(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + UpdateState(); + base.OnHoverLost(e); + } + + protected virtual void UpdateState() + { + bool showProgress = DownloadTracker.State.Value == DownloadState.Downloading || DownloadTracker.State.Value == DownloadState.Importing; + + IdleContent.FadeTo(showProgress ? 0 : 1, TRANSITION_DURATION, Easing.OutQuint); + DownloadInProgressContent.FadeTo(showProgress ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); + } + } +} diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs index 286e03e700..e16421503c 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs @@ -54,7 +54,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, - CornerRadius = BeatmapCard.CORNER_RADIUS, + CornerRadius = BeatmapCardBase.CORNER_RADIUS, Masking = true, Unhovered = _ => updateFromHoverChange(), Children = new Drawable[] @@ -67,7 +67,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { RelativeSizeAxes = Axes.X, Height = height, - CornerRadius = BeatmapCard.CORNER_RADIUS, + CornerRadius = BeatmapCardBase.CORNER_RADIUS, Masking = true, }, dropdownContent = new HoverHandlingContainer @@ -91,7 +91,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards borderContainer = new Container { RelativeSizeAxes = Axes.Both, - CornerRadius = BeatmapCard.CORNER_RADIUS, + CornerRadius = BeatmapCardBase.CORNER_RADIUS, Masking = true, BorderThickness = 3, Child = new Box @@ -139,9 +139,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards private void updateState() { - background.FadeTo(Expanded.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - dropdownContent.FadeTo(Expanded.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - borderContainer.FadeTo(Expanded.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + background.FadeTo(Expanded.Value ? 1 : 0, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); + dropdownContent.FadeTo(Expanded.Value ? 1 : 0, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); + borderContainer.FadeTo(Expanded.Value ? 1 : 0, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); content.TweenEdgeEffectTo(new EdgeEffectParameters { @@ -150,7 +150,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards Radius = 10, Colour = Colour4.Black.Opacity(Expanded.Value ? 0.3f : 0f), Hollow = true, - }, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + }, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); } private class ExpandedContentScrollContainer : OsuScrollContainer diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContentBackground.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContentBackground.cs index 392f5d1bfa..6388e1698c 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContentBackground.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContentBackground.cs @@ -62,10 +62,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards private void updateState() => Schedule(() => { - background.FadeColour(Dimmed.Value ? colourProvider.Background4 : colourProvider.Background2, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + background.FadeColour(Dimmed.Value ? colourProvider.Background4 : colourProvider.Background2, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); var gradient = ColourInfo.GradientHorizontal(Colour4.White.Opacity(0), Colour4.White.Opacity(0.2f)); - cover.FadeColour(gradient, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + cover.FadeColour(gradient, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); }); } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs index ffb4e0c540..b80c5221ab 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardDownloadProgressBar.cs @@ -82,14 +82,14 @@ namespace osu.Game.Beatmaps.Drawables.Cards break; case DownloadState.Importing: - foregroundFill.FadeColour(colours.Yellow, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + foregroundFill.FadeColour(colours.Yellow, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); break; } } private void progressChanged() { - foreground.ResizeWidthTo((float)progress.Value, progress.Value > 0 ? BeatmapCard.TRANSITION_DURATION : 0, Easing.OutQuint); + foreground.ResizeWidthTo((float)progress.Value, progress.Value > 0 ? BeatmapCardBase.TRANSITION_DURATION : 0, Easing.OutQuint); } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs index fb498d643a..39acbb352d 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs @@ -4,18 +4,14 @@ #nullable enable using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input.Events; using osu.Framework.Localisation; using osu.Game.Beatmaps.Drawables.Cards.Statistics; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; -using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet; @@ -24,18 +20,14 @@ using osu.Game.Resources.Localisation.Web; namespace osu.Game.Beatmaps.Drawables.Cards { - public class BeatmapCardExtra : OsuClickableContainer + public class BeatmapCardExtra : BeatmapCardBase { + protected override Drawable IdleContent => idleBottomContent; + protected override Drawable DownloadInProgressContent => downloadProgressBar; + private const float width = 475; private const float height = 140; - public Bindable Expanded { get; } = new BindableBool(); - - private readonly APIBeatmapSet beatmapSet; - private readonly Bindable favouriteState; - - private readonly BeatmapDownloadTracker downloadTracker; - [Cached] private readonly BeatmapCardContent content; @@ -50,12 +42,9 @@ namespace osu.Game.Beatmaps.Drawables.Cards [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; - public BeatmapCardExtra(APIBeatmapSet beatmapSet) - : base(HoverSampleSet.Submit) + public BeatmapCardExtra(APIBeatmapSet beatmapSet, bool allowExpansion = true) + : base(beatmapSet, allowExpansion) { - this.beatmapSet = beatmapSet; - favouriteState = new Bindable(new BeatmapSetFavouriteState(beatmapSet.HasFavourited, beatmapSet.FavouriteCount)); - downloadTracker = new BeatmapDownloadTracker(beatmapSet); content = new BeatmapCardContent(height); } @@ -69,19 +58,18 @@ namespace osu.Game.Beatmaps.Drawables.Cards GridContainer titleContainer = null!; GridContainer artistContainer = null!; - InternalChild = content.With(c => + Child = content.With(c => { c.MainContent = new Container { RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - downloadTracker, - thumbnail = new BeatmapCardThumbnail(beatmapSet) + thumbnail = new BeatmapCardThumbnail(BeatmapSet) { Name = @"Left (icon) area", Size = new Vector2(height), - Padding = new MarginPadding { Right = BeatmapCard.CORNER_RADIUS }, + Padding = new MarginPadding { Right = CORNER_RADIUS }, Child = leftIconArea = new FillFlowContainer { Margin = new MarginPadding(5), @@ -90,12 +78,12 @@ namespace osu.Game.Beatmaps.Drawables.Cards Spacing = new Vector2(1) } }, - buttonContainer = new CollapsibleButtonContainer(beatmapSet) + buttonContainer = new CollapsibleButtonContainer(BeatmapSet) { - X = height - BeatmapCard.CORNER_RADIUS, - Width = width - height + BeatmapCard.CORNER_RADIUS, - FavouriteState = { BindTarget = favouriteState }, - ButtonsCollapsedWidth = BeatmapCard.CORNER_RADIUS, + X = height - CORNER_RADIUS, + Width = width - height + CORNER_RADIUS, + FavouriteState = { BindTarget = FavouriteState }, + ButtonsCollapsedWidth = CORNER_RADIUS, ButtonsExpandedWidth = 30, ButtonsPadding = new MarginPadding { Vertical = 35 }, Children = new Drawable[] @@ -125,7 +113,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { new OsuSpriteText { - Text = new RomanisableString(beatmapSet.TitleUnicode, beatmapSet.Title), + Text = new RomanisableString(BeatmapSet.TitleUnicode, BeatmapSet.Title), Font = OsuFont.Default.With(size: 22.5f, weight: FontWeight.SemiBold), RelativeSizeAxes = Axes.X, Truncate = true @@ -166,7 +154,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { RelativeSizeAxes = Axes.X, Truncate = true, - Text = beatmapSet.Source, + Text = BeatmapSet.Source, Shadow = false, Font = OsuFont.GetFont(size: 14, weight: FontWeight.SemiBold), Colour = colourProvider.Content2 @@ -200,7 +188,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards d.AutoSizeAxes = Axes.Both; d.Margin = new MarginPadding { Top = 2 }; d.AddText("mapped by ", t => t.Colour = colourProvider.Content2); - d.AddUserLink(beatmapSet.Author); + d.AddUserLink(BeatmapSet.Author); }), statisticsContainer = new GridContainer { @@ -223,7 +211,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards new Drawable[3] } }, - new BeatmapCardExtraInfoRow(beatmapSet) + new BeatmapCardExtraInfoRow(BeatmapSet) } }, downloadProgressBar = new BeatmapCardDownloadProgressBar @@ -232,8 +220,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards Height = 6, Anchor = Anchor.Centre, Origin = Anchor.Centre, - State = { BindTarget = downloadTracker.State }, - Progress = { BindTarget = downloadTracker.Progress } + State = { BindTarget = DownloadTracker.State }, + Progress = { BindTarget = DownloadTracker.Progress } } } } @@ -246,18 +234,18 @@ namespace osu.Game.Beatmaps.Drawables.Cards RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Horizontal = 10, Vertical = 13 }, - Child = new BeatmapCardDifficultyList(beatmapSet) + Child = new BeatmapCardDifficultyList(BeatmapSet) }; c.Expanded.BindTarget = Expanded; }); - if (beatmapSet.HasVideo) + if (BeatmapSet.HasVideo) leftIconArea.Add(new IconPill(FontAwesome.Solid.Film) { IconSize = new Vector2(20) }); - if (beatmapSet.HasStoryboard) + if (BeatmapSet.HasStoryboard) leftIconArea.Add(new IconPill(FontAwesome.Solid.Image) { IconSize = new Vector2(20) }); - if (beatmapSet.HasExplicitContent) + if (BeatmapSet.HasExplicitContent) { titleContainer.Content[0][1] = new ExplicitContentBeatmapPill { @@ -267,7 +255,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards }; } - if (beatmapSet.TrackId != null) + if (BeatmapSet.TrackId != null) { artistContainer.Content[0][1] = new FeaturedArtistBeatmapPill { @@ -279,33 +267,12 @@ namespace osu.Game.Beatmaps.Drawables.Cards createStatistics(); - Action = () => beatmapSetOverlay?.FetchAndShowBeatmapSet(beatmapSet.OnlineID); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - downloadTracker.State.BindValueChanged(_ => updateState()); - Expanded.BindValueChanged(_ => updateState(), true); - FinishTransforms(true); - } - - protected override bool OnHover(HoverEvent e) - { - updateState(); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - updateState(); - base.OnHoverLost(e); + Action = () => beatmapSetOverlay?.FetchAndShowBeatmapSet(BeatmapSet.OnlineID); } private LocalisableString createArtistText() { - var romanisableArtist = new RomanisableString(beatmapSet.ArtistUnicode, beatmapSet.Artist); + var romanisableArtist = new RomanisableString(BeatmapSet.ArtistUnicode, BeatmapSet.Artist); return BeatmapsetsStrings.ShowDetailsByArtist(romanisableArtist); } @@ -317,28 +284,30 @@ namespace osu.Game.Beatmaps.Drawables.Cards return original; } - statisticsContainer.Content[0][0] = withMargin(new FavouritesStatistic(beatmapSet) + statisticsContainer.Content[0][0] = withMargin(new FavouritesStatistic(BeatmapSet) { - Current = favouriteState, + Current = FavouriteState, }); - statisticsContainer.Content[1][0] = withMargin(new PlayCountStatistic(beatmapSet)); + statisticsContainer.Content[1][0] = withMargin(new PlayCountStatistic(BeatmapSet)); - var hypesStatistic = HypesStatistic.CreateFor(beatmapSet); + var hypesStatistic = HypesStatistic.CreateFor(BeatmapSet); if (hypesStatistic != null) statisticsContainer.Content[0][1] = withMargin(hypesStatistic); - var nominationsStatistic = NominationsStatistic.CreateFor(beatmapSet); + var nominationsStatistic = NominationsStatistic.CreateFor(BeatmapSet); if (nominationsStatistic != null) statisticsContainer.Content[1][1] = withMargin(nominationsStatistic); - var dateStatistic = BeatmapCardDateStatistic.CreateFor(beatmapSet); + var dateStatistic = BeatmapCardDateStatistic.CreateFor(BeatmapSet); if (dateStatistic != null) statisticsContainer.Content[0][2] = withMargin(dateStatistic); } - private void updateState() + protected override void UpdateState() { + base.UpdateState(); + bool showDetails = IsHovered || Expanded.Value; buttonContainer.ShowDetails.Value = showDetails; @@ -347,11 +316,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards // Scale value is intentionally chosen to fit in the spacing of listing displays, as to not overlap horizontally with adjacent cards. // This avoids depth issues where a hovered (scaled) card to the right of another card would be beneath the card to the left. content.ScaleTo(Expanded.Value ? 1.03f : 1, 500, Easing.OutQuint); - - bool showProgress = downloadTracker.State.Value == DownloadState.Downloading || downloadTracker.State.Value == DownloadState.Importing; - - idleBottomContent.FadeTo(showProgress ? 0 : 1, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - downloadProgressBar.FadeTo(showProgress ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs index f11a5916e1..e6b305552a 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardThumbnail.cs @@ -88,8 +88,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards { bool shouldDim = Dimmed.Value || playButton.Playing.Value; - playButton.FadeTo(shouldDim ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - cover.FadeColour(shouldDim ? OsuColour.Gray(0.2f) : Color4.White, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + playButton.FadeTo(shouldDim ? 1 : 0, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); + cover.FadeColour(shouldDim ? OsuColour.Gray(0.2f) : Color4.White, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs index e362e3abeb..02c9ea640a 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/BeatmapCardIconButton.cs @@ -115,7 +115,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons bool isHovered = IsHovered && Enabled.Value; content.ScaleTo(isHovered ? 1.2f : 1, 500, Easing.OutQuint); - content.FadeColour(isHovered ? HoverColour : IdleColour, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + content.FadeColour(isHovered ? HoverColour : IdleColour, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs index c94e335e8f..d7ba4af21e 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/DownloadButton.cs @@ -69,7 +69,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons case DownloadState.LocallyAvailable: Action = null; TooltipText = string.Empty; - this.FadeOut(BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + this.FadeOut(BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); break; case DownloadState.NotDownloaded: @@ -81,7 +81,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons } Action = () => beatmaps.Download(beatmapSet, preferNoVideo.Value); - this.FadeIn(BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + this.FadeIn(BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); spinner.Hide(); Icon.Show(); diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/GoToBeatmapButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/GoToBeatmapButton.cs index 9a6a3c01b7..b039eb6f10 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/GoToBeatmapButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/GoToBeatmapButton.cs @@ -43,7 +43,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons private void updateState() { - this.FadeTo(state.Value == DownloadState.LocallyAvailable ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + this.FadeTo(state.Value == DownloadState.LocallyAvailable ? 1 : 0, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); } } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs b/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs index f7bab26666..6d66d5001a 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/Buttons/PlayButton.cs @@ -141,7 +141,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Buttons private void toggleLoading(bool loading) { Enabled.Value = !loading; - icon.FadeTo(loading ? 0 : 1, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + icon.FadeTo(loading ? 0 : 1, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); loadingSpinner.State.Value = loading ? Visibility.Visible : Visibility.Hidden; } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs b/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs index 3a2cb80a8d..e1a630182f 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/CollapsibleButtonContainer.cs @@ -78,7 +78,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards RelativeSizeAxes = Axes.Y; Masking = true; - CornerRadius = BeatmapCard.CORNER_RADIUS; + CornerRadius = BeatmapCardBase.CORNER_RADIUS; InternalChildren = new Drawable[] { @@ -133,7 +133,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards { Name = @"Main content", RelativeSizeAxes = Axes.Y, - CornerRadius = BeatmapCard.CORNER_RADIUS, + CornerRadius = BeatmapCardBase.CORNER_RADIUS, Masking = true, Children = new Drawable[] { @@ -169,10 +169,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards { float targetWidth = Width - (ShowDetails.Value ? ButtonsExpandedWidth : ButtonsCollapsedWidth); - mainArea.ResizeWidthTo(targetWidth, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + mainArea.ResizeWidthTo(targetWidth, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); - background.FadeColour(downloadTracker.State.Value == DownloadState.LocallyAvailable ? colours.Lime0 : colourProvider.Background3, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); - buttons.FadeTo(ShowDetails.Value ? 1 : 0, BeatmapCard.TRANSITION_DURATION, Easing.OutQuint); + background.FadeColour(downloadTracker.State.Value == DownloadState.LocallyAvailable ? colours.Lime0 : colourProvider.Background3, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); + buttons.FadeTo(ShowDetails.Value ? 1 : 0, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); foreach (var button in buttons) { From 33e930f47786c5edad0d003fdee13aaf10f079cc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Fri, 17 Dec 2021 13:29:20 +0100 Subject: [PATCH 14/76] Move scale-on-expand logic to `BeatmapCardContent` --- osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs | 4 ---- osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs | 4 ++++ osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs | 4 ---- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs index b0df2e40d3..a3b4c4c86c 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCard.cs @@ -280,10 +280,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards buttonContainer.ShowDetails.Value = showDetails; thumbnail.Dimmed.Value = showDetails; - // Scale value is intentionally chosen to fit in the spacing of listing displays, as to not overlap horizontally with adjacent cards. - // This avoids depth issues where a hovered (scaled) card to the right of another card would be beneath the card to the left. - content.ScaleTo(Expanded.Value ? 1.03f : 1, 500, Easing.OutQuint); - statisticsContainer.FadeTo(showDetails ? 1 : 0, TRANSITION_DURATION, Easing.OutQuint); } } diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs index e16421503c..9c43c16100 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardContent.cs @@ -139,6 +139,10 @@ namespace osu.Game.Beatmaps.Drawables.Cards private void updateState() { + // Scale value is intentionally chosen to fit in the spacing of listing displays, as to not overlap horizontally with adjacent cards. + // This avoids depth issues where a hovered (scaled) card to the right of another card would be beneath the card to the left. + this.ScaleTo(Expanded.Value ? 1.03f : 1, 500, Easing.OutQuint); + background.FadeTo(Expanded.Value ? 1 : 0, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); dropdownContent.FadeTo(Expanded.Value ? 1 : 0, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); borderContainer.FadeTo(Expanded.Value ? 1 : 0, BeatmapCardBase.TRANSITION_DURATION, Easing.OutQuint); diff --git a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs index 39acbb352d..308cba9f0f 100644 --- a/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs +++ b/osu.Game/Beatmaps/Drawables/Cards/BeatmapCardExtra.cs @@ -312,10 +312,6 @@ namespace osu.Game.Beatmaps.Drawables.Cards buttonContainer.ShowDetails.Value = showDetails; thumbnail.Dimmed.Value = showDetails; - - // Scale value is intentionally chosen to fit in the spacing of listing displays, as to not overlap horizontally with adjacent cards. - // This avoids depth issues where a hovered (scaled) card to the right of another card would be beneath the card to the left. - content.ScaleTo(Expanded.Value ? 1.03f : 1, 500, Easing.OutQuint); } } } From 2f2006715eab49d36b0205ea72f6fa75f4d6c69f Mon Sep 17 00:00:00 2001 From: StanR Date: Fri, 17 Dec 2021 23:39:03 +0300 Subject: [PATCH 15/76] Slightly refactor difficulty and pp calculators --- .../Difficulty/CatchPerformanceCalculator.cs | 6 +++ .../Difficulty/ManiaDifficultyAttributes.cs | 4 +- .../Difficulty/ManiaPerformanceCalculator.cs | 39 ++++++++++--------- .../Difficulty/OsuDifficultyAttributes.cs | 28 ++++++------- .../Difficulty/OsuDifficultyCalculator.cs | 6 +-- .../Difficulty/OsuPerformanceCalculator.cs | 25 ++++++------ .../Difficulty/TaikoDifficultyAttributes.cs | 16 ++++---- .../Difficulty/TaikoDifficultyCalculator.cs | 6 +-- .../Difficulty/TaikoPerformanceCalculator.cs | 25 ++++++------ .../Difficulty/DifficultyAttributes.cs | 2 +- 10 files changed, 85 insertions(+), 72 deletions(-) diff --git a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs index 439890dac2..d3f3445ff1 100644 --- a/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Catch/Difficulty/CatchPerformanceCalculator.cs @@ -90,6 +90,12 @@ namespace osu.Game.Rulesets.Catch.Difficulty if (mods.Any(m => m is ModNoFail)) value *= 0.90; + if (categoryDifficulty != null) + { + categoryDifficulty.Add("AR", Attributes.ApproachRate); + categoryDifficulty.Add("Max Combo", Attributes.MaxCombo); + } + return value; } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs index bfdef893e9..979a04ddf8 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaDifficultyAttributes.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty yield return v; // Todo: osu!mania doesn't output MaxCombo attribute for some reason. - yield return (ATTRIB_ID_STRAIN, StarRating); + yield return (ATTRIB_ID_DIFFICULTY, StarRating); yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow); yield return (ATTRIB_ID_SCORE_MULTIPLIER, ScoreMultiplier); } @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty { base.FromDatabaseAttributes(values); - StarRating = values[ATTRIB_ID_STRAIN]; + StarRating = values[ATTRIB_ID_DIFFICULTY]; GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW]; ScoreMultiplier = values[ATTRIB_ID_SCORE_MULTIPLIER]; } diff --git a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs index b04ff3548f..6bf0a5acb4 100644 --- a/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Mania/Difficulty/ManiaPerformanceCalculator.cs @@ -61,48 +61,51 @@ namespace osu.Game.Rulesets.Mania.Difficulty if (mods.Any(m => m is ModEasy)) multiplier *= 0.5; - double strainValue = computeStrainValue(); - double accValue = computeAccuracyValue(strainValue); + double difficultyValue = computeDifficultyValue(); + double accValue = computeAccuracyValue(difficultyValue); double totalValue = Math.Pow( - Math.Pow(strainValue, 1.1) + + Math.Pow(difficultyValue, 1.1) + Math.Pow(accValue, 1.1), 1.0 / 1.1 ) * multiplier; if (categoryDifficulty != null) { - categoryDifficulty["Strain"] = strainValue; - categoryDifficulty["Accuracy"] = accValue; + categoryDifficulty.Add("Difficulty", difficultyValue); + categoryDifficulty.Add("Accuracy", accValue); + categoryDifficulty.Add("Scaled Score", scaledScore); + categoryDifficulty.Add("Great Hit Window", Attributes.GreatHitWindow); + categoryDifficulty.Add("Max Combo", Attributes.MaxCombo); } return totalValue; } - private double computeStrainValue() + private double computeDifficultyValue() { - // Obtain strain difficulty - double strainValue = Math.Pow(5 * Math.Max(1, Attributes.StarRating / 0.2) - 4.0, 2.2) / 135.0; + // Obtain difficulty + double difficultyValue = Math.Pow(5 * Math.Max(1, Attributes.StarRating / 0.2) - 4.0, 2.2) / 135.0; // Longer maps are worth more - strainValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0); + difficultyValue *= 1.0 + 0.1 * Math.Min(1.0, totalHits / 1500.0); if (scaledScore <= 500000) - strainValue = 0; + difficultyValue = 0; else if (scaledScore <= 600000) - strainValue *= (scaledScore - 500000) / 100000 * 0.3; + difficultyValue *= (scaledScore - 500000) / 100000 * 0.3; else if (scaledScore <= 700000) - strainValue *= 0.3 + (scaledScore - 600000) / 100000 * 0.25; + difficultyValue *= 0.3 + (scaledScore - 600000) / 100000 * 0.25; else if (scaledScore <= 800000) - strainValue *= 0.55 + (scaledScore - 700000) / 100000 * 0.20; + difficultyValue *= 0.55 + (scaledScore - 700000) / 100000 * 0.20; else if (scaledScore <= 900000) - strainValue *= 0.75 + (scaledScore - 800000) / 100000 * 0.15; + difficultyValue *= 0.75 + (scaledScore - 800000) / 100000 * 0.15; else - strainValue *= 0.90 + (scaledScore - 900000) / 100000 * 0.1; + difficultyValue *= 0.90 + (scaledScore - 900000) / 100000 * 0.1; - return strainValue; + return difficultyValue; } - private double computeAccuracyValue(double strainValue) + private double computeAccuracyValue(double difficultyValue) { if (Attributes.GreatHitWindow <= 0) return 0; @@ -110,7 +113,7 @@ namespace osu.Game.Rulesets.Mania.Difficulty // Lots of arbitrary values from testing. // Considering to use derivation from perfect accuracy in a probabilistic manner - assume normal distribution double accuracyValue = Math.Max(0.0, 0.2 - (Attributes.GreatHitWindow - 34) * 0.006667) - * strainValue + * difficultyValue * Math.Pow(Math.Max(0.0, scaledScore - 960000) / 40000, 1.1); // Bonus for many hitcircles - it's harder to keep good accuracy up for longer diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs index 4b2e54da17..128ff772fd 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyAttributes.cs @@ -12,14 +12,14 @@ namespace osu.Game.Rulesets.Osu.Difficulty { public class OsuDifficultyAttributes : DifficultyAttributes { - [JsonProperty("aim_strain")] - public double AimStrain { get; set; } + [JsonProperty("aim_difficulty")] + public double AimDifficulty { get; set; } - [JsonProperty("speed_strain")] - public double SpeedStrain { get; set; } + [JsonProperty("speed_difficulty")] + public double SpeedDifficulty { get; set; } - [JsonProperty("flashlight_rating")] - public double FlashlightRating { get; set; } + [JsonProperty("flashlight_difficulty")] + public double FlashlightDifficulty { get; set; } [JsonProperty("slider_factor")] public double SliderFactor { get; set; } @@ -43,15 +43,15 @@ namespace osu.Game.Rulesets.Osu.Difficulty foreach (var v in base.ToDatabaseAttributes()) yield return v; - yield return (ATTRIB_ID_AIM, AimStrain); - yield return (ATTRIB_ID_SPEED, SpeedStrain); + yield return (ATTRIB_ID_AIM, AimDifficulty); + yield return (ATTRIB_ID_SPEED, SpeedDifficulty); yield return (ATTRIB_ID_OVERALL_DIFFICULTY, OverallDifficulty); yield return (ATTRIB_ID_APPROACH_RATE, ApproachRate); yield return (ATTRIB_ID_MAX_COMBO, MaxCombo); - yield return (ATTRIB_ID_STRAIN, StarRating); + yield return (ATTRIB_ID_DIFFICULTY, StarRating); if (ShouldSerializeFlashlightRating()) - yield return (ATTRIB_ID_FLASHLIGHT, FlashlightRating); + yield return (ATTRIB_ID_FLASHLIGHT, FlashlightDifficulty); yield return (ATTRIB_ID_SLIDER_FACTOR, SliderFactor); } @@ -60,13 +60,13 @@ namespace osu.Game.Rulesets.Osu.Difficulty { base.FromDatabaseAttributes(values); - AimStrain = values[ATTRIB_ID_AIM]; - SpeedStrain = values[ATTRIB_ID_SPEED]; + AimDifficulty = values[ATTRIB_ID_AIM]; + SpeedDifficulty = values[ATTRIB_ID_SPEED]; OverallDifficulty = values[ATTRIB_ID_OVERALL_DIFFICULTY]; ApproachRate = values[ATTRIB_ID_APPROACH_RATE]; MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO]; - StarRating = values[ATTRIB_ID_STRAIN]; - FlashlightRating = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT); + StarRating = values[ATTRIB_ID_DIFFICULTY]; + FlashlightDifficulty = values.GetValueOrDefault(ATTRIB_ID_FLASHLIGHT); SliderFactor = values[ATTRIB_ID_SLIDER_FACTOR]; } diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs index ed42f333c0..c5b1baaad1 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuDifficultyCalculator.cs @@ -74,9 +74,9 @@ namespace osu.Game.Rulesets.Osu.Difficulty { StarRating = starRating, Mods = mods, - AimStrain = aimRating, - SpeedStrain = speedRating, - FlashlightRating = flashlightRating, + AimDifficulty = aimRating, + SpeedDifficulty = speedRating, + FlashlightDifficulty = flashlightRating, SliderFactor = sliderFactor, ApproachRate = preempt > 1200 ? (1800 - preempt) / 120 : (1200 - preempt) / 150 + 5, OverallDifficulty = (80 - hitWindowGreat) / 6, diff --git a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs index 8d45c7a8cc..96c92bdb0d 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/OsuPerformanceCalculator.cs @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty { } - public override double Calculate(Dictionary categoryRatings = null) + public override double Calculate(Dictionary categoryDifficulty = null) { mods = Score.Mods; accuracy = Score.Accuracy; @@ -72,15 +72,16 @@ namespace osu.Game.Rulesets.Osu.Difficulty Math.Pow(flashlightValue, 1.1), 1.0 / 1.1 ) * multiplier; - if (categoryRatings != null) + if (categoryDifficulty != null) { - categoryRatings.Add("Aim", aimValue); - categoryRatings.Add("Speed", speedValue); - categoryRatings.Add("Accuracy", accuracyValue); - categoryRatings.Add("Flashlight", flashlightValue); - categoryRatings.Add("OD", Attributes.OverallDifficulty); - categoryRatings.Add("AR", Attributes.ApproachRate); - categoryRatings.Add("Max Combo", Attributes.MaxCombo); + categoryDifficulty.Add("Aim", aimValue); + categoryDifficulty.Add("Speed", speedValue); + categoryDifficulty.Add("Accuracy", accuracyValue); + categoryDifficulty.Add("Flashlight", flashlightValue); + categoryDifficulty.Add("OD", Attributes.OverallDifficulty); + categoryDifficulty.Add("AR", Attributes.ApproachRate); + categoryDifficulty.Add("Max Combo", Attributes.MaxCombo); + categoryDifficulty.Add("Effective Miss Count", effectiveMissCount); } return totalValue; @@ -88,7 +89,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double computeAimValue() { - double rawAim = Attributes.AimStrain; + double rawAim = Attributes.AimDifficulty; if (mods.Any(m => m is OsuModTouchDevice)) rawAim = Math.Pow(rawAim, 0.8); @@ -144,7 +145,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty private double computeSpeedValue() { - double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedStrain / 0.0675) - 4.0, 3.0) / 100000.0; + double speedValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.SpeedDifficulty / 0.0675) - 4.0, 3.0) / 100000.0; // Longer maps are worth more. double lengthBonus = 0.95 + 0.4 * Math.Min(1.0, totalHits / 2000.0) + @@ -227,7 +228,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty if (!mods.Any(h => h is OsuModFlashlight)) return 0.0; - double rawFlashlight = Attributes.FlashlightRating; + double rawFlashlight = Attributes.FlashlightDifficulty; if (mods.Any(m => m is OsuModTouchDevice)) rawFlashlight = Math.Pow(rawFlashlight, 0.8); diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs index b2b5d056c3..31f5a6f570 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyAttributes.cs @@ -9,14 +9,14 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { public class TaikoDifficultyAttributes : DifficultyAttributes { - [JsonProperty("stamina_strain")] - public double StaminaStrain { get; set; } + [JsonProperty("stamina_difficulty")] + public double StaminaDifficulty { get; set; } - [JsonProperty("rhythm_strain")] - public double RhythmStrain { get; set; } + [JsonProperty("rhythm_difficulty")] + public double RhythmDifficulty { get; set; } - [JsonProperty("colour_strain")] - public double ColourStrain { get; set; } + [JsonProperty("colour_difficulty")] + public double ColourDifficulty { get; set; } [JsonProperty("approach_rate")] public double ApproachRate { get; set; } @@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty yield return v; yield return (ATTRIB_ID_MAX_COMBO, MaxCombo); - yield return (ATTRIB_ID_STRAIN, StarRating); + yield return (ATTRIB_ID_DIFFICULTY, StarRating); yield return (ATTRIB_ID_GREAT_HIT_WINDOW, GreatHitWindow); } @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Taiko.Difficulty base.FromDatabaseAttributes(values); MaxCombo = (int)values[ATTRIB_ID_MAX_COMBO]; - StarRating = values[ATTRIB_ID_STRAIN]; + StarRating = values[ATTRIB_ID_DIFFICULTY]; GreatHitWindow = values[ATTRIB_ID_GREAT_HIT_WINDOW]; } } diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs index e84bee3d28..6afdef3f3c 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoDifficultyCalculator.cs @@ -91,9 +91,9 @@ namespace osu.Game.Rulesets.Taiko.Difficulty { StarRating = starRating, Mods = mods, - StaminaStrain = staminaRating, - RhythmStrain = rhythmRating, - ColourStrain = colourRating, + StaminaDifficulty = staminaRating, + RhythmDifficulty = rhythmRating, + ColourDifficulty = colourRating, GreatHitWindow = hitWindows.WindowFor(HitResult.Great) / clockRate, MaxCombo = beatmap.HitObjects.Count(h => h is Hit), }; diff --git a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs index 90dd733dfd..d287998f3b 100644 --- a/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs +++ b/osu.Game.Rulesets.Taiko/Difficulty/TaikoPerformanceCalculator.cs @@ -44,43 +44,46 @@ namespace osu.Game.Rulesets.Taiko.Difficulty if (mods.Any(m => m is ModHidden)) multiplier *= 1.10; - double strainValue = computeStrainValue(); + double difficultyValue = computeDifficultyValue(); double accuracyValue = computeAccuracyValue(); double totalValue = Math.Pow( - Math.Pow(strainValue, 1.1) + + Math.Pow(difficultyValue, 1.1) + Math.Pow(accuracyValue, 1.1), 1.0 / 1.1 ) * multiplier; if (categoryDifficulty != null) { - categoryDifficulty["Strain"] = strainValue; - categoryDifficulty["Accuracy"] = accuracyValue; + categoryDifficulty.Add("Difficulty", difficultyValue); + categoryDifficulty.Add("Accuracy", accuracyValue); + categoryDifficulty.Add("AR", Attributes.ApproachRate); + categoryDifficulty.Add("Great Hit Window", Attributes.GreatHitWindow); + categoryDifficulty.Add("Max Combo", Attributes.MaxCombo); } return totalValue; } - private double computeStrainValue() + private double computeDifficultyValue() { - double strainValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0075) - 4.0, 2.0) / 100000.0; + double difficultyValue = Math.Pow(5.0 * Math.Max(1.0, Attributes.StarRating / 0.0075) - 4.0, 2.0) / 100000.0; // Longer maps are worth more double lengthBonus = 1 + 0.1 * Math.Min(1.0, totalHits / 1500.0); - strainValue *= lengthBonus; + difficultyValue *= lengthBonus; // Penalize misses exponentially. This mainly fixes tag4 maps and the likes until a per-hitobject solution is available - strainValue *= Math.Pow(0.985, countMiss); + difficultyValue *= Math.Pow(0.985, countMiss); if (mods.Any(m => m is ModHidden)) - strainValue *= 1.025; + difficultyValue *= 1.025; if (mods.Any(m => m is ModFlashlight)) // Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps. - strainValue *= 1.05 * lengthBonus; + difficultyValue *= 1.05 * lengthBonus; // Scale the speed value with accuracy _slightly_ - return strainValue * Score.Accuracy; + return difficultyValue * Score.Accuracy; } private double computeAccuracyValue() diff --git a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs index 47736ee033..991b567f57 100644 --- a/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs +++ b/osu.Game/Rulesets/Difficulty/DifficultyAttributes.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Difficulty protected const int ATTRIB_ID_OVERALL_DIFFICULTY = 5; protected const int ATTRIB_ID_APPROACH_RATE = 7; protected const int ATTRIB_ID_MAX_COMBO = 9; - protected const int ATTRIB_ID_STRAIN = 11; + protected const int ATTRIB_ID_DIFFICULTY = 11; protected const int ATTRIB_ID_GREAT_HIT_WINDOW = 13; protected const int ATTRIB_ID_SCORE_MULTIPLIER = 15; protected const int ATTRIB_ID_FLASHLIGHT = 17; From 6caa950c44aba65eb9aef5aa2895d79e6ee0ddda Mon Sep 17 00:00:00 2001 From: MBmasher Date: Mon, 20 Dec 2021 19:16:41 +1100 Subject: [PATCH 16/76] Rename `osuPrevious` to `osuLoop` --- .../Difficulty/Skills/Flashlight.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 9c539d5e4b..cb1ccf949e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -42,27 +42,27 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills for (int i = 0; i < Previous.Count; i++) { - var osuPrevious = (OsuDifficultyHitObject)Previous[i]; - var osuPreviousHitObject = (OsuHitObject)(osuPrevious.BaseObject); + var osuLoop = (OsuDifficultyHitObject)Previous[i]; + var osuLoopHitObject = (OsuHitObject)(osuLoop.BaseObject); - OsuDifficultyHitObject osuLastPrevious; + OsuDifficultyHitObject osuLoopNext; if (i == 0) - osuLastPrevious = osuCurrent; + osuLoopNext = osuCurrent; else - osuLastPrevious = (OsuDifficultyHitObject)Previous[i - 1]; + osuLoopNext = (OsuDifficultyHitObject)Previous[i - 1]; - if (!(osuPrevious.BaseObject is Spinner)) + if (!(osuLoop.BaseObject is Spinner)) { - double jumpDistance = (osuHitObject.StackedPosition - osuPreviousHitObject.EndPosition).Length; + double jumpDistance = (osuHitObject.StackedPosition - osuLoopHitObject.EndPosition).Length; - cumulativeStrainTime += osuLastPrevious.StrainTime; + cumulativeStrainTime += osuLoopNext.StrainTime; // We want to nerf objects that can be easily seen within the Flashlight circle radius. if (i == 0) smallDistNerf = Math.Min(1.0, jumpDistance / 75.0); // We also want to nerf stacks so that only the first object of the stack is accounted for. - double stackNerf = Math.Min(1.0, (osuPrevious.JumpDistance / scalingFactor) / 25.0); + double stackNerf = Math.Min(1.0, (osuLoop.JumpDistance / scalingFactor) / 25.0); result += Math.Pow(0.8, i) * stackNerf * scalingFactor * jumpDistance / cumulativeStrainTime; } From 090c3e84e7004e544f7c57374865c87ceac41109 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 20 Dec 2021 17:38:14 +0900 Subject: [PATCH 17/76] Avoid blocking windows key usage when the osu! window is not active As discussed in https://github.com/ppy/osu/discussions/16147. --- osu.Desktop/Windows/GameplayWinKeyBlocker.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs index dbfd170ea1..4acaf61cea 100644 --- a/osu.Desktop/Windows/GameplayWinKeyBlocker.cs +++ b/osu.Desktop/Windows/GameplayWinKeyBlocker.cs @@ -14,6 +14,7 @@ namespace osu.Desktop.Windows { private Bindable disableWinKey; private IBindable localUserPlaying; + private IBindable isActive; [Resolved] private GameHost host { get; set; } @@ -24,13 +25,16 @@ namespace osu.Desktop.Windows localUserPlaying = localUserInfo.IsPlaying.GetBoundCopy(); localUserPlaying.BindValueChanged(_ => updateBlocking()); + isActive = host.IsActive.GetBoundCopy(); + isActive.BindValueChanged(_ => updateBlocking()); + disableWinKey = config.GetBindable(OsuSetting.GameplayDisableWinKey); disableWinKey.BindValueChanged(_ => updateBlocking(), true); } private void updateBlocking() { - bool shouldDisable = disableWinKey.Value && localUserPlaying.Value; + bool shouldDisable = isActive.Value && disableWinKey.Value && localUserPlaying.Value; if (shouldDisable) host.InputThread.Scheduler.Add(WindowsKey.Disable); From a59583ee0901c3086059cd38b44ffcb86c7729b5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Dec 2021 13:18:02 +0100 Subject: [PATCH 18/76] Add extension method for returning next playlist item --- .../OnlinePlay/PlaylistExtensionsTest.cs | 72 +++++++++++++++++++ osu.Game/Online/Rooms/PlaylistExtensions.cs | 10 +++ 2 files changed, 82 insertions(+) create mode 100644 osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs diff --git a/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs b/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs new file mode 100644 index 0000000000..d2ee47bc23 --- /dev/null +++ b/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs @@ -0,0 +1,72 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Game.Online.Rooms; + +namespace osu.Game.Tests.OnlinePlay +{ + [TestFixture] + public class PlaylistExtensionsTest + { + [Test] + public void TestPlaylistItemsInOrder() + { + var items = new[] + { + new PlaylistItem { ID = 1, BeatmapID = 1001, PlaylistOrder = 1 }, + new PlaylistItem { ID = 2, BeatmapID = 1002, PlaylistOrder = 2 }, + new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 }, + }; + + var nextItem = items.GetNextItem(); + + Assert.That(nextItem, Is.EqualTo(items[0])); + } + + [Test] + public void TestPlaylistItemsOutOfOrder() + { + var items = new[] + { + new PlaylistItem { ID = 2, BeatmapID = 1002, PlaylistOrder = 2 }, + new PlaylistItem { ID = 1, BeatmapID = 1001, PlaylistOrder = 1 }, + new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 }, + }; + + var nextItem = items.GetNextItem(); + + Assert.That(nextItem, Is.EqualTo(items[1])); + } + + [Test] + public void TestExpiredPlaylistItemsSkipped() + { + var items = new[] + { + new PlaylistItem { ID = 2, BeatmapID = 1002, PlaylistOrder = 2, Expired = true }, + new PlaylistItem { ID = 1, BeatmapID = 1001, PlaylistOrder = 1, Expired = true }, + new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 }, + }; + + var nextItem = items.GetNextItem(); + + Assert.That(nextItem, Is.EqualTo(items[2])); + } + + [Test] + public void TestAllItemsExpired() + { + var items = new[] + { + new PlaylistItem { ID = 2, BeatmapID = 1002, PlaylistOrder = 2, Expired = true }, + new PlaylistItem { ID = 1, BeatmapID = 1001, PlaylistOrder = 1, Expired = true }, + new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3, Expired = true }, + }; + + var nextItem = items.GetNextItem(); + + Assert.That(nextItem, Is.Null); + } + } +} diff --git a/osu.Game/Online/Rooms/PlaylistExtensions.cs b/osu.Game/Online/Rooms/PlaylistExtensions.cs index 992011da3c..e900dc53ef 100644 --- a/osu.Game/Online/Rooms/PlaylistExtensions.cs +++ b/osu.Game/Online/Rooms/PlaylistExtensions.cs @@ -1,6 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +#nullable enable + +using System.Collections.Generic; using System.Linq; using Humanizer; using Humanizer.Localisation; @@ -10,6 +13,13 @@ namespace osu.Game.Online.Rooms { public static class PlaylistExtensions { + /// + /// Returns the next to be played from the supplied , + /// or if all items are expired. + /// + public static PlaylistItem? GetNextItem(this IEnumerable playlist) => + playlist.OrderBy(item => item.PlaylistOrder).FirstOrDefault(item => !item.Expired); + public static string GetTotalDuration(this BindableList playlist) => playlist.Select(p => p.Beatmap.Value.Length).Sum().Milliseconds().Humanize(minUnit: TimeUnit.Second, maxUnit: TimeUnit.Hour, precision: 2); } From a5a9922f81529f46d2efe46e11af0c6822512bf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Dec 2021 13:19:00 +0100 Subject: [PATCH 19/76] Fix lounge screen content not matching current room playlist item --- osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs | 8 ++++---- osu.Game/Online/Rooms/PlaylistExtensions.cs | 4 ++-- .../OnlinePlay/Components/OnlinePlayBackgroundSprite.cs | 4 ++-- .../Screens/OnlinePlay/Lounge/LoungeBackgroundScreen.cs | 3 +-- osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs | 5 ++--- 5 files changed, 11 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs b/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs index d2ee47bc23..001513be1f 100644 --- a/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs +++ b/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs @@ -19,7 +19,7 @@ namespace osu.Game.Tests.OnlinePlay new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 }, }; - var nextItem = items.GetNextItem(); + var nextItem = items.GetCurrentItem(); Assert.That(nextItem, Is.EqualTo(items[0])); } @@ -34,7 +34,7 @@ namespace osu.Game.Tests.OnlinePlay new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 }, }; - var nextItem = items.GetNextItem(); + var nextItem = items.GetCurrentItem(); Assert.That(nextItem, Is.EqualTo(items[1])); } @@ -49,7 +49,7 @@ namespace osu.Game.Tests.OnlinePlay new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 }, }; - var nextItem = items.GetNextItem(); + var nextItem = items.GetCurrentItem(); Assert.That(nextItem, Is.EqualTo(items[2])); } @@ -64,7 +64,7 @@ namespace osu.Game.Tests.OnlinePlay new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3, Expired = true }, }; - var nextItem = items.GetNextItem(); + var nextItem = items.GetCurrentItem(); Assert.That(nextItem, Is.Null); } diff --git a/osu.Game/Online/Rooms/PlaylistExtensions.cs b/osu.Game/Online/Rooms/PlaylistExtensions.cs index e900dc53ef..46cb218910 100644 --- a/osu.Game/Online/Rooms/PlaylistExtensions.cs +++ b/osu.Game/Online/Rooms/PlaylistExtensions.cs @@ -14,10 +14,10 @@ namespace osu.Game.Online.Rooms public static class PlaylistExtensions { /// - /// Returns the next to be played from the supplied , + /// Returns the first non-expired in playlist order from the supplied , /// or if all items are expired. /// - public static PlaylistItem? GetNextItem(this IEnumerable playlist) => + public static PlaylistItem? GetCurrentItem(this IEnumerable playlist) => playlist.OrderBy(item => item.PlaylistOrder).FirstOrDefault(item => !item.Expired); public static string GetTotalDuration(this BindableList playlist) => diff --git a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs index e2ba0b03b0..d144e1e3a9 100644 --- a/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs +++ b/osu.Game/Screens/OnlinePlay/Components/OnlinePlayBackgroundSprite.cs @@ -1,10 +1,10 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Beatmaps.Drawables; +using osu.Game.Online.Rooms; namespace osu.Game.Screens.OnlinePlay.Components { @@ -30,7 +30,7 @@ namespace osu.Game.Screens.OnlinePlay.Components private void updateBeatmap() { - sprite.Beatmap.Value = Playlist.FirstOrDefault()?.Beatmap.Value; + sprite.Beatmap.Value = Playlist.GetCurrentItem()?.Beatmap.Value; } protected virtual UpdateableBeatmapBackgroundSprite CreateBackgroundSprite() => new UpdateableBeatmapBackgroundSprite(BeatmapSetCoverType) { RelativeSizeAxes = Axes.Both }; diff --git a/osu.Game/Screens/OnlinePlay/Lounge/LoungeBackgroundScreen.cs b/osu.Game/Screens/OnlinePlay/Lounge/LoungeBackgroundScreen.cs index 6c00ca2e81..926c35c5da 100644 --- a/osu.Game/Screens/OnlinePlay/Lounge/LoungeBackgroundScreen.cs +++ b/osu.Game/Screens/OnlinePlay/Lounge/LoungeBackgroundScreen.cs @@ -3,7 +3,6 @@ #nullable enable -using System.Linq; using osu.Framework.Bindables; using osu.Framework.Screens; using osu.Game.Online.Rooms; @@ -20,7 +19,7 @@ namespace osu.Game.Screens.OnlinePlay.Lounge public LoungeBackgroundScreen() { SelectedRoom.BindValueChanged(onSelectedRoomChanged); - playlist.BindCollectionChanged((_, __) => PlaylistItem = playlist.FirstOrDefault()); + playlist.BindCollectionChanged((_, __) => PlaylistItem = playlist.GetCurrentItem()); } private void onSelectedRoomChanged(ValueChangedEvent room) diff --git a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs index 6b111d76a5..c833621fbc 100644 --- a/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs +++ b/osu.Game/Screens/OnlinePlay/OnlinePlayComposite.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics.Containers; @@ -73,7 +72,7 @@ namespace osu.Game.Screens.OnlinePlay private IBindable subScreenSelectedItem { get; set; } /// - /// The currently selected item in the , or the last item from + /// The currently selected item in the , or the current item from /// if this is not within a . /// protected readonly Bindable SelectedItem = new Bindable(); @@ -88,7 +87,7 @@ namespace osu.Game.Screens.OnlinePlay protected virtual void UpdateSelectedItem() => SelectedItem.Value = RoomID.Value == null || subScreenSelectedItem == null - ? Playlist.LastOrDefault() + ? Playlist.GetCurrentItem() : subScreenSelectedItem.Value; } } From 0975f570ba2bdf82dd4aee1bd36412b94cb0b31e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 20 Dec 2021 13:42:49 +0100 Subject: [PATCH 20/76] Return last playlist item if all expired --- .../OnlinePlay/PlaylistExtensionsTest.cs | 29 ++++++++++++++----- osu.Game/Online/Rooms/PlaylistExtensions.cs | 14 +++++++-- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs b/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs index 001513be1f..5913cb0f29 100644 --- a/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs +++ b/osu.Game.Tests/OnlinePlay/PlaylistExtensionsTest.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using NUnit.Framework; using osu.Game.Online.Rooms; @@ -9,6 +10,17 @@ namespace osu.Game.Tests.OnlinePlay [TestFixture] public class PlaylistExtensionsTest { + [Test] + public void TestEmpty() + { + // mostly an extreme edge case, i.e. during room creation. + var items = Array.Empty(); + + var currentItem = items.GetCurrentItem(); + + Assert.That(currentItem, Is.Null); + } + [Test] public void TestPlaylistItemsInOrder() { @@ -19,9 +31,9 @@ namespace osu.Game.Tests.OnlinePlay new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 }, }; - var nextItem = items.GetCurrentItem(); + var currentItem = items.GetCurrentItem(); - Assert.That(nextItem, Is.EqualTo(items[0])); + Assert.That(currentItem, Is.EqualTo(items[0])); } [Test] @@ -34,9 +46,9 @@ namespace osu.Game.Tests.OnlinePlay new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 }, }; - var nextItem = items.GetCurrentItem(); + var currentItem = items.GetCurrentItem(); - Assert.That(nextItem, Is.EqualTo(items[1])); + Assert.That(currentItem, Is.EqualTo(items[1])); } [Test] @@ -49,9 +61,9 @@ namespace osu.Game.Tests.OnlinePlay new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3 }, }; - var nextItem = items.GetCurrentItem(); + var currentItem = items.GetCurrentItem(); - Assert.That(nextItem, Is.EqualTo(items[2])); + Assert.That(currentItem, Is.EqualTo(items[2])); } [Test] @@ -64,9 +76,10 @@ namespace osu.Game.Tests.OnlinePlay new PlaylistItem { ID = 3, BeatmapID = 1003, PlaylistOrder = 3, Expired = true }, }; - var nextItem = items.GetCurrentItem(); + var currentItem = items.GetCurrentItem(); - Assert.That(nextItem, Is.Null); + // if all items are expired, the last-played item is expected to be returned. + Assert.That(currentItem, Is.EqualTo(items[2])); } } } diff --git a/osu.Game/Online/Rooms/PlaylistExtensions.cs b/osu.Game/Online/Rooms/PlaylistExtensions.cs index 46cb218910..355ae6188d 100644 --- a/osu.Game/Online/Rooms/PlaylistExtensions.cs +++ b/osu.Game/Online/Rooms/PlaylistExtensions.cs @@ -15,10 +15,18 @@ namespace osu.Game.Online.Rooms { /// /// Returns the first non-expired in playlist order from the supplied , - /// or if all items are expired. + /// or the last-played if all items are expired, + /// or if was empty. /// - public static PlaylistItem? GetCurrentItem(this IEnumerable playlist) => - playlist.OrderBy(item => item.PlaylistOrder).FirstOrDefault(item => !item.Expired); + public static PlaylistItem? GetCurrentItem(this ICollection playlist) + { + if (playlist.Count == 0) + return null; + + return playlist.All(item => item.Expired) + ? playlist.OrderByDescending(item => item.PlaylistOrder).First() + : playlist.OrderBy(item => item.PlaylistOrder).First(item => !item.Expired); + } public static string GetTotalDuration(this BindableList playlist) => playlist.Select(p => p.Beatmap.Value.Length).Sum().Milliseconds().Humanize(minUnit: TimeUnit.Second, maxUnit: TimeUnit.Hour, precision: 2); From 9344897542ff6b6463d8377d61f63b3ec0c8b5b3 Mon Sep 17 00:00:00 2001 From: dekrain Date: Mon, 20 Dec 2021 16:51:51 +0100 Subject: [PATCH 21/76] Split session statics reset method to prevent unloading seasonal backgrounds while idle --- osu.Game/Configuration/SessionStatics.cs | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/osu.Game/Configuration/SessionStatics.cs b/osu.Game/Configuration/SessionStatics.cs index ac94c39bd2..99f1e5a8b2 100644 --- a/osu.Game/Configuration/SessionStatics.cs +++ b/osu.Game/Configuration/SessionStatics.cs @@ -13,9 +13,7 @@ namespace osu.Game.Configuration /// public class SessionStatics : InMemoryConfigManager { - protected override void InitialiseDefaults() => ResetValues(); - - public void ResetValues() + protected override void InitialiseDefaults() { ensureDefault(SetDefault(Static.LoginOverlayDisplayed, false)); ensureDefault(SetDefault(Static.MutedAudioNotificationShownOnce, false)); @@ -24,6 +22,18 @@ namespace osu.Game.Configuration ensureDefault(SetDefault(Static.SeasonalBackgrounds, null)); } + /// + /// Used to revert statics to their defaults after being idle for appropiate amount of time. + /// Does not affect loaded seasonal backgrounds. + /// + public void ResetValues() + { + ensureDefault(GetBindable(Static.LoginOverlayDisplayed)); + ensureDefault(GetBindable(Static.MutedAudioNotificationShownOnce)); + ensureDefault(GetBindable(Static.LowBatteryNotificationShownOnce)); + ensureDefault(GetBindable(Static.LastHoverSoundPlaybackTime)); + } + private void ensureDefault(Bindable bindable) => bindable.SetDefault(); } From e21dbf10ff7734be7b0493d488f311e1a1657f1b Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 21 Dec 2021 12:25:32 +0900 Subject: [PATCH 22/76] Refactor further to remove indexing confusion --- .../Difficulty/Skills/Flashlight.cs | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index 21a2fc2252..ad7464244e 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -40,32 +40,33 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills double result = 0.0; + OsuDifficultyHitObject lastObj = null; + + // This is iterating backwards in time from the current object. for (int i = 0; i < Previous.Count; i++) { - var osuLoop = (OsuDifficultyHitObject)Previous[i]; - var osuLoopHitObject = (OsuHitObject)(osuLoop.BaseObject); + var currentObj = (OsuDifficultyHitObject)Previous[i]; + var currentHitObject = (OsuHitObject)(currentObj.BaseObject); - OsuDifficultyHitObject osuLoopNext; - if (i == 0) - osuLoopNext = osuCurrent; - else - osuLoopNext = (OsuDifficultyHitObject)Previous[i - 1]; + lastObj ??= currentObj; - if (!(osuLoop.BaseObject is Spinner)) + if (!(currentObj.BaseObject is Spinner)) { - double jumpDistance = (osuHitObject.StackedPosition - osuLoopHitObject.EndPosition).Length; + double jumpDistance = (osuHitObject.StackedPosition - currentHitObject.EndPosition).Length; - cumulativeStrainTime += osuLoopNext.StrainTime; + cumulativeStrainTime += lastObj.StrainTime; // We want to nerf objects that can be easily seen within the Flashlight circle radius. if (i == 0) smallDistNerf = Math.Min(1.0, jumpDistance / 75.0); // We also want to nerf stacks so that only the first object of the stack is accounted for. - double stackNerf = Math.Min(1.0, (osuLoop.LazyJumpDistance / scalingFactor) / 25.0); + double stackNerf = Math.Min(1.0, (currentObj.LazyJumpDistance / scalingFactor) / 25.0); result += Math.Pow(0.8, i) * stackNerf * scalingFactor * jumpDistance / cumulativeStrainTime; } + + lastObj = currentObj; } return Math.Pow(smallDistNerf * result, 2.0); From 28d6ff5d9c8bac9390e7576829623798934fc61a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Dec 2021 12:36:01 +0900 Subject: [PATCH 23/76] Fix potential wrong thread mutation in `ColourHitErrorMeter` --- .../Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs index 5012be7249..00e6059541 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs @@ -53,13 +53,13 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters LayoutEasing = Easing.OutQuint; } - public void Push(Color4 colour) + public void Push(Color4 colour) => Schedule(() => { Add(new HitErrorCircle(colour, drawable_judgement_size)); if (Children.Count > max_available_judgements) Children.FirstOrDefault(c => !c.IsRemoved)?.Remove(); - } + }); } internal class HitErrorCircle : Container From c21b2d166223d2305a0f3d7ce8d9fc77c3bc4a66 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 21 Dec 2021 12:39:07 +0900 Subject: [PATCH 24/76] Fix incorrect variable --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index ad7464244e..bdf7032521 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills var currentObj = (OsuDifficultyHitObject)Previous[i]; var currentHitObject = (OsuHitObject)(currentObj.BaseObject); - lastObj ??= currentObj; + lastObj ??= osuCurrent; if (!(currentObj.BaseObject is Spinner)) { From f366cdc73eabbaff9224e3c7010e3f641a079158 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Tue, 21 Dec 2021 12:39:34 +0900 Subject: [PATCH 25/76] Extract initial set out of loop --- osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs index bdf7032521..ff0780be85 100644 --- a/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs +++ b/osu.Game.Rulesets.Osu/Difficulty/Skills/Flashlight.cs @@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills double result = 0.0; - OsuDifficultyHitObject lastObj = null; + OsuDifficultyHitObject lastObj = osuCurrent; // This is iterating backwards in time from the current object. for (int i = 0; i < Previous.Count; i++) @@ -48,8 +48,6 @@ namespace osu.Game.Rulesets.Osu.Difficulty.Skills var currentObj = (OsuDifficultyHitObject)Previous[i]; var currentHitObject = (OsuHitObject)(currentObj.BaseObject); - lastObj ??= osuCurrent; - if (!(currentObj.BaseObject is Spinner)) { double jumpDistance = (osuHitObject.StackedPosition - currentHitObject.EndPosition).Length; From 52db7b36fc44a66d64a5b68d5a266ece48a2cebc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Dec 2021 12:55:21 +0900 Subject: [PATCH 26/76] Move `Schedule` call to base implementation of error meter for extra safety --- .../Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs | 4 ++-- osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs | 7 ++++++- 2 files changed, 8 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs index 00e6059541..5012be7249 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/ColourHitErrorMeter.cs @@ -53,13 +53,13 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters LayoutEasing = Easing.OutQuint; } - public void Push(Color4 colour) => Schedule(() => + public void Push(Color4 colour) { Add(new HitErrorCircle(colour, drawable_judgement_size)); if (Children.Count > max_available_judgements) Children.FirstOrDefault(c => !c.IsRemoved)?.Remove(); - }); + } } internal class HitErrorCircle : Container diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs index c7b06a3a2c..16c23f07f5 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs @@ -40,9 +40,14 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters if (gameplayClockContainer != null) gameplayClockContainer.OnSeek += Clear; - processor.NewJudgement += OnNewJudgement; + // Scheduled as meter implementations are likely going to change/add drawables when reacting to this. + processor.NewJudgement += j => Schedule(() => OnNewJudgement(j)); } + /// + /// Fired when a new judgement arrives. + /// + /// The new judgement. protected abstract void OnNewJudgement(JudgementResult judgement); protected Color4 GetColourForHitResult(HitResult result) From edcbd4de6d27cd9788523064207e4111d2d99f80 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Dec 2021 13:05:26 +0900 Subject: [PATCH 27/76] Fix incorrect event unbind logic --- osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs index 16c23f07f5..1f08cb8aa7 100644 --- a/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs +++ b/osu.Game/Screens/Play/HUD/HitErrorMeters/HitErrorMeter.cs @@ -40,10 +40,12 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters if (gameplayClockContainer != null) gameplayClockContainer.OnSeek += Clear; - // Scheduled as meter implementations are likely going to change/add drawables when reacting to this. - processor.NewJudgement += j => Schedule(() => OnNewJudgement(j)); + processor.NewJudgement += processorNewJudgement; } + // Scheduled as meter implementations are likely going to change/add drawables when reacting to this. + private void processorNewJudgement(JudgementResult j) => Schedule(() => OnNewJudgement(j)); + /// /// Fired when a new judgement arrives. /// @@ -89,7 +91,7 @@ namespace osu.Game.Screens.Play.HUD.HitErrorMeters base.Dispose(isDisposing); if (processor != null) - processor.NewJudgement -= OnNewJudgement; + processor.NewJudgement -= processorNewJudgement; if (gameplayClockContainer != null) gameplayClockContainer.OnSeek -= Clear; From 9aff646ff49ac0194b194621b9c90e1af9ab2cc9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 21 Dec 2021 13:20:12 +0900 Subject: [PATCH 28/76] Centralise all multiplayer button clicking test logic This adds the "wait for enabled" check in a way that can be easily reused, as it keeps getting missed in test implementations. This particular commit hopefully fixes https://github.com/ppy/osu/runs/4583845033?check_suite_focus=true. --- .../Visual/Multiplayer/QueueModeTestScene.cs | 23 ++----------- .../Multiplayer/TestSceneMultiplayer.cs | 18 ++-------- .../TestSceneMultiplayerMatchSubScreen.cs | 33 ++++--------------- .../TestSceneMultiplayerReadyButton.cs | 28 +++++----------- .../TestSceneMultiplayerSpectateButton.cs | 17 +++------- .../Visual/OsuManualInputManagerTestScene.cs | 23 +++++++++++++ 6 files changed, 48 insertions(+), 94 deletions(-) diff --git a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs index 5acb44ac45..88c54eb2bb 100644 --- a/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs +++ b/osu.Game.Tests/Visual/Multiplayer/QueueModeTestScene.cs @@ -5,7 +5,6 @@ using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Testing; @@ -20,7 +19,6 @@ using osu.Game.Screens.OnlinePlay.Multiplayer; using osu.Game.Screens.OnlinePlay.Multiplayer.Match; using osu.Game.Screens.Play; using osu.Game.Tests.Resources; -using osuTK.Input; namespace osu.Game.Tests.Visual.Multiplayer { @@ -86,11 +84,7 @@ namespace osu.Game.Tests.Visual.Multiplayer AddUntilStep("wait for room open", () => this.ChildrenOfType().FirstOrDefault()?.IsLoaded == true); AddWaitStep("wait for transition", 2); - AddStep("create room", () => - { - InputManager.MoveMouseTo(this.ChildrenOfType().Single()); - InputManager.Click(MouseButton.Left); - }); + ClickButtonWhenEnabled(); AddUntilStep("wait for join", () => Client.RoomJoined); } @@ -104,24 +98,13 @@ namespace osu.Game.Tests.Visual.Multiplayer protected void RunGameplay() { AddUntilStep("wait for idle", () => Client.LocalUser?.State == MultiplayerUserState.Idle); - clickReadyButton(); + ClickButtonWhenEnabled(); AddUntilStep("wait for ready", () => Client.LocalUser?.State == MultiplayerUserState.Ready); - clickReadyButton(); + ClickButtonWhenEnabled(); AddUntilStep("wait for player", () => multiplayerComponents.CurrentScreen is Player player && player.IsLoaded); AddStep("exit player", () => multiplayerComponents.MultiplayerScreen.MakeCurrent()); } - - private void clickReadyButton() - { - AddUntilStep("wait for ready button to be enabled", () => this.ChildrenOfType().Single().ChildrenOfType