diff --git a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs index 7799095a77..7470a543f9 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs @@ -29,7 +29,9 @@ namespace osu.Game.Beatmaps.Drawables private BeatmapGroupState state; - public List BeatmapPanels; + public List BeatmapPanels; + + public BeatmapSetInfo BeatmapSet; public BeatmapGroupState State { @@ -73,7 +75,9 @@ namespace osu.Game.Beatmaps.Drawables GainedSelection = panelGainedSelection, StartRequested = p => { StartRequested?.Invoke(p.Beatmap); }, RelativeSizeAxes = Axes.X, - }).ToList(); + }).ToList(); + + BeatmapSet = set; } private void headerGainedSelection(BeatmapSetHeader panel) diff --git a/osu.Game/Screens/Select/CarouselContainer.cs b/osu.Game/Screens/Select/CarouselContainer.cs index 144e4cabba..715eda58a0 100644 --- a/osu.Game/Screens/Select/CarouselContainer.cs +++ b/osu.Game/Screens/Select/CarouselContainer.cs @@ -16,10 +16,11 @@ using osu.Game.Beatmaps.Drawables; using osu.Framework.Timing; using osu.Framework.Input; using OpenTK.Input; +using System.Collections; namespace osu.Game.Screens.Select { - class CarouselContainer : ScrollContainer + class CarouselContainer : ScrollContainer, IEnumerable { private Container scrollableContent; private List groups = new List(); @@ -27,29 +28,29 @@ namespace osu.Game.Screens.Select public BeatmapGroup SelectedGroup { get; private set; } public BeatmapPanel SelectedPanel { get; private set; } - private List yPositions = new List(); - private CarouselLifetimeList Lifetime; - - public CarouselContainer() - { - DistanceDecayJump = 0.01; - - Add(scrollableContent = new Container(Lifetime = new CarouselLifetimeList(DepthComparer)) - { - RelativeSizeAxes = Axes.X, - }); - } - - internal class CarouselLifetimeList : LifetimeList - { - public CarouselLifetimeList(IComparer comparer) - : base(comparer) - { - } - - public int StartIndex; + private List yPositions = new List(); + private CarouselLifetimeList Lifetime; + + public CarouselContainer() + { + DistanceDecayJump = 0.01; + + Add(scrollableContent = new Container(Lifetime = new CarouselLifetimeList(DepthComparer)) + { + RelativeSizeAxes = Axes.X, + }); + } + + internal class CarouselLifetimeList : LifetimeList + { + public CarouselLifetimeList(IComparer comparer) + : base(comparer) + { + } + + public int StartIndex; public int EndIndex; - + public override bool Update(FrameTimeInfo time) { bool anyAliveChanged = false; @@ -75,9 +76,9 @@ namespace osu.Game.Screens.Select } return anyAliveChanged; - } - } - + } + } + public void AddGroup(BeatmapGroup group) { group.State = BeatmapGroupState.Collapsed; @@ -100,7 +101,7 @@ namespace osu.Game.Screens.Select yPositions.Add(currentY); panel.MoveToY(currentY, 750, EasingTypes.OutExpo); - if (advance) + if (advance && panel.IsVisible) currentY += panel.DrawHeight + 5; } @@ -200,7 +201,9 @@ namespace osu.Game.Screens.Select /// Half the draw height of the carousel container. private void updatePanel(Panel p, float halfHeight) { - float panelDrawY = p.Position.Y - Current + p.DrawHeight / 2; + var height = p.IsVisible ? p.DrawHeight : 0; + + float panelDrawY = p.Position.Y - Current + height / 2; float dist = Math.Abs(1f - panelDrawY / halfHeight); // Setting the origin position serves as an additive position on top of potential @@ -222,11 +225,11 @@ namespace osu.Game.Screens.Select float drawHeight = DrawHeight; float halfHeight = drawHeight / 2; - foreach (Panel p in Lifetime.AliveItems) - { - float panelPosY = p.Position.Y; - p.IsOnScreen = panelPosY >= Current - p.DrawHeight && panelPosY <= Current + drawHeight; - updatePanel(p, halfHeight); + foreach (Panel p in Lifetime.AliveItems) + { + float panelPosY = p.Position.Y; + p.IsOnScreen = panelPosY >= Current - p.DrawHeight && panelPosY <= Current + drawHeight; + updatePanel(p, halfHeight); } // Determine range of indices for items that are now definitely on screen to be added @@ -247,6 +250,13 @@ namespace osu.Game.Screens.Select } } + public void InvalidateVisible() + { + Lifetime.StartIndex = 0; + Lifetime.EndIndex = groups.Count - 1; + computeYPositions(); + } + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) { int direction = 0; @@ -288,5 +298,15 @@ namespace osu.Game.Screens.Select return base.OnKeyDown(state, args); } + + public IEnumerator GetEnumerator() + { + return groups.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } } } diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index a6fb62945f..8366da7021 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -49,6 +49,8 @@ namespace osu.Game.Screens.Select public string Search { get; private set; } = string.Empty; public SortMode Sort { get; private set; } = SortMode.Title; + private SearchTextBox searchTextBox; + public FilterControl() { AutoSizeAxes = Axes.Y; @@ -72,11 +74,17 @@ namespace osu.Game.Screens.Select Direction = FlowDirection.VerticalOnly, Children = new Drawable[] { - new SearchTextBox { RelativeSizeAxes = Axes.X }, + searchTextBox = new SearchTextBox { RelativeSizeAxes = Axes.X }, new GroupSortTabs() } } }; + + searchTextBox.OnChange += (sender, text) => + { + Search = searchTextBox.Text; + FilterChanged?.Invoke(); + }; } private class TabItem : ClickableContainer diff --git a/osu.Game/Screens/Select/PlaySongSelect.cs b/osu.Game/Screens/Select/PlaySongSelect.cs index 1688326725..6114d5d56a 100644 --- a/osu.Game/Screens/Select/PlaySongSelect.cs +++ b/osu.Game/Screens/Select/PlaySongSelect.cs @@ -31,6 +31,7 @@ using osu.Game.Graphics.Containers; using osu.Framework.Input; using OpenTK.Input; using osu.Game.Graphics; +using System.Collections.Generic; namespace osu.Game.Screens.Select { @@ -52,6 +53,8 @@ namespace osu.Game.Screens.Select private AudioSample sampleChangeDifficulty; private AudioSample sampleChangeBeatmap; + + private List beatmapGroups; class WedgeBackground : Container { @@ -82,6 +85,7 @@ namespace osu.Game.Screens.Select } Player player; + FilterControl filter; private void start() { @@ -112,6 +116,7 @@ namespace osu.Game.Screens.Select { const float carouselWidth = 640; const float bottomToolHeight = 50; + beatmapGroups = new List(); Children = new Drawable[] { new ParallaxContainer @@ -134,10 +139,11 @@ namespace osu.Game.Screens.Select Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, }, - new FilterControl + filter = new FilterControl { Position = wedged_container_start_position, RelativeSizeAxes = Axes.X, + FilterChanged = filterChanged, }, beatmapInfoWedge = new BeatmapInfoWedge { @@ -203,6 +209,41 @@ namespace osu.Game.Screens.Select Task.Factory.StartNew(() => addBeatmapSets(game, initialAddSetsTask.Token), initialAddSetsTask.Token); } + private void filterChanged() + { + var search = filter.Search; + BeatmapGroup newSelection = null; + bool changed = false; + foreach (var beatmapGroup in carousel) + { + var set = beatmapGroup.BeatmapSet; + if (set == null) + continue; + bool match = string.IsNullOrEmpty(search) + || (set.Metadata.Artist ?? "").IndexOf(search, StringComparison.InvariantCultureIgnoreCase) != -1 + || (set.Metadata.ArtistUnicode ?? "").IndexOf(search, StringComparison.InvariantCultureIgnoreCase) != -1 + || (set.Metadata.Title ?? "").IndexOf(search, StringComparison.InvariantCultureIgnoreCase) != -1 + || (set.Metadata.TitleUnicode ?? "").IndexOf(search, StringComparison.InvariantCultureIgnoreCase) != -1; + if (match) + { + changed = changed && beatmapGroup.Header.Alpha == 1; + beatmapGroup.Header.Alpha = 1; + if (newSelection == null || beatmapGroup.BeatmapSet.OnlineBeatmapSetID == Beatmap.BeatmapSetInfo.OnlineBeatmapSetID) + newSelection = beatmapGroup; + } + else + { + changed = changed && beatmapGroup.Header.Alpha == 0; + beatmapGroup.Header.Alpha = 0; + beatmapGroup.State = BeatmapGroupState.Collapsed; + } + } + if (newSelection != null) + selectBeatmap(newSelection.BeatmapSet.Beatmaps[0]); + if (changed) + carousel.InvalidateVisible(); + } + private void onDatabaseOnBeatmapSetAdded(BeatmapSetInfo s) { Schedule(() => addBeatmapSet(s, Game, true)); @@ -347,7 +388,7 @@ namespace osu.Game.Screens.Select var beatmap = new WorkingBeatmap(beatmapSet.Beatmaps.FirstOrDefault(), beatmapSet, database); - var group = new BeatmapGroup(beatmap) + var group = new BeatmapGroup(beatmap, beatmapSet) { SelectionChanged = selectionChanged, StartRequested = b => start() @@ -357,6 +398,8 @@ namespace osu.Game.Screens.Select //this likely won't scale so well, but allows us to completely async the loading flow. Task.WhenAll(group.BeatmapPanels.Select(panel => panel.Preload(game))).ContinueWith(task => Schedule(delegate { + beatmapGroups.Add(group); + carousel.AddGroup(group); if (Beatmap == null || select)