diff --git a/osu-framework b/osu-framework index 797a351db2..e57f32e483 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 797a351db2e852fef5296453641ffbf6b2f6dc11 +Subproject commit e57f32e483fa47ee066becc9e0a1b3187b340606 diff --git a/osu.Game.Rulesets.Osu/OsuRuleset.cs b/osu.Game.Rulesets.Osu/OsuRuleset.cs index bb5700976d..85a46f3aaa 100644 --- a/osu.Game.Rulesets.Osu/OsuRuleset.cs +++ b/osu.Game.Rulesets.Osu/OsuRuleset.cs @@ -5,7 +5,6 @@ using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Osu.Mods; -using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.OsuDifficulty; using osu.Game.Rulesets.Osu.UI; using osu.Game.Rulesets.UI; @@ -18,6 +17,8 @@ using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Osu.Scoring; using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Edit; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Objects; namespace osu.Game.Rulesets.Osu { @@ -33,21 +34,28 @@ namespace osu.Game.Rulesets.Osu new KeyBinding(InputKey.MouseRight, OsuAction.RightButton), }; - public override IEnumerable GetBeatmapStatistics(WorkingBeatmap beatmap) => new[] + public override IEnumerable GetBeatmapStatistics(WorkingBeatmap beatmap) { - new BeatmapStatistic + IEnumerable hitObjects = beatmap.Beatmap.HitObjects; + IEnumerable circles = hitObjects.Where(d => !(d is IHasEndTime)); + IEnumerable sliders = hitObjects.Where(s => s is IHasCurve); + + return new[] { - Name = @"Circle count", - Content = beatmap.Beatmap.HitObjects.Count(h => h is HitCircle).ToString(), - Icon = FontAwesome.fa_dot_circle_o - }, - new BeatmapStatistic - { - Name = @"Slider count", - Content = beatmap.Beatmap.HitObjects.Count(h => h is Slider).ToString(), - Icon = FontAwesome.fa_circle_o - } - }; + new BeatmapStatistic + { + Name = @"Circle Count", + Content = circles.Count().ToString(), + Icon = FontAwesome.fa_circle_o + }, + new BeatmapStatistic + { + Name = @"Slider Count", + Content = sliders.Count().ToString(), + Icon = FontAwesome.fa_circle + } + }; + } public override IEnumerable GetModsFor(ModType type) { diff --git a/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs new file mode 100644 index 0000000000..0168cedc86 --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseBeatmapInfoWedge.cs @@ -0,0 +1,69 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; +using osu.Game.Screens.Select; + +namespace osu.Game.Tests.Visual +{ + public class TestCaseBeatmapInfoWedge : OsuTestCase + { + private BeatmapManager beatmaps; + private readonly Random random; + private readonly BeatmapInfoWedge infoWedge; + private readonly Bindable beatmap = new Bindable(); + + public TestCaseBeatmapInfoWedge() + { + random = new Random(0123); + + Add(infoWedge = new BeatmapInfoWedge + { + Size = new Vector2(0.5f, 245), + RelativeSizeAxes = Axes.X, + Margin = new MarginPadding + { + Top = 20, + }, + }); + + AddStep("show", () => + { + Content.FadeInFromZero(250); + infoWedge.State = Visibility.Visible; + infoWedge.UpdateBeatmap(beatmap); + }); + AddStep("hide", () => + { + infoWedge.State = Visibility.Hidden; + Content.FadeOut(100); + }); + AddStep("random beatmap", randomBeatmap); + AddStep("null beatmap", () => infoWedge.UpdateBeatmap(beatmap.Default)); + } + + [BackgroundDependencyLoader] + private void load(OsuGameBase game, BeatmapManager beatmaps) + { + this.beatmaps = beatmaps; + beatmap.BindTo(game.Beatmap); + } + + private void randomBeatmap() + { + var sets = beatmaps.GetAllUsableBeatmapSets(); + if (sets.Count == 0) + return; + + var b = sets[random.Next(0, sets.Count)].Beatmaps[0]; + beatmap.Value = beatmaps.GetWorkingBeatmap(b); + infoWedge.UpdateBeatmap(beatmap); + } + } +} diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index 1db453137e..2d16d8b064 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -90,6 +90,7 @@ + diff --git a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs index 163dd7fbe9..9eb84421d4 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapGroup.cs @@ -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) diff --git a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs b/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs index e6bf08eb9f..d8ba1e9195 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapPanel.cs @@ -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; diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs index 85daefea57..9bb7b5c737 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetHeader.cs @@ -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(); + } + } } } diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 578b02d90a..f6b832d0f7 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); @@ -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; diff --git a/osu.Game/Screens/Select/FilterCriteria.cs b/osu.Game/Screens/Select/FilterCriteria.cs index c1355bfa63..f410c69212 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.Value = !canConvert(panel.Beatmap); + switch (g.State) { case BeatmapGroupState.Hidden: diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index cf08ab273e..7fb6a82981 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using System.Linq; using System.Threading; using OpenTK; using OpenTK.Input; @@ -309,14 +308,7 @@ namespace osu.Game.Screens.Select carousel.Filter(criteria, debounce); } - private void onBeatmapSetAdded(BeatmapSetInfo s) - { - Schedule(() => - { - carousel.UpdateBeatmapSet(s); - carousel.SelectBeatmap(s.Beatmaps.First()); - }); - } + private void onBeatmapSetAdded(BeatmapSetInfo s) => Schedule(() => carousel.UpdateBeatmapSet(s)); private void onBeatmapSetRemoved(BeatmapSetInfo s) => Schedule(() => removeBeatmapSet(s)); 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" />