2020-02-19 14:40:54 +00: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.
using System.Linq ;
using osu.Framework.Allocation ;
using osu.Framework.Extensions.Color4Extensions ;
using osu.Framework.Graphics ;
using osu.Framework.Graphics.Containers ;
using osu.Framework.Graphics.Effects ;
using osu.Framework.Graphics.Shapes ;
2020-02-19 15:17:02 +00:00
using osu.Framework.Graphics.Sprites ;
using osu.Framework.Graphics.Textures ;
2020-02-19 23:54:35 +00:00
using osu.Framework.Threading ;
2020-02-19 14:40:54 +00:00
using osu.Game.Audio ;
using osu.Game.Beatmaps ;
using osu.Game.Graphics.Containers ;
using osu.Game.Graphics.Sprites ;
2020-02-21 07:13:24 +00:00
using osu.Game.Graphics.UserInterface ;
2020-02-19 14:40:54 +00:00
using osu.Game.Online.API.Requests ;
using osu.Game.Overlays.BeatmapListing ;
using osu.Game.Overlays.Direct ;
using osu.Game.Rulesets ;
using osuTK ;
using osuTK.Graphics ;
namespace osu.Game.Overlays
{
public class BeatmapListingOverlay : FullscreenOverlay
{
[Resolved]
private PreviewTrackManager previewTrackManager { get ; set ; }
[Resolved]
private RulesetStore rulesets { get ; set ; }
private SearchBeatmapSetsRequest getSetsRequest ;
2020-02-19 15:17:02 +00:00
2020-02-20 02:08:42 +00:00
private Drawable currentContent ;
2020-02-19 14:40:54 +00:00
private BeatmapListingSearchSection searchSection ;
private BeatmapListingSortTabControl sortControl ;
public BeatmapListingOverlay ( )
: base ( OverlayColourScheme . Blue )
{
}
[BackgroundDependencyLoader]
private void load ( )
{
Children = new Drawable [ ]
{
new Box
{
RelativeSizeAxes = Axes . Both ,
Colour = ColourProvider . Background6
} ,
new BasicScrollContainer
{
RelativeSizeAxes = Axes . Both ,
ScrollbarVisible = false ,
Child = new ReverseChildIDFillFlowContainer < Drawable >
{
AutoSizeAxes = Axes . Y ,
RelativeSizeAxes = Axes . X ,
Direction = FillDirection . Vertical ,
Spacing = new Vector2 ( 0 , 10 ) ,
Children = new Drawable [ ]
{
new FillFlowContainer
{
AutoSizeAxes = Axes . Y ,
RelativeSizeAxes = Axes . X ,
Direction = FillDirection . Vertical ,
Masking = true ,
EdgeEffect = new EdgeEffectParameters
{
Colour = Color4 . Black . Opacity ( 0.25f ) ,
Type = EdgeEffectType . Shadow ,
Radius = 3 ,
Offset = new Vector2 ( 0f , 1f ) ,
} ,
Children = new Drawable [ ]
{
new BeatmapListingHeader ( ) ,
searchSection = new BeatmapListingSearchSection ( ) ,
}
} ,
new Container
{
AutoSizeAxes = Axes . Y ,
RelativeSizeAxes = Axes . X ,
Children = new Drawable [ ]
{
new Box
{
RelativeSizeAxes = Axes . Both ,
Colour = ColourProvider . Background4 ,
} ,
new FillFlowContainer
{
RelativeSizeAxes = Axes . X ,
AutoSizeAxes = Axes . Y ,
Children = new Drawable [ ]
{
new Container
{
RelativeSizeAxes = Axes . X ,
Height = 40 ,
Children = new Drawable [ ]
{
new Box
{
RelativeSizeAxes = Axes . Both ,
Colour = ColourProvider . Background5
} ,
sortControl = new BeatmapListingSortTabControl
{
Anchor = Anchor . CentreLeft ,
Origin = Anchor . CentreLeft ,
Margin = new MarginPadding { Left = 20 }
}
}
} ,
2020-02-21 07:13:24 +00:00
new Container
2020-02-19 14:40:54 +00:00
{
AutoSizeAxes = Axes . Y ,
RelativeSizeAxes = Axes . X ,
2020-02-19 15:17:02 +00:00
Padding = new MarginPadding { Horizontal = 20 } ,
2020-02-21 07:13:24 +00:00
Children = new Drawable [ ]
{
panelTarget = new Container
{
AutoSizeAxes = Axes . Y ,
RelativeSizeAxes = Axes . X ,
} ,
loadingLayer = new LoadingLayer ( panelTarget ) ,
}
} ,
2020-02-19 14:40:54 +00:00
}
}
}
}
}
}
}
} ;
}
protected override void LoadComplete ( )
{
base . LoadComplete ( ) ;
var sortCriteria = sortControl . Current ;
var sortDirection = sortControl . SortDirection ;
searchSection . Query . BindValueChanged ( query = >
{
sortCriteria . Value = string . IsNullOrEmpty ( query . NewValue ) ? DirectSortCriteria . Ranked : DirectSortCriteria . Relevance ;
sortDirection . Value = SortDirection . Descending ;
2020-02-19 23:54:35 +00:00
queueUpdateSearch ( true ) ;
2020-02-19 14:40:54 +00:00
} ) ;
2020-02-19 23:54:35 +00:00
searchSection . Ruleset . BindValueChanged ( _ = > queueUpdateSearch ( ) ) ;
searchSection . Category . BindValueChanged ( _ = > queueUpdateSearch ( ) ) ;
sortCriteria . BindValueChanged ( _ = > queueUpdateSearch ( ) ) ;
sortDirection . BindValueChanged ( _ = > queueUpdateSearch ( ) ) ;
}
private ScheduledDelegate queryChangedDebounce ;
2020-02-21 07:13:24 +00:00
private LoadingLayer loadingLayer ;
private Container panelTarget ;
2020-02-19 23:54:35 +00:00
private void queueUpdateSearch ( bool queryTextChanged = false )
{
getSetsRequest ? . Cancel ( ) ;
queryChangedDebounce ? . Cancel ( ) ;
queryChangedDebounce = Scheduler . AddDelayed ( updateSearch , queryTextChanged ? 500 : 100 ) ;
2020-02-19 14:40:54 +00:00
}
private void updateSearch ( )
{
if ( ! IsLoaded )
return ;
if ( State . Value = = Visibility . Hidden )
return ;
if ( API = = null )
return ;
previewTrackManager . StopAnyPlaying ( this ) ;
2020-02-21 07:13:24 +00:00
loadingLayer . Show ( ) ;
2020-02-19 14:40:54 +00:00
getSetsRequest = new SearchBeatmapSetsRequest (
searchSection . Query . Value ,
searchSection . Ruleset . Value ,
searchSection . Category . Value ,
sortControl . Current . Value ,
sortControl . SortDirection . Value ) ;
getSetsRequest . Success + = response = > Schedule ( ( ) = > recreatePanels ( response ) ) ;
API . Queue ( getSetsRequest ) ;
}
private void recreatePanels ( SearchBeatmapSetsResponse response )
{
2020-02-19 23:43:13 +00:00
if ( response . Total = = 0 )
2020-02-19 14:40:54 +00:00
{
searchSection . BeatmapSet = null ;
2020-02-19 23:54:35 +00:00
LoadComponentAsync ( new NotFoundDrawable ( ) , addContentToPlaceholder ) ;
2020-02-19 14:40:54 +00:00
return ;
}
2020-02-19 15:17:02 +00:00
var beatmaps = response . BeatmapSets . Select ( r = > r . ToBeatmapSet ( rulesets ) ) . ToList ( ) ;
2020-02-19 14:40:54 +00:00
var newPanels = new FillFlowContainer < DirectPanel >
{
RelativeSizeAxes = Axes . X ,
AutoSizeAxes = Axes . Y ,
Spacing = new Vector2 ( 10 ) ,
Alpha = 0 ,
2020-02-19 15:17:02 +00:00
Margin = new MarginPadding { Vertical = 15 } ,
2020-02-19 14:40:54 +00:00
ChildrenEnumerable = beatmaps . Select < BeatmapSetInfo , DirectPanel > ( b = > new DirectGridPanel ( b )
{
Anchor = Anchor . TopCentre ,
Origin = Anchor . TopCentre ,
} )
} ;
LoadComponentAsync ( newPanels , loaded = >
{
2020-02-19 15:17:02 +00:00
addContentToPlaceholder ( loaded ) ;
2020-02-19 14:40:54 +00:00
searchSection . BeatmapSet = beatmaps . First ( ) ;
2020-02-19 23:54:35 +00:00
} ) ;
2020-02-19 15:17:02 +00:00
}
private void addContentToPlaceholder ( Drawable content )
{
2020-02-21 07:13:24 +00:00
loadingLayer . Hide ( ) ;
2020-02-20 04:48:34 +00:00
Drawable lastContent = currentContent ;
if ( lastContent ! = null )
{
lastContent . FadeOut ( 100 , Easing . OutQuint ) . Expire ( ) ;
// 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 ) ;
}
2020-02-21 07:13:24 +00:00
panelTarget . Add ( currentContent = content ) ;
2020-02-20 02:08:42 +00:00
currentContent . FadeIn ( 200 , Easing . OutQuint ) ;
2020-02-19 15:17:02 +00:00
}
protected override void Dispose ( bool isDisposing )
{
getSetsRequest ? . Cancel ( ) ;
2020-02-19 23:54:35 +00:00
queryChangedDebounce ? . Cancel ( ) ;
2020-02-19 15:17:02 +00:00
base . Dispose ( isDisposing ) ;
}
2020-02-19 23:43:13 +00:00
private class NotFoundDrawable : CompositeDrawable
2020-02-19 15:17:02 +00:00
{
2020-02-19 23:43:13 +00:00
public NotFoundDrawable ( )
2020-02-19 15:17:02 +00:00
{
RelativeSizeAxes = Axes . X ;
Height = 250 ;
2020-02-20 02:08:42 +00:00
Alpha = 0 ;
2020-02-19 15:17:02 +00: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 ,
2020-02-19 23:43:13 +00:00
Text = @"... nope, nothing found." ,
2020-02-19 15:17:02 +00:00
}
}
} ) ;
}
2020-02-19 14:40:54 +00:00
}
}
}