diff --git a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs index 8c1378cae4..11cc6122ac 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs @@ -17,7 +17,7 @@ namespace osu.Game.Beatmaps.Drawables /// /// Fires when one of our difficulties was selected. Will fire on first expand. /// - public Action SelectionChanged; + public Action SelectionChanged; /// /// Fires when one of our difficulties is clicked when already selected. Should start playing the map. @@ -89,8 +89,6 @@ namespace osu.Game.Beatmaps.Drawables //we want to make sure one of our children is selected in the case none have been selected yet. if (SelectedPanel == null) BeatmapPanels.First().State = PanelSelectedState.Selected; - else - SelectionChanged?.Invoke(this, SelectedPanel.Beatmap); } private void panelGainedSelection(BeatmapPanel panel) @@ -106,7 +104,7 @@ namespace osu.Game.Beatmaps.Drawables finally { State = BeatmapGroupState.Expanded; - SelectionChanged?.Invoke(this, panel.Beatmap); + SelectionChanged?.Invoke(this, SelectedPanel); } } } diff --git a/osu.Game/Beatmaps/WorkingBeatmap.cs b/osu.Game/Beatmaps/WorkingBeatmap.cs index 616128dab5..589557b088 100644 --- a/osu.Game/Beatmaps/WorkingBeatmap.cs +++ b/osu.Game/Beatmaps/WorkingBeatmap.cs @@ -94,6 +94,9 @@ namespace osu.Game.Beatmaps { if (track != null && BeatmapInfo.AudioEquals(other.BeatmapInfo)) other.track = track; + + if (background != null && BeatmapInfo.BackgroundEquals(other.BeatmapInfo)) + other.background = background; } public virtual void Dispose() diff --git a/osu.Game/Database/BeatmapInfo.cs b/osu.Game/Database/BeatmapInfo.cs index 5097622deb..c2e35d64a8 100644 --- a/osu.Game/Database/BeatmapInfo.cs +++ b/osu.Game/Database/BeatmapInfo.cs @@ -93,5 +93,9 @@ namespace osu.Game.Database public bool AudioEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null && BeatmapSet.Path == other.BeatmapSet.Path && (Metadata ?? BeatmapSet.Metadata).AudioFile == (other.Metadata ?? other.BeatmapSet.Metadata).AudioFile; + + public bool BackgroundEquals(BeatmapInfo other) => other != null && BeatmapSet != null && other.BeatmapSet != null && + BeatmapSet.Path == other.BeatmapSet.Path && + (Metadata ?? BeatmapSet.Metadata).BackgroundFile == (other.Metadata ?? other.BeatmapSet.Metadata).BackgroundFile; } } diff --git a/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs index 43e14e59de..a529dde592 100644 --- a/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs +++ b/osu.Game/Online/API/Requests/GetBeatmapDetailsRequest.cs @@ -6,7 +6,7 @@ using osu.Game.Database; namespace osu.Game.Online.API.Requests { - public class GetBeatmapDetailsRequest : APIRequest + public class GetBeatmapDetailsRequest : APIRequest { private readonly BeatmapInfo beatmap; @@ -20,7 +20,7 @@ namespace osu.Game.Online.API.Requests protected override string Target => $@"beatmaps/{lookupString}"; } - public class GetBeatmapDeatilsResponse : BeatmapMetrics + public class GetBeatmapDetailsResponse : BeatmapMetrics { //the online API returns some metrics as a nested object. [JsonProperty(@"failtimes")] diff --git a/osu.Game/Screens/Play/PlayerLoader.cs b/osu.Game/Screens/Play/PlayerLoader.cs index 765abd9873..9afa1018c6 100644 --- a/osu.Game/Screens/Play/PlayerLoader.cs +++ b/osu.Game/Screens/Play/PlayerLoader.cs @@ -14,6 +14,7 @@ using osu.Game.Graphics.Sprites; using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Menu; using OpenTK; +using osu.Framework.Localisation; namespace osu.Game.Screens.Play { @@ -174,10 +175,17 @@ namespace osu.Game.Screens.Play } }; } - } + private readonly WorkingBeatmap beatmap; + public BeatmapMetadataDisplay(WorkingBeatmap beatmap) + { + this.beatmap = beatmap; + } + + [BackgroundDependencyLoader] + private void load(LocalisationEngine localisation) { var metadata = beatmap?.BeatmapInfo?.Metadata ?? new BeatmapMetadata(); @@ -194,7 +202,7 @@ namespace osu.Game.Screens.Play { new OsuSpriteText { - Text = metadata.Title, + Current = localisation.GetUnicodePreference(metadata.TitleUnicode, metadata.Title), TextSize = 36, Font = @"Exo2.0-MediumItalic", Origin = Anchor.TopCentre, @@ -202,7 +210,7 @@ namespace osu.Game.Screens.Play }, new OsuSpriteText { - Text = metadata.Artist, + Current = localisation.GetUnicodePreference(metadata.ArtistUnicode, metadata.Artist), TextSize = 26, Font = @"Exo2.0-MediumItalic", Origin = Anchor.TopCentre, diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 06aaea041a..d05dd43b63 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -120,7 +120,7 @@ namespace osu.Game.Screens.Select public void RemoveBeatmap(BeatmapSetInfo info) => removeGroup(groups.Find(b => b.BeatmapSet.ID == info.ID)); - public Action SelectionChanged; + public Action SelectionChanged; public Action StartRequested; @@ -230,7 +230,7 @@ namespace osu.Game.Screens.Select return new BeatmapGroup(beatmapSet, database) { - SelectionChanged = SelectionChanged, + SelectionChanged = (g, p) => selectGroup(g, p), StartRequested = b => StartRequested?.Invoke(), State = BeatmapGroupState.Collapsed }; @@ -328,21 +328,33 @@ namespace osu.Game.Screens.Select private void selectGroup(BeatmapGroup group, BeatmapPanel panel = null, bool animated = true) { - if (panel == null) - panel = group.BeatmapPanels.First(); + try + { + if (panel == null) + panel = group.BeatmapPanels.First(); - Trace.Assert(group.BeatmapPanels.Contains(panel), @"Selected panel must be in provided group"); + if (selectedPanel == panel) return; - if (selectedGroup != null && selectedGroup != group && selectedGroup.State != BeatmapGroupState.Hidden) - selectedGroup.State = BeatmapGroupState.Collapsed; + Trace.Assert(group.BeatmapPanels.Contains(panel), @"Selected panel must be in provided group"); - group.State = BeatmapGroupState.Expanded; - selectedGroup = group; - panel.State = PanelSelectedState.Selected; - selectedPanel = panel; + if (selectedGroup != null && selectedGroup != group && selectedGroup.State != BeatmapGroupState.Hidden) + selectedGroup.State = BeatmapGroupState.Collapsed; - float selectedY = computeYPositions(animated); - ScrollTo(selectedY, animated); + group.State = BeatmapGroupState.Expanded; + panel.State = PanelSelectedState.Selected; + + if (selectedPanel == panel) return; + + selectedPanel = panel; + selectedGroup = group; + + SelectionChanged?.Invoke(panel.Beatmap); + } + finally + { + float selectedY = computeYPositions(animated); + ScrollTo(selectedY, animated); + } } protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index 61c1f0cc33..44f2aeb0da 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -74,156 +74,21 @@ namespace osu.Game.Screens.Select } State = Visibility.Visible; - var lastContainer = beatmapInfoContainer; - - float newDepth = lastContainer?.Depth + 1 ?? 0; - - BeatmapInfo beatmapInfo = beatmap.BeatmapInfo; - BeatmapMetadata metadata = beatmap.BeatmapInfo?.Metadata ?? beatmap.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); - - List labels = new List(); - - if (beatmap.Beatmap != null) - { - HitObject lastObject = beatmap.Beatmap.HitObjects.LastOrDefault(); - double endTime = (lastObject as IHasEndTime)?.EndTime ?? lastObject?.StartTime ?? 0; - - labels.Add(new InfoLabel(new BeatmapStatistic - { - Name = "Length", - Icon = FontAwesome.fa_clock_o, - Content = beatmap.Beatmap.HitObjects.Count == 0 ? "-" : TimeSpan.FromMilliseconds(endTime - beatmap.Beatmap.HitObjects.First().StartTime).ToString(@"m\:ss"), - })); - - labels.Add(new InfoLabel(new BeatmapStatistic - { - Name = "BPM", - Icon = FontAwesome.fa_circle, - Content = getBPMRange(beatmap.Beatmap), - })); - - //get statistics fromt he current ruleset. - labels.AddRange(beatmap.BeatmapInfo.Ruleset.CreateInstance().GetBeatmapStatistics(beatmap).Select(s => new InfoLabel(s))); - } - AlwaysPresent = true; + var lastContainer = beatmapInfoContainer; + float newDepth = lastContainer?.Depth + 1 ?? 0; + Add(beatmapInfoContainer = new AsyncLoadWrapper( - new BufferedContainer + new BufferedWedgeInfo(beatmap) { + Shear = -Shear, OnLoadComplete = d => { FadeIn(250); lastContainer?.FadeOut(250); lastContainer?.Expire(); - }, - PixelSnapping = true, - CacheDrawnFrameBuffer = true, - Shear = -Shear, - RelativeSizeAxes = Axes.Both, - Children = new Drawable[] - { - // We will create the white-to-black gradient by modulating transparency and having - // a black backdrop. This results in an sRGB-space gradient and not linear space, - // transitioning from white to black more perceptually uniformly. - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - }, - // We use a container, such that we can set the colour gradient to go across the - // vertices of the masked container instead of the vertices of the (larger) sprite. - new Container - { - RelativeSizeAxes = Axes.Both, - ColourInfo = ColourInfo.GradientVertical(Color4.White, Color4.White.Opacity(0.3f)), - Children = new[] - { - // Zoomed-in and cropped beatmap background - new BeatmapBackgroundSprite(beatmap) - { - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - FillMode = FillMode.Fill, - }, - }, - }, - new DifficultyColourBar(beatmap.BeatmapInfo) - { - RelativeSizeAxes = Axes.Y, - Width = 20, - }, - new FillFlowContainer - { - Name = "Top-aligned metadata", - Anchor = Anchor.TopLeft, - Origin = Anchor.TopLeft, - Direction = FillDirection.Vertical, - Margin = new MarginPadding { Top = 10, Left = 25, Right = 10, Bottom = 20 }, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new OsuSpriteText - { - Font = @"Exo2.0-MediumItalic", - Text = beatmapInfo.Version, - TextSize = 24, - }, - } - }, - new FillFlowContainer - { - Name = "Bottom-aligned metadata", - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Direction = FillDirection.Vertical, - Margin = new MarginPadding { Top = 15, Left = 25, Right = 10, Bottom = 20 }, - AutoSizeAxes = Axes.Both, - Children = new Drawable[] - { - new OsuSpriteText - { - Font = @"Exo2.0-MediumItalic", - Text = !string.IsNullOrEmpty(metadata.Source) ? metadata.Source + " — " + metadata.Title : metadata.Title, - TextSize = 28, - }, - new OsuSpriteText - { - Font = @"Exo2.0-MediumItalic", - Text = metadata.Artist, - TextSize = 17, - }, - new FillFlowContainer - { - Margin = new MarginPadding { Top = 10 }, - Direction = FillDirection.Horizontal, - AutoSizeAxes = Axes.Both, - Children = new[] - { - new OsuSpriteText - { - Font = @"Exo2.0-Medium", - Text = "mapped by ", - TextSize = 15, - }, - new OsuSpriteText - { - Font = @"Exo2.0-Bold", - Text = metadata.Author, - TextSize = 15, - }, - } - }, - new FillFlowContainer - { - Margin = new MarginPadding { Top = 20, Left = 10 }, - Spacing = new Vector2(40, 0), - AutoSizeAxes = Axes.Both, - Children = labels - }, - } - }, } }) { @@ -231,23 +96,164 @@ namespace osu.Game.Screens.Select }); } - private string getBPMRange(Beatmap beatmap) + public class BufferedWedgeInfo : BufferedContainer { - double bpmMax = beatmap.TimingInfo.BPMMaximum; - double bpmMin = beatmap.TimingInfo.BPMMinimum; - - if (Precision.AlmostEquals(bpmMin, bpmMax)) return Math.Round(bpmMin) + "bpm"; - - return Math.Round(bpmMin) + "-" + Math.Round(bpmMax) + "bpm (mostly " + Math.Round(beatmap.TimingInfo.BPMMode) + "bpm)"; - } - - public class InfoLabel : Container - { - public InfoLabel(BeatmapStatistic statistic) + public BufferedWedgeInfo(WorkingBeatmap beatmap) { - AutoSizeAxes = Axes.Both; + BeatmapInfo beatmapInfo = beatmap.BeatmapInfo; + BeatmapMetadata metadata = beatmapInfo.Metadata ?? beatmap.BeatmapSetInfo?.Metadata ?? new BeatmapMetadata(); + + List labels = new List(); + + if (beatmap.Beatmap != null) + { + HitObject lastObject = beatmap.Beatmap.HitObjects.LastOrDefault(); + double endTime = (lastObject as IHasEndTime)?.EndTime ?? lastObject?.StartTime ?? 0; + + labels.Add(new InfoLabel(new BeatmapStatistic + { + Name = "Length", + Icon = FontAwesome.fa_clock_o, + Content = beatmap.Beatmap.HitObjects.Count == 0 ? "-" : TimeSpan.FromMilliseconds(endTime - beatmap.Beatmap.HitObjects.First().StartTime).ToString(@"m\:ss"), + })); + + labels.Add(new InfoLabel(new BeatmapStatistic + { + Name = "BPM", + Icon = FontAwesome.fa_circle, + Content = getBPMRange(beatmap.Beatmap), + })); + + //get statistics fromt he current ruleset. + labels.AddRange(beatmapInfo.Ruleset.CreateInstance().GetBeatmapStatistics(beatmap).Select(s => new InfoLabel(s))); + } + + PixelSnapping = true; + CacheDrawnFrameBuffer = true; + RelativeSizeAxes = Axes.Both; + Children = new Drawable[] { + // We will create the white-to-black gradient by modulating transparency and having + // a black backdrop. This results in an sRGB-space gradient and not linear space, + // transitioning from white to black more perceptually uniformly. + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + }, + // We use a container, such that we can set the colour gradient to go across the + // vertices of the masked container instead of the vertices of the (larger) sprite. + new Container + { + RelativeSizeAxes = Axes.Both, + ColourInfo = ColourInfo.GradientVertical(Color4.White, Color4.White.Opacity(0.3f)), + Children = new[] + { + // Zoomed-in and cropped beatmap background + new BeatmapBackgroundSprite(beatmap) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fill, + }, + }, + }, + new DifficultyColourBar(beatmap.BeatmapInfo) + { + RelativeSizeAxes = Axes.Y, + Width = 20, + }, + new FillFlowContainer + { + Name = "Top-aligned metadata", + Anchor = Anchor.TopLeft, + Origin = Anchor.TopLeft, + Direction = FillDirection.Vertical, + Margin = new MarginPadding { Top = 10, Left = 25, Right = 10, Bottom = 20 }, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new OsuSpriteText + { + Font = @"Exo2.0-MediumItalic", + Text = beatmapInfo.Version, + TextSize = 24, + }, + } + }, + new FillFlowContainer + { + Name = "Bottom-aligned metadata", + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Direction = FillDirection.Vertical, + Margin = new MarginPadding { Top = 15, Left = 25, Right = 10, Bottom = 20 }, + AutoSizeAxes = Axes.Both, + Children = new Drawable[] + { + new OsuSpriteText + { + Font = @"Exo2.0-MediumItalic", + Text = !string.IsNullOrEmpty(metadata.Source) ? metadata.Source + " — " + metadata.Title : metadata.Title, + TextSize = 28, + }, + new OsuSpriteText + { + Font = @"Exo2.0-MediumItalic", + Text = metadata.Artist, + TextSize = 17, + }, + new FillFlowContainer + { + Margin = new MarginPadding { Top = 10 }, + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + Children = new[] + { + new OsuSpriteText + { + Font = @"Exo2.0-Medium", + Text = "mapped by ", + TextSize = 15, + }, + new OsuSpriteText + { + Font = @"Exo2.0-Bold", + Text = metadata.Author, + TextSize = 15, + }, + } + }, + new FillFlowContainer + { + Margin = new MarginPadding { Top = 20, Left = 10 }, + Spacing = new Vector2(40, 0), + AutoSizeAxes = Axes.Both, + Children = labels + }, + } + }, + }; + } + + private string getBPMRange(Beatmap beatmap) + { + double bpmMax = beatmap.TimingInfo.BPMMaximum; + double bpmMin = beatmap.TimingInfo.BPMMinimum; + + if (Precision.AlmostEquals(bpmMin, bpmMax)) return Math.Round(bpmMin) + "bpm"; + + return Math.Round(bpmMin) + "-" + Math.Round(bpmMax) + "bpm (mostly " + Math.Round(beatmap.TimingInfo.BPMMode) + "bpm)"; + } + + public class InfoLabel : Container + { + public InfoLabel(BeatmapStatistic statistic) + { + AutoSizeAxes = Axes.Both; + Children = new Drawable[] + { new TextAwesome { Icon = FontAwesome.fa_square, @@ -273,23 +279,23 @@ namespace osu.Game.Screens.Select TextSize = 17, Origin = Anchor.CentreLeft }, - }; - } - } - - private class DifficultyColourBar : DifficultyColouredContainer - { - public DifficultyColourBar(BeatmapInfo beatmap) : base(beatmap) - { + }; + } } - [BackgroundDependencyLoader] - private void load() + private class DifficultyColourBar : DifficultyColouredContainer { - const float full_opacity_ratio = 0.7f; - - Children = new Drawable[] + public DifficultyColourBar(BeatmapInfo beatmap) : base(beatmap) { + } + + [BackgroundDependencyLoader] + private void load() + { + const float full_opacity_ratio = 0.7f; + + Children = new Drawable[] + { new Box { RelativeSizeAxes = Axes.Both, @@ -305,7 +311,8 @@ namespace osu.Game.Screens.Select X = full_opacity_ratio, Width = 1 - full_opacity_ratio, } - }; + }; + } } } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 182158fa5d..7d0648ac11 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using System.Linq; using System.Threading; using OpenTK; using OpenTK.Input; @@ -15,8 +14,8 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Input; using osu.Framework.Screens; +using osu.Framework.Threading; using osu.Game.Beatmaps; -using osu.Game.Beatmaps.Drawables; using osu.Game.Database; using osu.Game.Graphics; using osu.Game.Graphics.Containers; @@ -197,6 +196,12 @@ namespace osu.Game.Screens.Select private void raiseSelect() { + var pendingSelection = selectionChangedDebounce; + selectionChangedDebounce = null; + + pendingSelection?.RunTask(); + pendingSelection?.Cancel(); // cancel the already scheduled task. + if (Beatmap == null) return; OnSelected(); @@ -297,25 +302,37 @@ namespace osu.Game.Screens.Select carousel.SelectBeatmap(beatmap?.BeatmapInfo); } + private ScheduledDelegate selectionChangedDebounce; + + // We need to keep track of the last selected beatmap ignoring debounce to play the correct selection sounds. + private BeatmapInfo selectionChangeNoBounce; + /// /// selection has been changed as the result of interaction with the carousel. /// - private void selectionChanged(BeatmapGroup group, BeatmapInfo beatmap) + private void selectionChanged(BeatmapInfo beatmap) { bool beatmapSetChange = false; if (!beatmap.Equals(Beatmap?.BeatmapInfo)) { - if (beatmap.BeatmapSetInfoID == Beatmap?.BeatmapInfo.BeatmapSetInfoID) + if (beatmap.BeatmapSetInfoID == selectionChangeNoBounce?.BeatmapSetInfoID) sampleChangeDifficulty.Play(); else { sampleChangeBeatmap.Play(); beatmapSetChange = true; } - Beatmap = database.GetWorkingBeatmap(beatmap, Beatmap); } - ensurePlayingSelected(beatmapSetChange); + + selectionChangeNoBounce = beatmap; + + selectionChangedDebounce?.Cancel(); + selectionChangedDebounce = Scheduler.AddDelayed(delegate + { + Beatmap = database.GetWorkingBeatmap(beatmap, Beatmap); + ensurePlayingSelected(beatmapSetChange); + }, 100); } private void ensurePlayingSelected(bool preview = false) @@ -331,11 +348,6 @@ namespace osu.Game.Screens.Select } } - private void selectBeatmap(BeatmapSetInfo beatmapSet = null) - { - carousel.SelectBeatmap(beatmapSet != null ? beatmapSet.Beatmaps.First() : Beatmap?.BeatmapInfo); - } - private void removeBeatmapSet(BeatmapSetInfo beatmapSet) { carousel.RemoveBeatmap(beatmapSet);