diff --git a/osu.Game/Localisation/SongSelectStrings.cs b/osu.Game/Localisation/SongSelectStrings.cs index 12f70cd967..e1ac328420 100644 --- a/osu.Game/Localisation/SongSelectStrings.cs +++ b/osu.Game/Localisation/SongSelectStrings.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using osu.Framework.Localisation; diff --git a/osu.Game/Screens/Select/BeatmapCarousel.cs b/osu.Game/Screens/Select/BeatmapCarousel.cs index 774ecc2c9c..68d3247275 100644 --- a/osu.Game/Screens/Select/BeatmapCarousel.cs +++ b/osu.Game/Screens/Select/BeatmapCarousel.cs @@ -49,6 +49,11 @@ namespace osu.Game.Screens.Select /// public Action? BeatmapSetsChanged; + /// + /// Triggered after filter conditions have finished being applied to the model hierarchy. + /// + public Action? FilterApplied; + /// /// The currently selected beatmap. /// @@ -56,6 +61,11 @@ namespace osu.Game.Screens.Select private CarouselBeatmap? selectedBeatmap => selectedBeatmapSet?.Beatmaps.FirstOrDefault(s => s.State.Value == CarouselItemState.Selected); + /// + /// The total count of non-filtered beatmaps displayed. + /// + public int CountDisplayed => beatmapSets.Where(s => !s.Filtered.Value).Sum(s => s.Beatmaps.Count(b => !b.Filtered.Value)); + /// /// The currently selected beatmap set. /// @@ -639,6 +649,8 @@ namespace osu.Game.Screens.Select if (alwaysResetScrollPosition || !Scroll.UserScrolling) ScrollToSelected(true); + + FilterApplied?.Invoke(); } } diff --git a/osu.Game/Screens/Select/FilterControl.cs b/osu.Game/Screens/Select/FilterControl.cs index 2f21ffbe6a..b44ca8ac04 100644 --- a/osu.Game/Screens/Select/FilterControl.cs +++ b/osu.Game/Screens/Select/FilterControl.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; +using osu.Framework.Localisation; using osu.Game.Collections; using osu.Game.Configuration; using osu.Game.Graphics; @@ -27,20 +28,27 @@ namespace osu.Game.Screens.Select { public partial class FilterControl : Container { - public const float HEIGHT = 2 * side_margin + 85; - private const float side_margin = 20; + public const float HEIGHT = 2 * side_margin + 120; + + private const float side_margin = 10; public Action FilterChanged; public Bindable CurrentTextSearch => searchTextBox.Current; + public LocalisableString InformationalText + { + get => searchTextBox.FilterText.Text; + set => searchTextBox.FilterText.Text = value; + } + private OsuTabControl sortTabs; private Bindable sortMode; private Bindable groupMode; - private SeekLimitedSearchTextBox searchTextBox; + private FilterControlTextBox searchTextBox; private CollectionDropdown collectionDropdown; @@ -99,72 +107,63 @@ namespace osu.Game.Screens.Select { RelativeSizeAxes = Axes.Both, Spacing = new Vector2(0, 5), - Children = new[] + Children = new Drawable[] { - new Container + searchTextBox = new FilterControlTextBox { RelativeSizeAxes = Axes.X, - Height = 60, - Children = new Drawable[] + }, + new Box + { + RelativeSizeAxes = Axes.X, + Height = 1, + Colour = OsuColour.Gray(80), + }, + new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ColumnDimensions = new[] { - searchTextBox = new SeekLimitedSearchTextBox { RelativeSizeAxes = Axes.X }, - new Box + new Dimension(GridSizeMode.AutoSize), + new Dimension(GridSizeMode.Absolute, OsuTabControl.HORIZONTAL_SPACING), + new Dimension(), + new Dimension(GridSizeMode.Absolute, OsuTabControl.HORIZONTAL_SPACING), + new Dimension(GridSizeMode.AutoSize), + }, + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, + Content = new[] + { + new[] { - RelativeSizeAxes = Axes.X, - Height = 1, - Colour = OsuColour.Gray(80), - Origin = Anchor.BottomLeft, - Anchor = Anchor.BottomLeft, - }, - new GridContainer - { - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - ColumnDimensions = new[] + new OsuSpriteText { - new Dimension(GridSizeMode.AutoSize), - new Dimension(GridSizeMode.Absolute, OsuTabControl.HORIZONTAL_SPACING), - new Dimension(), - new Dimension(GridSizeMode.Absolute, OsuTabControl.HORIZONTAL_SPACING), - new Dimension(GridSizeMode.AutoSize), + Text = SortStrings.Default, + Font = OsuFont.GetFont(size: 14), + Margin = new MarginPadding(5), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, }, - RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, - Content = new[] + Empty(), + sortTabs = new OsuTabControl { - new[] - { - new OsuSpriteText - { - Text = SortStrings.Default, - Font = OsuFont.GetFont(size: 14), - Margin = new MarginPadding(5), - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - }, - Empty(), - sortTabs = new OsuTabControl - { - RelativeSizeAxes = Axes.X, - Height = 24, - AutoSort = true, - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - AccentColour = colours.GreenLight, - Current = { BindTarget = sortMode } - }, - Empty(), - new OsuTabControlCheckbox - { - Text = "Show converted", - Current = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), - Anchor = Anchor.BottomRight, - Origin = Anchor.BottomRight, - }, - } - } - }, + RelativeSizeAxes = Axes.X, + Height = 24, + AutoSort = true, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + AccentColour = colours.GreenLight, + Current = { BindTarget = sortMode } + }, + Empty(), + new OsuTabControlCheckbox + { + Text = "Show converted", + Current = config.GetBindable(OsuSetting.ShowConvertedBeatmaps), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + } } }, new Container @@ -248,5 +247,32 @@ namespace osu.Game.Screens.Select protected override bool OnClick(ClickEvent e) => true; protected override bool OnHover(HoverEvent e) => true; + + private partial class FilterControlTextBox : SeekLimitedSearchTextBox + { + private const float filter_text_size = 12; + + public OsuSpriteText FilterText { get; private set; } + + public FilterControlTextBox() + { + Height += filter_text_size; + TextContainer.Margin = new MarginPadding { Bottom = filter_text_size }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + TextContainer.Add(FilterText = new OsuSpriteText + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.TopLeft, + Depth = float.MinValue, + Font = OsuFont.Default.With(size: filter_text_size, weight: FontWeight.SemiBold), + Margin = new MarginPadding { Top = 2, Left = 2 }, + Colour = colours.Yellow + }); + } + } } } diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 661eec8e97..c5e914b461 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -162,6 +162,7 @@ namespace osu.Game.Screens.Select BleedBottom = Footer.HEIGHT, SelectionChanged = updateSelectedBeatmap, BeatmapSetsChanged = carouselBeatmapsLoaded, + FilterApplied = updateVisibleBeatmapCount, GetRecommendedBeatmap = s => recommender?.GetRecommendedBeatmap(s), }, c => carouselContainer.Child = c); @@ -828,6 +829,7 @@ namespace osu.Game.Screens.Select private void carouselBeatmapsLoaded() { bindBindables(); + updateVisibleBeatmapCount(); Carousel.AllowSelection = true; @@ -857,6 +859,15 @@ namespace osu.Game.Screens.Select } } + private void updateVisibleBeatmapCount() + { + FilterControl.InformationalText = Carousel.CountDisplayed == 1 + // Intentionally not localised until we have proper support for this (see https://github.com/ppy/osu-framework/pull/4918 + // but also in this case we want support for formatting a number within a string). + ? $"{Carousel.CountDisplayed:#,0} matching beatmap" + : $"{Carousel.CountDisplayed:#,0} matching beatmaps"; + } + private bool boundLocalBindables; private void bindBindables()