2020-02-19 22:40:54 +08:00
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
2020-03-06 08:09:43 +08:00
using System.Collections.Generic ;
2020-02-19 22:40:54 +08:00
using System.Linq ;
2020-03-06 08:09:43 +08:00
using System.Threading ;
2020-05-14 14:35:11 +08:00
using System.Threading.Tasks ;
2020-02-19 22:40:54 +08:00
using osu.Framework.Allocation ;
2020-05-14 14:35:11 +08:00
using osu.Framework.Extensions.IEnumerableExtensions ;
2021-06-22 13:53:21 +08:00
using osu.Framework.Localisation ;
2020-02-19 22:40:54 +08:00
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
using osu.Framework.Graphics.Shapes ;
2020-02-19 23:17:02 +08:00
using osu.Framework.Graphics.Sprites ;
using osu.Framework.Graphics.Textures ;
2020-04-21 14:47:43 +08:00
using osu.Framework.Input.Events ;
2020-02-19 22:40:54 +08:00
using osu.Game.Audio ;
using osu.Game.Beatmaps ;
2021-06-19 20:54:24 +08:00
using osu.Game.Graphics ;
2020-02-19 22:40:54 +08:00
using osu.Game.Graphics.Sprites ;
2021-06-19 20:54:24 +08:00
using osu.Game.Graphics.Containers ;
2020-02-19 22:40:54 +08:00
using osu.Game.Overlays.BeatmapListing ;
2020-04-21 15:03:18 +08:00
using osu.Game.Overlays.BeatmapListing.Panels ;
2021-06-16 12:46:13 +08:00
using osu.Game.Resources.Localisation.Web ;
2020-02-19 22:40:54 +08:00
using osuTK ;
2021-01-18 15:48:12 +08:00
using osuTK.Graphics ;
2020-02-19 22:40:54 +08:00
namespace osu.Game.Overlays
{
2021-01-18 16:13:38 +08:00
public class BeatmapListingOverlay : OnlineOverlay < BeatmapListingHeader >
2020-02-19 22:40:54 +08:00
{
[Resolved]
private PreviewTrackManager previewTrackManager { get ; set ; }
2020-02-20 10:08:42 +08:00
private Drawable currentContent ;
2020-03-06 08:09:43 +08:00
private Container panelTarget ;
2020-05-12 02:18:47 +08:00
private FillFlowContainer < BeatmapPanel > foundContent ;
private NotFoundDrawable notFoundContent ;
2021-06-19 20:54:24 +08:00
private SupporterRequiredDrawable supporterRequiredContent ;
2021-01-18 15:48:12 +08:00
private BeatmapListingFilterControl filterControl ;
2020-02-19 22:40:54 +08:00
public BeatmapListingOverlay ( )
2021-01-18 15:48:12 +08:00
: base ( OverlayColourScheme . Blue )
2020-02-19 22:40:54 +08:00
{
}
[BackgroundDependencyLoader]
private void load ( )
{
2021-01-18 15:48:12 +08:00
Child = new FillFlowContainer
2020-02-19 22:40:54 +08:00
{
2021-01-18 15:48:12 +08:00
RelativeSizeAxes = Axes . X ,
AutoSizeAxes = Axes . Y ,
Direction = FillDirection . Vertical ,
Children = new Drawable [ ]
2020-02-19 22:40:54 +08:00
{
2021-01-18 15:48:12 +08:00
filterControl = new BeatmapListingFilterControl
{
TypingStarted = onTypingStarted ,
SearchStarted = onSearchStarted ,
SearchFinished = onSearchFinished ,
} ,
new Container
2020-02-19 22:40:54 +08:00
{
AutoSizeAxes = Axes . Y ,
RelativeSizeAxes = Axes . X ,
Children = new Drawable [ ]
{
2021-01-18 15:48:12 +08:00
new Box
2020-02-19 22:40:54 +08:00
{
2021-01-18 15:48:12 +08:00
RelativeSizeAxes = Axes . Both ,
Colour = ColourProvider . Background4 ,
2020-02-19 22:40:54 +08:00
} ,
2021-01-18 15:48:12 +08:00
panelTarget = new Container
2020-02-19 22:40:54 +08:00
{
AutoSizeAxes = Axes . Y ,
RelativeSizeAxes = Axes . X ,
2021-01-18 15:48:12 +08:00
Padding = new MarginPadding { Horizontal = 20 } ,
2020-02-19 22:40:54 +08:00
Children = new Drawable [ ]
{
2021-01-18 15:48:12 +08:00
foundContent = new FillFlowContainer < BeatmapPanel > ( ) ,
notFoundContent = new NotFoundDrawable ( ) ,
2021-06-19 20:54:24 +08:00
supporterRequiredContent = new SupporterRequiredDrawable ( ) ,
2021-01-18 15:48:12 +08:00
}
}
} ,
2021-01-04 21:42:39 +08:00
} ,
2021-01-18 15:48:12 +08:00
}
2020-02-19 22:40:54 +08:00
} ;
}
2021-01-18 15:48:12 +08:00
protected override BeatmapListingHeader CreateHeader ( ) = > new BeatmapListingHeader ( ) ;
2021-02-09 17:32:44 +08:00
protected override Color4 BackgroundColour = > ColourProvider . Background6 ;
2021-01-18 15:48:12 +08:00
2020-11-10 15:26:30 +08:00
private void onTypingStarted ( )
{
// temporary until the textbox/header is updated to always stay on screen.
2021-01-18 15:48:12 +08:00
ScrollFlow . ScrollToStart ( ) ;
2020-11-10 15:26:30 +08:00
}
2020-04-21 14:47:43 +08:00
protected override void OnFocus ( FocusEvent e )
{
base . OnFocus ( e ) ;
filterControl . TakeFocus ( ) ;
}
2020-03-06 08:09:43 +08:00
private CancellationTokenSource cancellationToken ;
2020-02-20 07:54:35 +08:00
2020-03-06 08:09:43 +08:00
private void onSearchStarted ( )
2020-02-20 07:54:35 +08:00
{
2020-03-06 08:09:43 +08:00
cancellationToken ? . Cancel ( ) ;
2020-02-19 22:40:54 +08:00
previewTrackManager . StopAnyPlaying ( this ) ;
2020-03-06 08:09:43 +08:00
if ( panelTarget . Any ( ) )
2021-01-18 15:48:12 +08:00
Loading . Show ( ) ;
2020-02-19 22:40:54 +08:00
}
2020-05-14 14:35:11 +08:00
private Task panelLoadDelegate ;
2021-06-22 13:53:21 +08:00
private void onSearchFinished ( BeatmapListingFilterControl . SearchResult searchResult )
2020-02-19 22:40:54 +08:00
{
2021-06-27 02:31:26 +08:00
if ( searchResult . Type = = BeatmapListingFilterControl . SearchResultType . SupporterOnlyFilters )
2021-06-19 20:54:24 +08:00
{
2021-06-27 02:31:26 +08:00
supporterRequiredContent . UpdateText ( searchResult . SupporterOnlyFiltersUsed ) ;
2021-06-22 13:53:21 +08:00
addContentToPlaceholder ( supporterRequiredContent ) ;
2021-06-19 20:54:24 +08:00
return ;
}
2021-06-22 13:53:21 +08:00
var newPanels = searchResult . Results . Select < BeatmapSetInfo , BeatmapPanel > ( b = > new GridBeatmapPanel ( b )
{
Anchor = Anchor . TopCentre ,
Origin = Anchor . TopCentre ,
} ) ;
2020-05-14 14:35:11 +08:00
if ( filterControl . CurrentPage = = 0 )
2020-02-19 22:40:54 +08:00
{
2020-05-14 14:35:11 +08:00
//No matches case
if ( ! newPanels . Any ( ) )
{
2021-06-22 13:53:21 +08:00
addContentToPlaceholder ( notFoundContent ) ;
2020-05-14 14:35:11 +08:00
return ;
}
// spawn new children with the contained so we only clear old content at the last moment.
var content = new FillFlowContainer < BeatmapPanel >
2020-02-19 22:40:54 +08:00
{
2020-05-12 02:18:47 +08:00
RelativeSizeAxes = Axes . X ,
AutoSizeAxes = Axes . Y ,
Spacing = new Vector2 ( 10 ) ,
Alpha = 0 ,
Margin = new MarginPadding { Vertical = 15 } ,
2020-05-14 14:35:11 +08:00
ChildrenEnumerable = newPanels
2020-05-12 02:18:47 +08:00
} ;
2020-05-14 14:35:11 +08:00
panelLoadDelegate = LoadComponentAsync ( foundContent = content , addContentToPlaceholder , ( cancellationToken = new CancellationTokenSource ( ) ) . Token ) ;
2020-05-12 02:18:47 +08:00
}
else
{
2020-05-14 14:35:11 +08:00
panelLoadDelegate = LoadComponentsAsync ( newPanels , loaded = >
2020-05-12 02:18:47 +08:00
{
2020-05-14 14:35:11 +08:00
lastFetchDisplayedTime = Time . Current ;
foundContent . AddRange ( loaded ) ;
loaded . ForEach ( p = > p . FadeIn ( 200 , Easing . OutQuint ) ) ;
2020-05-12 02:18:47 +08:00
} ) ;
}
2020-02-19 23:17:02 +08:00
}
private void addContentToPlaceholder ( Drawable content )
{
2021-01-18 15:48:12 +08:00
Loading . Hide ( ) ;
2020-05-14 14:35:11 +08:00
lastFetchDisplayedTime = Time . Current ;
2020-02-21 15:13:24 +08:00
2021-01-26 03:11:50 +08:00
if ( content = = currentContent )
return ;
2020-03-06 08:09:43 +08:00
var lastContent = currentContent ;
2020-02-20 12:48:34 +08:00
if ( lastContent ! = null )
{
2021-01-26 14:42:48 +08:00
var transform = lastContent . FadeOut ( 100 , Easing . OutQuint ) ;
2020-02-20 12:48:34 +08:00
2021-06-22 13:53:21 +08:00
if ( lastContent = = notFoundContent | | lastContent = = supporterRequiredContent )
2021-01-26 03:18:23 +08:00
{
2021-06-27 02:40:54 +08:00
// the placeholders may be used multiple times, so don't expire/dispose them.
2021-01-26 14:42:48 +08:00
transform . Schedule ( ( ) = > panelTarget . Remove ( lastContent ) ) ;
}
else
{
// Consider the case when the new content is smaller than the last content.
// If the auto-size computation is delayed until fade out completes, the background remain high for too long making the resulting transition to the smaller height look weird.
// At the same time, if the last content's height is bypassed immediately, there is a period where the new content is at Alpha = 0 when the auto-sized height will be 0.
// To resolve both of these issues, the bypass is delayed until a point when the content transitions (fade-in and fade-out) overlap and it looks good to do so.
lastContent . Delay ( 25 ) . Schedule ( ( ) = > lastContent . BypassAutoSizeAxes = Axes . Y ) . Then ( ) . Schedule ( ( ) = > lastContent . Expire ( ) ) ;
}
2020-02-20 12:48:34 +08:00
}
2020-05-12 02:18:47 +08:00
if ( ! content . IsAlive )
panelTarget . Add ( content ) ;
2021-01-26 14:42:48 +08:00
content . FadeInFromZero ( 200 , Easing . OutQuint ) ;
2020-05-12 02:18:47 +08:00
currentContent = content ;
2020-02-19 23:17:02 +08:00
}
protected override void Dispose ( bool isDisposing )
{
2020-03-06 08:09:43 +08:00
cancellationToken ? . Cancel ( ) ;
2020-02-19 23:17:02 +08:00
base . Dispose ( isDisposing ) ;
}
2021-01-26 04:41:05 +08:00
public class NotFoundDrawable : CompositeDrawable
2020-02-19 23:17:02 +08:00
{
2020-02-20 07:43:13 +08:00
public NotFoundDrawable ( )
2020-02-19 23:17:02 +08:00
{
RelativeSizeAxes = Axes . X ;
Height = 250 ;
2020-02-20 10:08:42 +08:00
Alpha = 0 ;
2020-02-19 23:17:02 +08:00
Margin = new MarginPadding { Top = 15 } ;
}
[BackgroundDependencyLoader]
private void load ( TextureStore textures )
{
AddInternal ( new FillFlowContainer
{
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
RelativeSizeAxes = Axes . Y ,
AutoSizeAxes = Axes . X ,
Direction = FillDirection . Horizontal ,
Spacing = new Vector2 ( 10 , 0 ) ,
Children = new Drawable [ ]
{
new Sprite
{
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
RelativeSizeAxes = Axes . Both ,
FillMode = FillMode . Fit ,
Texture = textures . Get ( @"Online/not-found" )
} ,
new OsuSpriteText
{
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
2021-06-16 12:46:13 +08:00
Text = BeatmapsStrings . ListingSearchNotFoundQuote ,
2020-02-19 23:17:02 +08:00
}
}
} ) ;
}
2020-02-19 22:40:54 +08:00
}
2020-05-12 02:18:47 +08:00
2021-06-27 02:40:54 +08:00
// TODO: localisation requires Text/LinkFlowContainer support for localising strings with links inside
// (https://github.com/ppy/osu-framework/issues/4530)
2021-06-19 20:54:24 +08:00
public class SupporterRequiredDrawable : CompositeDrawable
{
2021-06-24 13:45:38 +08:00
private LinkFlowContainer supporterRequiredText ;
2021-06-20 21:23:54 +08:00
2021-06-19 20:54:24 +08:00
public SupporterRequiredDrawable ( )
{
RelativeSizeAxes = Axes . X ;
2021-06-20 17:17:07 +08:00
Height = 225 ;
2021-06-19 20:54:24 +08:00
Alpha = 0 ;
}
[BackgroundDependencyLoader]
private void load ( TextureStore textures )
{
AddInternal ( new FillFlowContainer
{
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
RelativeSizeAxes = Axes . Y ,
AutoSizeAxes = Axes . X ,
Direction = FillDirection . Horizontal ,
2021-06-24 13:45:38 +08:00
Children = new Drawable [ ]
2021-06-19 20:54:24 +08:00
{
new Sprite
{
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
RelativeSizeAxes = Axes . Both ,
FillMode = FillMode . Fit ,
Texture = textures . Get ( @"Online/supporter-required" ) ,
} ,
2021-06-24 13:45:38 +08:00
supporterRequiredText = new LinkFlowContainer
{
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
AutoSizeAxes = Axes . Both ,
Margin = new MarginPadding { Bottom = 10 } ,
} ,
2021-06-19 20:54:24 +08:00
}
} ) ;
}
2021-06-22 13:53:21 +08:00
public void UpdateText ( List < LocalisableString > filters )
2021-06-21 15:31:47 +08:00
{
2021-06-24 13:45:38 +08:00
supporterRequiredText . Clear ( ) ;
2021-06-19 20:54:24 +08:00
2021-06-24 13:45:38 +08:00
supporterRequiredText . AddText (
BeatmapsStrings . ListingSearchSupporterFilterQuoteDefault ( string . Join ( " and " , filters ) , "" ) . ToString ( ) ,
2021-06-22 13:53:21 +08:00
t = >
{
t . Font = OsuFont . GetFont ( size : 16 ) ;
t . Colour = Colour4 . White ;
}
2021-06-24 13:45:38 +08:00
) ;
2021-06-22 13:53:21 +08:00
supporterRequiredText . AddLink ( BeatmapsStrings . ListingSearchSupporterFilterQuoteLinkText . ToString ( ) , @"/store/products/supporter-tag" ) ;
2021-06-19 20:54:24 +08:00
}
}
2020-05-14 14:35:11 +08:00
private const double time_between_fetches = 500 ;
private double lastFetchDisplayedTime ;
2020-05-12 02:18:47 +08:00
protected override void Update ( )
{
base . Update ( ) ;
2020-05-14 14:35:11 +08:00
const int pagination_scroll_distance = 500 ;
bool shouldShowMore = panelLoadDelegate ? . IsCompleted ! = false
& & Time . Current - lastFetchDisplayedTime > time_between_fetches
2021-01-18 15:48:12 +08:00
& & ( ScrollFlow . ScrollableExtent > 0 & & ScrollFlow . IsScrolledToEnd ( pagination_scroll_distance ) ) ;
2020-05-14 14:35:11 +08:00
if ( shouldShowMore )
filterControl . FetchNextPage ( ) ;
2020-05-12 02:18:47 +08:00
}
2020-02-19 22:40:54 +08:00
}
}