diff --git a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs index 67a4cf6890..9eda065495 100644 --- a/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuAnimatedButton.cs @@ -93,7 +93,8 @@ namespace osu.Game.Graphics.UserInterface base.LoadComplete(); Colour = dimColour; - Enabled.BindValueChanged(_ => this.FadeColour(dimColour, 200, Easing.OutQuint)); + Enabled.BindValueChanged(_ => this.FadeColour(dimColour, 200, Easing.OutQuint), true); + FinishTransforms(true); } private Color4 dimColour => Enabled.Value ? Color4.White : colours.Gray9; diff --git a/osu.Game/Screens/SelectV2/PanelBeatmapSet.SpreadDisplay.cs b/osu.Game/Screens/SelectV2/PanelBeatmapSet.SpreadDisplay.cs new file mode 100644 index 0000000000..177d7c7162 --- /dev/null +++ b/osu.Game/Screens/SelectV2/PanelBeatmapSet.SpreadDisplay.cs @@ -0,0 +1,204 @@ +// 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.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.Game.Beatmaps; +using osu.Game.Configuration; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; +using osuTK; + +namespace osu.Game.Screens.SelectV2 +{ + public partial class PanelBeatmapSet + { + public partial class SpreadDisplay : OsuAnimatedButton + { + public Bindable BeatmapSet { get; } = new Bindable(); + public BindableBool Expanded { get; } = new BindableBool(); + + private readonly Bindable scopedBeatmapSet = new Bindable(); + private readonly Bindable showConvertedBeatmaps = new Bindable(); + + private const double transition_duration = 200; + + [Resolved] + private Bindable ruleset { get; set; } = null!; + + [Resolved] + private OsuColour colours { get; set; } = null!; + + private FillFlowContainer flow = null!; + private OsuSpriteText countText = null!; // TODO + private SpriteIcon icon = null!; + + public SpreadDisplay() + { + AutoSizeAxes = Axes.X; + Height = 14; + Content.CornerRadius = 5; + } + + [BackgroundDependencyLoader] + private void load(ISongSelect? songSelect, OsuConfigManager configManager) + { + Add(new FillFlowContainer + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(5), + Padding = new MarginPadding { Horizontal = 5 }, + Children = new Drawable[] + { + flow = new FillFlowContainer + { + AutoSizeAxes = Axes.X, + RelativeSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(2), + }, + countText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Font = OsuFont.Style.Caption2, + }, + icon = new SpriteIcon + { + Size = new Vector2(12), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Solid.Eye, + Alpha = 0, + } + } + }); + + if (songSelect != null) + scopedBeatmapSet.BindTo(songSelect.ScopedBeatmapSet); + + configManager.BindWith(OsuSetting.ShowConvertedBeatmaps, showConvertedBeatmaps); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + BeatmapSet.BindValueChanged(_ => updateBeatmapSet()); + showConvertedBeatmaps.BindValueChanged(_ => updateBeatmapSet(), true); + Expanded.BindValueChanged(_ => updateEnabled()); + scopedBeatmapSet.BindValueChanged(_ => updateEnabled(), true); + Enabled.BindValueChanged(_ => updateAppearance(), true); + FinishTransforms(true); + } + + private void updateBeatmapSet() + { + if (BeatmapSet.Value == null) + { + this.FadeOut(transition_duration, Easing.OutQuint); + return; + } + + flow.Clear(); + + var starDifficulties = BeatmapSet.Value.Beatmaps + .Where(b => b.AllowGameplayWithRuleset(ruleset.Value, showConvertedBeatmaps.Value)) + .OrderBy(b => b.Ruleset.OnlineID) + .ThenBy(b => b.StarRating) + .Select(b => b.StarRating) + .ToList(); + this.FadeTo(starDifficulties.Count > 0 ? 1 : 0, transition_duration, Easing.OutQuint); + + if (starDifficulties.Count == 0) + return; + + // TODO: figure overflow later + + foreach (double starDifficulty in starDifficulties) + { + var circle = new Circle + { + Size = new Vector2(5, 10), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Colour = colours.ForStarDifficulty(starDifficulty) + }; + flow.Add(circle); + flow.SetLayoutPosition(circle, (float)starDifficulty); + } + + Action = () => scopedBeatmapSet.Value = BeatmapSet.Value; + updateEnabled(); + } + + private void updateEnabled() + { + Enabled.Value = Expanded.Value && scopedBeatmapSet.Value == null; + } + + protected override bool OnMouseDown(MouseDownEvent e) + { + if (!Enabled.Value) + return false; + + base.OnMouseDown(e); + return true; + } + + protected override bool OnClick(ClickEvent e) + { + if (!Enabled.Value) + return false; + + // this is a crude workaround with an issue with `OsuAnimatedButton` that isn't easily fixable. + // the issue is that when wanting to turn off the hover layer upon click, `HoverColour` can be set to a transparent colour, + // *but* this has to happen *before* `base.OnClick()`. + // this is because `base.OnClick()` uses `FlashColour()` to flash the button on click, + // but that `FlashColour()` call implicitly copies `hoverColour` *at the point of call* into the transform that ends the flash. + updateAppearance(false); + return base.OnClick(e); + } + + protected override bool OnHover(HoverEvent e) + { + updateAppearance(); + + if (!Enabled.Value) + return false; + + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateAppearance(); + + if (!Enabled.Value) + return; + + base.OnHoverLost(e); + } + + private void updateAppearance(bool? isInteractable = null) + { + isInteractable ??= Enabled.Value && IsHovered; + + HoverColour = isInteractable.Value ? Colour4.White.Opacity(0.1f) : Colour4.Transparent; + icon.FadeTo(isInteractable.Value ? 1 : 0, transition_duration, Easing.OutQuint); + } + } + } +} diff --git a/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs b/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs index befdba1b2b..95262e16e1 100644 --- a/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs +++ b/osu.Game/Screens/SelectV2/PanelBeatmapSet.cs @@ -47,7 +47,7 @@ namespace osu.Game.Screens.SelectV2 private Drawable chevronIcon = null!; private PanelUpdateBeatmapButton updateButton = null!; private BeatmapSetOnlineStatusPill statusPill = null!; - private DifficultySpectrumDisplay difficultiesDisplay = null!; + private SpreadDisplay spreadDisplay = null!; [Resolved] private OverlayColourProvider colourProvider { get; set; } = null!; @@ -151,10 +151,11 @@ namespace osu.Game.Screens.SelectV2 Origin = Anchor.CentreLeft, Margin = new MarginPadding { Right = 5f, Top = -2f }, }, - difficultiesDisplay = new DifficultySpectrumDisplay + spreadDisplay = new SpreadDisplay { - Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, + Anchor = Anchor.CentreLeft, + Expanded = { BindTarget = Expanded } }, }, } @@ -200,7 +201,7 @@ namespace osu.Game.Screens.SelectV2 artistText.Text = new RomanisableString(beatmapSet.Metadata.ArtistUnicode, beatmapSet.Metadata.Artist); updateButton.BeatmapSet = beatmapSet; statusPill.Status = beatmapSet.Status; - difficultiesDisplay.BeatmapSet = beatmapSet; + spreadDisplay.BeatmapSet.Value = beatmapSet; } protected override void FreeAfterUse() @@ -211,7 +212,7 @@ namespace osu.Game.Screens.SelectV2 scheduledBackgroundRetrieval = null; setBackground.Beatmap = null; updateButton.BeatmapSet = null; - difficultiesDisplay.BeatmapSet = null; + spreadDisplay.BeatmapSet.Value = null; } [Resolved]