From 58560f8acfe0259795358e969ddee6ca0600d2ad Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Feb 2025 17:11:09 +0900 Subject: [PATCH 1/2] Add tracking of expansion states for groups and sets --- .../SongSelect/BeatmapCarouselV2TestScene.cs | 3 +- osu.Game/Screens/SelectV2/BeatmapCarousel.cs | 38 ++++++++++++------- .../SelectV2/BeatmapCarouselFilterGrouping.cs | 20 +++++----- osu.Game/Screens/SelectV2/CarouselItem.cs | 7 +++- 4 files changed, 42 insertions(+), 26 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs b/osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs index f7be5f12e8..72c9611fdb 100644 --- a/osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs +++ b/osu.Game.Tests/Visual/SongSelect/BeatmapCarouselV2TestScene.cs @@ -153,7 +153,8 @@ namespace osu.Game.Tests.Visual.SongSelect var groupingFilter = Carousel.Filters.OfType().Single(); GroupDefinition g = groupingFilter.GroupItems.Keys.ElementAt(group); - CarouselItem item = groupingFilter.GroupItems[g].ElementAt(panel); + // offset by one because the group itself is included in the items list. + CarouselItem item = groupingFilter.GroupItems[g].ElementAt(panel + 1); return ReferenceEquals(Carousel.CurrentSelection, item.Model); }); diff --git a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs index 858888c517..9f62780dda 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarousel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarousel.cs @@ -105,12 +105,12 @@ namespace osu.Game.Screens.SelectV2 // Special case – collapsing an open group. if (lastSelectedGroup == group) { - setVisibilityOfGroupItems(lastSelectedGroup, false); + setExpansionStateOfGroup(lastSelectedGroup, false); lastSelectedGroup = null; return false; } - setVisibleGroup(group); + setExpandedGroup(group); return false; case BeatmapSetInfo setInfo: @@ -127,11 +127,11 @@ namespace osu.Game.Screens.SelectV2 GroupDefinition? group = grouping.GroupItems.SingleOrDefault(kvp => kvp.Value.Any(i => ReferenceEquals(i.Model, beatmapInfo))).Key; if (group != null) - setVisibleGroup(group); + setExpandedGroup(group); } else { - setVisibleSet(beatmapInfo); + setExpandedSet(beatmapInfo); } return true; @@ -158,37 +158,47 @@ namespace osu.Game.Screens.SelectV2 } } - private void setVisibleGroup(GroupDefinition group) + private void setExpandedGroup(GroupDefinition group) { if (lastSelectedGroup != null) - setVisibilityOfGroupItems(lastSelectedGroup, false); + setExpansionStateOfGroup(lastSelectedGroup, false); lastSelectedGroup = group; - setVisibilityOfGroupItems(group, true); + setExpansionStateOfGroup(group, true); } - private void setVisibilityOfGroupItems(GroupDefinition group, bool visible) + private void setExpansionStateOfGroup(GroupDefinition group, bool expanded) { if (grouping.GroupItems.TryGetValue(group, out var items)) { foreach (var i in items) - i.IsVisible = visible; + { + if (i.Model is GroupDefinition) + i.IsExpanded = expanded; + else + i.IsVisible = expanded; + } } } - private void setVisibleSet(BeatmapInfo beatmapInfo) + private void setExpandedSet(BeatmapInfo beatmapInfo) { if (lastSelectedBeatmap != null) - setVisibilityOfSetItems(lastSelectedBeatmap.BeatmapSet!, false); + setExpansionStateOfSetItems(lastSelectedBeatmap.BeatmapSet!, false); lastSelectedBeatmap = beatmapInfo; - setVisibilityOfSetItems(beatmapInfo.BeatmapSet!, true); + setExpansionStateOfSetItems(beatmapInfo.BeatmapSet!, true); } - private void setVisibilityOfSetItems(BeatmapSetInfo set, bool visible) + private void setExpansionStateOfSetItems(BeatmapSetInfo set, bool expanded) { if (grouping.SetItems.TryGetValue(set, out var items)) { foreach (var i in items) - i.IsVisible = visible; + { + if (i.Model is BeatmapSetInfo) + i.IsExpanded = expanded; + else + i.IsVisible = expanded; + } } } diff --git a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs index ea737d8b7f..e4160cc0fa 100644 --- a/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs +++ b/osu.Game/Screens/SelectV2/BeatmapCarouselFilterGrouping.cs @@ -65,7 +65,11 @@ namespace osu.Game.Screens.SelectV2 if (b.StarRating > starGroup) { starGroup = (int)Math.Floor(b.StarRating); - newItems.Add(new CarouselItem(new GroupDefinition($"{starGroup} - {++starGroup} *")) { DrawHeight = GroupPanel.HEIGHT }); + var groupDefinition = new GroupDefinition($"{starGroup} - {++starGroup} *"); + var groupItem = new CarouselItem(groupDefinition) { DrawHeight = GroupPanel.HEIGHT }; + + newItems.Add(groupItem); + groupItems[groupDefinition] = new HashSet { groupItem }; } newItems.Add(item); @@ -91,14 +95,13 @@ namespace osu.Game.Screens.SelectV2 if (newBeatmapSet) { - newItems.Insert(i, new CarouselItem(beatmap.BeatmapSet!) { DrawHeight = BeatmapSetPanel.HEIGHT }); + var setItem = new CarouselItem(beatmap.BeatmapSet!) { DrawHeight = BeatmapSetPanel.HEIGHT }; + setItems[beatmap.BeatmapSet!] = new HashSet { setItem }; + newItems.Insert(i, setItem); i++; } - if (!setItems.TryGetValue(beatmap.BeatmapSet!, out var related)) - setItems[beatmap.BeatmapSet!] = related = new HashSet(); - - related.Add(item); + setItems[beatmap.BeatmapSet!].Add(item); item.IsVisible = false; } @@ -121,10 +124,7 @@ namespace osu.Game.Screens.SelectV2 if (lastGroup != null) { - if (!groupItems.TryGetValue(lastGroup, out var groupRelated)) - groupItems[lastGroup] = groupRelated = new HashSet(); - groupRelated.Add(item); - + groupItems[lastGroup].Add(item); item.IsVisible = false; } } diff --git a/osu.Game/Screens/SelectV2/CarouselItem.cs b/osu.Game/Screens/SelectV2/CarouselItem.cs index 13d5c840cf..32be33e99a 100644 --- a/osu.Game/Screens/SelectV2/CarouselItem.cs +++ b/osu.Game/Screens/SelectV2/CarouselItem.cs @@ -30,10 +30,15 @@ namespace osu.Game.Screens.SelectV2 public float DrawHeight { get; set; } = DEFAULT_HEIGHT; /// - /// Whether this item is visible or collapsed (hidden). + /// Whether this item is visible or hidden. /// public bool IsVisible { get; set; } = true; + /// + /// Whether this item is expanded or not. Should only be used for headers of groups. + /// + public bool IsExpanded { get; set; } + public CarouselItem(object model) { Model = model; From 599b59cb1447467048bda41105956bd0c532863e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 4 Feb 2025 17:16:36 +0900 Subject: [PATCH 2/2] Add expanded state to sample drawable representations --- ...estSceneBeatmapCarouselV2GroupSelection.cs | 25 ++++++++++++++++++- osu.Game/Screens/SelectV2/BeatmapPanel.cs | 1 + osu.Game/Screens/SelectV2/BeatmapSetPanel.cs | 9 ++++++- osu.Game/Screens/SelectV2/Carousel.cs | 2 ++ osu.Game/Screens/SelectV2/GroupPanel.cs | 10 +++++++- osu.Game/Screens/SelectV2/ICarouselPanel.cs | 7 +++++- 6 files changed, 50 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2GroupSelection.cs b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2GroupSelection.cs index 04ca0a9085..f4d97be5a5 100644 --- a/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2GroupSelection.cs +++ b/osu.Game.Tests/Visual/SongSelect/TestSceneBeatmapCarouselV2GroupSelection.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Visual.SongSelect } [Test] - public void TestOpenCloseGroupWithNoSelection() + public void TestOpenCloseGroupWithNoSelectionMouse() { AddBeatmaps(10, 5); WaitForDrawablePanels(); @@ -41,6 +41,29 @@ namespace osu.Game.Tests.Visual.SongSelect CheckNoSelection(); } + [Test] + public void TestOpenCloseGroupWithNoSelectionKeyboard() + { + AddBeatmaps(10, 5); + WaitForDrawablePanels(); + + AddAssert("no beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); + CheckNoSelection(); + + SelectNextPanel(); + Select(); + AddUntilStep("some beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.GreaterThan(0)); + AddAssert("keyboard selected is expanded", () => getKeyboardSelectedPanel()?.Expanded.Value, () => Is.True); + CheckNoSelection(); + + Select(); + AddUntilStep("no beatmaps visible", () => Carousel.ChildrenOfType().Count(p => p.Alpha > 0), () => Is.Zero); + AddAssert("keyboard selected is collapsed", () => getKeyboardSelectedPanel()?.Expanded.Value, () => Is.False); + CheckNoSelection(); + + GroupPanel? getKeyboardSelectedPanel() => Carousel.ChildrenOfType().SingleOrDefault(p => p.KeyboardSelected.Value); + } + [Test] public void TestCarouselRemembersSelection() { diff --git a/osu.Game/Screens/SelectV2/BeatmapPanel.cs b/osu.Game/Screens/SelectV2/BeatmapPanel.cs index 4a9e406def..3edfd4203b 100644 --- a/osu.Game/Screens/SelectV2/BeatmapPanel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapPanel.cs @@ -100,6 +100,7 @@ namespace osu.Game.Screens.SelectV2 public CarouselItem? Item { get; set; } public BindableBool Selected { get; } = new BindableBool(); + public BindableBool Expanded { get; } = new BindableBool(); public BindableBool KeyboardSelected { get; } = new BindableBool(); public double DrawYPosition { get; set; } diff --git a/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs b/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs index 06e3ad3426..79ffe0f68a 100644 --- a/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs +++ b/osu.Game/Screens/SelectV2/BeatmapSetPanel.cs @@ -25,6 +25,7 @@ namespace osu.Game.Screens.SelectV2 private BeatmapCarousel carousel { get; set; } = null!; private OsuSpriteText text = null!; + private Box box = null!; [BackgroundDependencyLoader] private void load() @@ -34,7 +35,7 @@ namespace osu.Game.Screens.SelectV2 InternalChildren = new Drawable[] { - new Box + box = new Box { Colour = Color4.Yellow.Darken(5), Alpha = 0.8f, @@ -48,6 +49,11 @@ namespace osu.Game.Screens.SelectV2 } }; + Expanded.BindValueChanged(value => + { + box.FadeColour(value.NewValue ? Color4.Yellow.Darken(2) : Color4.Yellow.Darken(5), 500, Easing.OutQuint); + }); + KeyboardSelected.BindValueChanged(value => { if (value.NewValue) @@ -85,6 +91,7 @@ namespace osu.Game.Screens.SelectV2 public CarouselItem? Item { get; set; } public BindableBool Selected { get; } = new BindableBool(); + public BindableBool Expanded { get; } = new BindableBool(); public BindableBool KeyboardSelected { get; } = new BindableBool(); public double DrawYPosition { get; set; } diff --git a/osu.Game/Screens/SelectV2/Carousel.cs b/osu.Game/Screens/SelectV2/Carousel.cs index a1bafac620..608ef207d9 100644 --- a/osu.Game/Screens/SelectV2/Carousel.cs +++ b/osu.Game/Screens/SelectV2/Carousel.cs @@ -571,6 +571,7 @@ namespace osu.Game.Screens.SelectV2 c.Selected.Value = c.Item == currentSelection?.CarouselItem; c.KeyboardSelected.Value = c.Item == currentKeyboardSelection?.CarouselItem; + c.Expanded.Value = c.Item.IsExpanded; } } @@ -674,6 +675,7 @@ namespace osu.Game.Screens.SelectV2 carouselPanel.Item = null; carouselPanel.Selected.Value = false; carouselPanel.KeyboardSelected.Value = false; + carouselPanel.Expanded.Value = false; } #endregion diff --git a/osu.Game/Screens/SelectV2/GroupPanel.cs b/osu.Game/Screens/SelectV2/GroupPanel.cs index 882d77cb8d..7ed256ca6a 100644 --- a/osu.Game/Screens/SelectV2/GroupPanel.cs +++ b/osu.Game/Screens/SelectV2/GroupPanel.cs @@ -26,6 +26,8 @@ namespace osu.Game.Screens.SelectV2 private Box activationFlash = null!; private OsuSpriteText text = null!; + private Box box = null!; + [BackgroundDependencyLoader] private void load() { @@ -34,7 +36,7 @@ namespace osu.Game.Screens.SelectV2 InternalChildren = new Drawable[] { - new Box + box = new Box { Colour = Color4.DarkBlue.Darken(5), Alpha = 0.8f, @@ -60,6 +62,11 @@ namespace osu.Game.Screens.SelectV2 activationFlash.FadeTo(value.NewValue ? 0.2f : 0, 500, Easing.OutQuint); }); + Expanded.BindValueChanged(value => + { + box.FadeColour(value.NewValue ? Color4.SkyBlue : Color4.DarkBlue.Darken(5), 500, Easing.OutQuint); + }); + KeyboardSelected.BindValueChanged(value => { if (value.NewValue) @@ -97,6 +104,7 @@ namespace osu.Game.Screens.SelectV2 public CarouselItem? Item { get; set; } public BindableBool Selected { get; } = new BindableBool(); + public BindableBool Expanded { get; } = new BindableBool(); public BindableBool KeyboardSelected { get; } = new BindableBool(); public double DrawYPosition { get; set; } diff --git a/osu.Game/Screens/SelectV2/ICarouselPanel.cs b/osu.Game/Screens/SelectV2/ICarouselPanel.cs index a956bb22a3..4fba0d2827 100644 --- a/osu.Game/Screens/SelectV2/ICarouselPanel.cs +++ b/osu.Game/Screens/SelectV2/ICarouselPanel.cs @@ -14,10 +14,15 @@ namespace osu.Game.Screens.SelectV2 public interface ICarouselPanel { /// - /// Whether this item has selection. Should be read from to update the visual state. + /// Whether this item has selection (see ). Should be read from to update the visual state. /// BindableBool Selected { get; } + /// + /// Whether this item is expanded (see ). Should be read from to update the visual state. + /// + BindableBool Expanded { get; } + /// /// Whether this item has keyboard selection. Should be read from to update the visual state. ///