1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-13 16:32:54 +08:00

Merge pull request #1689 from peppy/song-select-ruleset-filter

Filter invalid difficulties at song select
This commit is contained in:
Dan Balasescu 2017-12-12 13:25:57 +09:00 committed by GitHub
commit a7f5eb0a3d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 108 additions and 50 deletions

View File

@ -40,36 +40,45 @@ namespace osu.Game.Beatmaps.Drawables
public BeatmapSetInfo BeatmapSet;
private BeatmapGroupState state;
public BeatmapGroupState State
{
get { return state; }
set
{
state = value;
switch (value)
{
case BeatmapGroupState.Expanded:
Header.State = PanelSelectedState.Selected;
foreach (BeatmapPanel panel in BeatmapPanels)
panel.State = panel == SelectedPanel ? PanelSelectedState.Selected : PanelSelectedState.NotSelected;
break;
case BeatmapGroupState.Collapsed:
Header.State = PanelSelectedState.NotSelected;
foreach (BeatmapPanel panel in BeatmapPanels)
panel.State = PanelSelectedState.Hidden;
break;
case BeatmapGroupState.Hidden:
Header.State = PanelSelectedState.Hidden;
foreach (BeatmapPanel panel in BeatmapPanels)
panel.State = PanelSelectedState.Hidden;
break;
}
UpdateState();
StateChanged?.Invoke(state);
}
}
public void UpdateState()
{
switch (state)
{
case BeatmapGroupState.Expanded:
Header.State = PanelSelectedState.Selected;
foreach (BeatmapPanel panel in BeatmapPanels)
if (panel == SelectedPanel)
panel.State = PanelSelectedState.Selected;
else if (panel.Filtered)
panel.State = PanelSelectedState.Hidden;
else
panel.State = PanelSelectedState.NotSelected;
break;
case BeatmapGroupState.Collapsed:
Header.State = PanelSelectedState.NotSelected;
foreach (BeatmapPanel panel in BeatmapPanels)
panel.State = PanelSelectedState.Hidden;
break;
case BeatmapGroupState.Hidden:
Header.State = PanelSelectedState.Hidden;
foreach (BeatmapPanel panel in BeatmapPanels)
panel.State = PanelSelectedState.Hidden;
break;
}
}
public BeatmapGroup(BeatmapSetInfo beatmapSet, BeatmapManager manager)
{
if (beatmapSet == null)
@ -88,7 +97,7 @@ namespace osu.Game.Beatmaps.Drawables
RelativeSizeAxes = Axes.X,
};
BeatmapPanels = BeatmapSet.Beatmaps.Where(b => !b.Hidden).OrderBy(b => b.StarDifficulty).Select(b => new BeatmapPanel(b)
BeatmapPanels = BeatmapSet.Beatmaps.Where(b => !b.Hidden).OrderBy(b => b.RulesetID).ThenBy(b => b.StarDifficulty).Select(b => new BeatmapPanel(b)
{
Alpha = 0,
GainedSelection = panelGainedSelection,
@ -108,7 +117,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)

View File

@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
@ -61,6 +62,8 @@ namespace osu.Game.Beatmaps.Drawables
return base.OnClick(state);
}
public BindableBool Filtered = new BindableBool();
protected override void ApplyState(PanelSelectedState last = PanelSelectedState.Hidden)
{
if (!IsLoaded) return;

View File

@ -7,6 +7,7 @@ using System.Linq;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
@ -157,8 +158,7 @@ namespace osu.Game.Beatmaps.Drawables
if (panels == null)
throw new ArgumentNullException(nameof(panels));
foreach (var p in panels)
difficultyIcons.Add(new DifficultyIcon(p.Beatmap));
difficultyIcons.AddRange(panels.Select(p => new FilterableDifficultyIcon(p)));
}
public MenuItem[] ContextMenuItems
@ -178,5 +178,18 @@ namespace osu.Game.Beatmaps.Drawables
return items.ToArray();
}
}
public class FilterableDifficultyIcon : DifficultyIcon
{
private readonly BindableBool filtered = new BindableBool();
public FilterableDifficultyIcon(BeatmapPanel panel)
: base(panel.Beatmap)
{
filtered.BindTo(panel.Filtered);
filtered.ValueChanged += v => this.FadeTo(v ? 0.1f : 1, 100);
filtered.TriggerChange();
}
}
}
}

View File

@ -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);
}
/// <summary>
/// Increment selection in the carousel in a chosen direction.
/// </summary>
/// <param name="direction">The direction to increment. Negative is backwards.</param>
/// <param name="skipDifficulties">Whether to skip individual difficulties and only increment over full groups.</param>
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<BeatmapPanel> 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<BeatmapGroup> getVisibleGroups() => groups.Where(selectGroup => selectGroup.State != BeatmapGroupState.Hidden);
@ -307,6 +327,8 @@ namespace osu.Game.Screens.Select
computeYPositions();
selectedGroup?.UpdateState();
if (selectedGroup == null || selectedGroup.State == BeatmapGroupState.Hidden)
SelectNext();
else
@ -421,11 +443,13 @@ namespace osu.Game.Screens.Select
panel.MoveToX(-50, 500, Easing.OutExpo);
bool isHidden = panel.State == PanelSelectedState.Hidden;
//on first display we want to begin hidden under our group's header.
if (panel.Alpha == 0)
if (isHidden || panel.Alpha == 0)
panel.MoveToY(headerY);
movePanel(panel, true, animated, ref currentY);
movePanel(panel, !isHidden, animated, ref currentY);
}
}
else
@ -459,8 +483,8 @@ namespace osu.Game.Screens.Select
{
try
{
if (panel == null)
panel = group.BeatmapPanels.First();
if (panel == null || panel.Filtered == true)
panel = group.BeatmapPanels.First(p => !p.Filtered);
if (selectedPanel == panel) return;

View File

@ -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<BeatmapGroup> 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.Value = !canConvert(panel.Beatmap);
switch (g.State)
{
case BeatmapGroupState.Hidden:

View File

@ -602,6 +602,7 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-frame
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=Constants/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=EnumMember/@EntryIndexedValue">&lt;Policy Inspect="False" Prefix="" Suffix="" Style="AaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=LocalConstants/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=LocalFunctions/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateConstants/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aa_bb" /&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateInstanceFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb"&gt;&lt;ExtraRule Prefix="_" Suffix="" Style="aaBb" /&gt;&lt;/Policy&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/PredefinedNamingRules/=PrivateStaticFields/@EntryIndexedValue">&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;</s:String>