diff --git a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs index 163dd7fbe9..59ff7b06e5 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs @@ -40,6 +40,7 @@ namespace osu.Game.Beatmaps.Drawables public BeatmapSetInfo BeatmapSet; private BeatmapGroupState state; + public BeatmapGroupState State { get { return state; } @@ -52,7 +53,8 @@ namespace osu.Game.Beatmaps.Drawables case BeatmapGroupState.Expanded: Header.State = PanelSelectedState.Selected; foreach (BeatmapPanel panel in BeatmapPanels) - panel.State = panel == SelectedPanel ? PanelSelectedState.Selected : PanelSelectedState.NotSelected; + panel.State = panel == SelectedPanel ? PanelSelectedState.Selected : + !panel.Filtered ? PanelSelectedState.NotSelected : PanelSelectedState.Hidden; break; case BeatmapGroupState.Collapsed: Header.State = PanelSelectedState.NotSelected; @@ -108,7 +110,7 @@ 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; + BeatmapPanels.First(p => !p.Filtered).State = PanelSelectedState.Selected; } private void panelGainedSelection(BeatmapPanel panel) diff --git a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs b/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs index e6bf08eb9f..c94f4fdc57 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs @@ -61,6 +61,8 @@ namespace osu.Game.Beatmaps.Drawables return base.OnClick(state); } + public bool Filtered { get; set; } + protected override void ApplyState(PanelSelectedState last = PanelSelectedState.Hidden) { if (!IsLoaded) return; diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 578b02d90a..fb6761eb8d 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -131,7 +131,7 @@ namespace osu.Game.Screens.Select var newSelection = newGroup.BeatmapPanels.Find(p => p.Beatmap.ID == selectedPanel?.Beatmap.ID); - if(newSelection == null && oldGroup != null && selectedPanel != null) + if (newSelection == null && oldGroup != null && selectedPanel != null) newSelection = newGroup.BeatmapPanels[Math.Min(newGroup.BeatmapPanels.Count - 1, oldGroup.BeatmapPanels.IndexOf(selectedPanel))]; selectGroup(newGroup, newSelection); @@ -178,42 +178,62 @@ namespace osu.Game.Screens.Select SelectionChanged?.Invoke(null); } + /// + /// Increment selection in the carousel in a chosen direction. + /// + /// The direction to increment. Negative is backwards. + /// Whether to skip individual difficulties and only increment over full groups. public void SelectNext(int direction = 1, bool skipDifficulties = true) { + // todo: we may want to refactor and remove this as an optimisation in the future. if (groups.All(g => g.State == BeatmapGroupState.Hidden)) { selectNullBeatmap(); return; } - if (!skipDifficulties && selectedGroup != null) - { - int i = selectedGroup.BeatmapPanels.IndexOf(selectedPanel) + direction; + int originalIndex = Math.Max(0, groups.IndexOf(selectedGroup)); + int currentIndex = originalIndex; - if (i >= 0 && i < selectedGroup.BeatmapPanels.Count) - { - //changing difficulty panel, not set. - selectGroup(selectedGroup, selectedGroup.BeatmapPanels[i]); - return; - } - } + // local function to increment the index in the required direction, wrapping over extremities. + int incrementIndex() => currentIndex = (currentIndex + direction + groups.Count) % groups.Count; - int startIndex = Math.Max(0, groups.IndexOf(selectedGroup)); - int index = startIndex; + // in the case we are skipping difficulties, we want to increment the index once before starting to find out new target + // (we don't care about the currently selected group). + if (skipDifficulties) + incrementIndex(); do { - index = (index + direction + groups.Count) % groups.Count; - if (groups[index].State != BeatmapGroupState.Hidden) - { - if (skipDifficulties) - SelectBeatmap(groups[index].SelectedPanel != null ? groups[index].SelectedPanel.Beatmap : groups[index].BeatmapPanels.First().Beatmap); - else - SelectBeatmap(direction == 1 ? groups[index].BeatmapPanels.First().Beatmap : groups[index].BeatmapPanels.Last().Beatmap); + var group = groups[currentIndex]; + if (group.State == BeatmapGroupState.Hidden) continue; + + // we are only interested in non-filtered panels. + IEnumerable validPanels = group.BeatmapPanels.Where(p => !p.Filtered); + + // if we are considering difficulties, we need to do a few extrea steps. + if (!skipDifficulties) + { + // we want to reverse the panel order if we are searching backwards. + if (direction < 0) + validPanels = validPanels.Reverse(); + + // if we are currently on the selected panel, let's try to find a valid difficulty before leaving to the next group. + // the first valid difficulty is found by skipping to the selected panel and then one further. + if (currentIndex == originalIndex) + validPanels = validPanels.SkipWhile(p => p != selectedPanel).Skip(1); + } + + var next = validPanels.FirstOrDefault(); + + // at this point, we can perform the selection change if we have a valid new target, else continue to increment in the specified direction. + if (next != null) + { + selectGroup(group, next); return; } - } while (index != startIndex); + } while (incrementIndex() != originalIndex); } private IEnumerable getVisibleGroups() => groups.Where(selectGroup => selectGroup.State != BeatmapGroupState.Hidden); @@ -416,6 +436,8 @@ namespace osu.Game.Screens.Select foreach (BeatmapPanel panel in group.BeatmapPanels) { + if (panel.Filtered) continue; + if (panel == selectedPanel) selectedY = currentY + panel.DrawHeight / 2 - DrawHeight / 2; @@ -460,7 +482,7 @@ namespace osu.Game.Screens.Select try { if (panel == null) - panel = group.BeatmapPanels.First(); + panel = group.BeatmapPanels.First(p => !p.Filtered); if (selectedPanel == panel) return; diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index c1355bfa63..96778291a1 100644 --- a/osu.Game/Screens/Select/FilterCriteria.cs +++ b/osu.Game/Screens/Select/FilterCriteria.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using osu.Game.Beatmaps; using osu.Game.Beatmaps.Drawables; using osu.Game.Rulesets; using osu.Game.Screens.Select.Filter; @@ -18,19 +19,26 @@ namespace osu.Game.Screens.Select public RulesetInfo Ruleset; public bool AllowConvertedBeatmaps; + private bool canConvert(BeatmapInfo beatmapInfo) => beatmapInfo.RulesetID == Ruleset.ID || beatmapInfo.RulesetID == 0 && Ruleset.ID > 0 && AllowConvertedBeatmaps; + public void Filter(List groups) { foreach (var g in groups) { var set = g.BeatmapSet; - bool hasCurrentMode = AllowConvertedBeatmaps || set.Beatmaps.Any(bm => bm.RulesetID == (Ruleset?.ID ?? 0)); + // we only support converts from osu! mode to other modes for now. + // in the future this will have to change, at which point this condition will become a touch more complicated. + bool hasCurrentMode = set.Beatmaps.Any(canConvert); bool match = hasCurrentMode; if (!string.IsNullOrEmpty(SearchText)) match &= set.Metadata.SearchableTerms.Any(term => term.IndexOf(SearchText, StringComparison.InvariantCultureIgnoreCase) >= 0); + foreach (var panel in g.BeatmapPanels) + panel.Filtered = !canConvert(panel.Beatmap); + switch (g.State) { case BeatmapGroupState.Hidden: diff --git a/osu.sln.DotSettings b/osu.sln.DotSettings index 76929dcbb3..2f52881d6d 100644 --- a/osu.sln.DotSettings +++ b/osu.sln.DotSettings @@ -602,6 +602,7 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-frame <Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /> <Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> + <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"><ExtraRule Prefix="_" Suffix="" Style="aaBb" /></Policy> <Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" />