Squash commits from private fork

Temporary comments left to-remove later
This commit is contained in:
「空白」 2020-05-12 03:18:47 +09:00
parent d5ae8f5ef4
commit 35e7cee458
6 changed files with 236 additions and 45 deletions

View File

@ -13,4 +13,13 @@ namespace osu.Game.Online.API.Requests
[JsonProperty("cursor")]
public dynamic CursorJson;
}
public abstract class ResponseWithCursor<T> : ResponseWithCursor where T : class
{
/// <summary>
/// Cursor deserialized into T class type (cannot implicitly convert type to object using raw Cursor)
/// </summary>
[JsonProperty("cursor")]
public T Cursor;
}
}

View File

@ -5,11 +5,21 @@ using osu.Framework.IO.Network;
using osu.Game.Overlays;
using osu.Game.Overlays.BeatmapListing;
using osu.Game.Rulesets;
using Newtonsoft.Json;
namespace osu.Game.Online.API.Requests
{
public class SearchBeatmapSetsRequest : APIRequest<SearchBeatmapSetsResponse>
{
public class Cursor
{
[JsonProperty("approved_date")]
public string ApprovedDate;
[JsonProperty("_id")]
public string Id;
}
public SearchCategory SearchCategory { get; set; }
public SortCriteria SortCriteria { get; set; }
@ -22,17 +32,20 @@ namespace osu.Game.Online.API.Requests
private readonly string query;
private readonly RulesetInfo ruleset;
private readonly Cursor cursor;
private string directionString => SortDirection == SortDirection.Descending ? @"desc" : @"asc";
public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset)
public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset, Cursor cursor = null,
SearchCategory searchCategory = SearchCategory.Any, SortCriteria sortCriteria = SortCriteria.Ranked, SortDirection sortDirection = SortDirection.Descending)
{
this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query);
this.ruleset = ruleset;
this.cursor = cursor;
SearchCategory = SearchCategory.Any;
SortCriteria = SortCriteria.Ranked;
SortDirection = SortDirection.Descending;
SearchCategory = searchCategory;
SortCriteria = sortCriteria;
SortDirection = sortDirection;
Genre = SearchGenre.Any;
Language = SearchLanguage.Any;
}
@ -55,6 +68,12 @@ namespace osu.Game.Online.API.Requests
req.AddParameter("sort", $"{SortCriteria.ToString().ToLowerInvariant()}_{directionString}");
if (cursor != null)
{
req.AddParameter("cursor[_id]", cursor.Id);
req.AddParameter("cursor[approved_date]", cursor.ApprovedDate);
}
return req;
}

View File

@ -7,7 +7,7 @@ using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
{
public class SearchBeatmapSetsResponse : ResponseWithCursor
public class SearchBeatmapSetsResponse : ResponseWithCursor<SearchBeatmapSetsRequest.Cursor>
{
[JsonProperty("beatmapsets")]
public IEnumerable<APIBeatmapSet> BeatmapSets;

View File

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
@ -13,7 +12,6 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Rulesets;
using osuTK;
using osuTK.Graphics;
@ -24,6 +22,8 @@ namespace osu.Game.Overlays.BeatmapListing
{
public Action<List<BeatmapSetInfo>> SearchFinished;
public Action SearchStarted;
/// <summary> List of currently displayed beatmap entries </summary>
private List<BeatmapSetInfo> currentBeatmaps;
[Resolved]
private IAPIProvider api { get; set; }
@ -35,7 +35,7 @@ namespace osu.Game.Overlays.BeatmapListing
private readonly BeatmapListingSortTabControl sortControl;
private readonly Box sortControlBackground;
private SearchBeatmapSetsRequest getSetsRequest;
private BeatmapListingPager beatmapListingPager;
public BeatmapListingFilterControl()
{
@ -115,12 +115,13 @@ namespace osu.Game.Overlays.BeatmapListing
}
private ScheduledDelegate queryChangedDebounce;
private ScheduledDelegate queryPagingDebounce;
private void queueUpdateSearch(bool queryTextChanged = false)
{
SearchStarted?.Invoke();
getSetsRequest?.Cancel();
beatmapListingPager?.Reset();
queryChangedDebounce?.Cancel();
queryChangedDebounce = Scheduler.AddDelayed(updateSearch, queryTextChanged ? 500 : 100);
@ -128,37 +129,55 @@ namespace osu.Game.Overlays.BeatmapListing
private void updateSearch()
{
getSetsRequest = new SearchBeatmapSetsRequest(searchControl.Query.Value, searchControl.Ruleset.Value)
{
SearchCategory = searchControl.Category.Value,
SortCriteria = sortControl.Current.Value,
SortDirection = sortControl.SortDirection.Value,
Genre = searchControl.Genre.Value,
Language = searchControl.Language.Value
};
beatmapListingPager = new BeatmapListingPager(
api,
rulesets,
searchControl.Query.Value,
searchControl.Ruleset.Value,
searchControl.Category.Value,
sortControl.Current.Value,
sortControl.SortDirection.Value
);
getSetsRequest.Success += response => Schedule(() => onSearchFinished(response));
queryPagingDebounce?.Cancel();
queryPagingDebounce = null;
beatmapListingPager.PageFetched += onSearchFinished;
api.Queue(getSetsRequest);
AddPageToResult();
}
private void onSearchFinished(SearchBeatmapSetsResponse response)
private void onSearchFinished(List<BeatmapSetInfo> beatmaps)
{
var beatmaps = response.BeatmapSets.Select(r => r.ToBeatmapSet(rulesets)).ToList();
searchControl.BeatmapSet = response.Total == 0 ? null : beatmaps.First();
queryPagingDebounce = Scheduler.AddDelayed(() => queryPagingDebounce = null, 1000);
if (currentBeatmaps == null || !beatmapListingPager.IsPastFirstPage)
currentBeatmaps = beatmaps;
else
currentBeatmaps.AddRange(beatmaps);
SearchFinished?.Invoke(beatmaps);
}
protected override void Dispose(bool isDisposing)
{
getSetsRequest?.Cancel();
beatmapListingPager?.Reset();
queryChangedDebounce?.Cancel();
queryPagingDebounce?.Cancel();
base.Dispose(isDisposing);
}
public void TakeFocus() => searchControl.TakeFocus();
/// <summary> Request next 50 matches if available </summary>
public void AddPageToResult()
{
if (beatmapListingPager == null || !beatmapListingPager.CanFetchNextPage)
return;
if (queryPagingDebounce != null)
return;
beatmapListingPager.FetchNextPage();
}
}
}

View File

@ -0,0 +1,92 @@
// 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.Collections.Generic;
using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Rulesets;
namespace osu.Game.Overlays.BeatmapListing
{
public class BeatmapListingPager
{
private readonly IAPIProvider api;
private readonly RulesetStore rulesets;
private readonly string query;
private readonly RulesetInfo ruleset;
private readonly SearchCategory searchCategory;
private readonly SortCriteria sortCriteria;
private readonly SortDirection sortDirection;
public event PageFetchHandler PageFetched;
private SearchBeatmapSetsRequest getSetsRequest;
private SearchBeatmapSetsResponse lastResponse;
/// <summary> Reports end of results </summary>
private bool isLastPageFetched = false;
/// <summary> Job in process lock flag </summary>
private bool isFetching => getSetsRequest != null;
/// <summary> Whether beatmaps should be appended or replaced </summary>
public bool IsPastFirstPage { get; private set; } = false;
/// <summary> call FetchNextPage() safe-check </summary>
public bool CanFetchNextPage => !isLastPageFetched && !isFetching;
public BeatmapListingPager(IAPIProvider api, RulesetStore rulesets, string query, RulesetInfo ruleset, SearchCategory searchCategory = SearchCategory.Any, SortCriteria sortCriteria = SortCriteria.Ranked, SortDirection sortDirection = SortDirection.Descending)
{
this.api = api;
this.rulesets = rulesets;
this.query = query;
this.ruleset = ruleset;
this.searchCategory = searchCategory;
this.sortCriteria = sortCriteria;
this.sortDirection = sortDirection;
}
public void FetchNextPage()
{
if (isFetching)
return;
if (lastResponse != null)
IsPastFirstPage = true;
getSetsRequest = new SearchBeatmapSetsRequest(
query,
ruleset,
lastResponse?.Cursor,
searchCategory,
sortCriteria,
sortDirection);
getSetsRequest.Success += response =>
{
var sets = response.BeatmapSets.Select(responseJson => responseJson.ToBeatmapSet(rulesets)).ToList();
if (sets.Count == 0)
isLastPageFetched = true;
lastResponse = response;
getSetsRequest = null;
PageFetched?.Invoke(sets);
};
api.Queue(getSetsRequest);
}
public void Reset()
{
isLastPageFetched = false;
IsPastFirstPage = false;
lastResponse = null;
getSetsRequest?.Cancel();
getSetsRequest = null;
}
public delegate void PageFetchHandler(List<BeatmapSetInfo> sets);
}
}

View File

@ -30,13 +30,21 @@ namespace osu.Game.Overlays
private Drawable currentContent;
private LoadingLayer loadingLayer;
private Container panelTarget;
private FillFlowContainer<BeatmapPanel> foundContent;
private NotFoundDrawable notFoundContent;
private OverlayScrollContainer resultScrollContainer;
/// <summary> Scroll distance threshold from results tail, higher means sooner </summary>
private const int pagination_scroll_distance = 500;
/// <summary> This is paging event flag </summary>
private bool shouldAddNextPage => resultScrollContainer.ScrollableExtent > 0 && resultScrollContainer.IsScrolledToEnd(pagination_scroll_distance);
public BeatmapListingOverlay()
: base(OverlayColourScheme.Blue)
{
}
private BeatmapListingFilterControl filterControl;
private BeatmapListingFilterControl filterControl;//actual search settings
[BackgroundDependencyLoader]
private void load()
@ -48,7 +56,7 @@ namespace osu.Game.Overlays
RelativeSizeAxes = Axes.Both,
Colour = ColourProvider.Background6
},
new OverlayScrollContainer
resultScrollContainer = new OverlayScrollContainer
{
RelativeSizeAxes = Axes.Both,
ScrollbarVisible = false,
@ -80,9 +88,14 @@ namespace osu.Game.Overlays
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Padding = new MarginPadding { Horizontal = 20 }
},
loadingLayer = new LoadingLayer(panelTarget)
Padding = new MarginPadding { Horizontal = 20 },
Children = new Drawable[]
{
foundContent = new FillFlowContainer<BeatmapPanel>(),
notFoundContent = new NotFoundDrawable(),
loadingLayer = new LoadingLayer(panelTarget)
}
}
}
},
}
@ -112,27 +125,52 @@ namespace osu.Game.Overlays
private void onSearchFinished(List<BeatmapSetInfo> beatmaps)
{
//No matches case
if (!beatmaps.Any())
{
LoadComponentAsync(new NotFoundDrawable(), addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token);
LoadComponentAsync(notFoundContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token);
return;
}
var newPanels = new FillFlowContainer<BeatmapPanel>
//New query case
if (!shouldAddNextPage)
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(10),
Alpha = 0,
Margin = new MarginPadding { Vertical = 15 },
ChildrenEnumerable = beatmaps.Select<BeatmapSetInfo, BeatmapPanel>(b => new GridBeatmapPanel(b)
//Spawn new child
var newPanels = new FillFlowContainer<BeatmapPanel>
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
})
};
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(10),
Alpha = 0,
Margin = new MarginPadding { Vertical = 15 },
ChildrenEnumerable = beatmaps.Select<BeatmapSetInfo, BeatmapPanel>(b => new GridBeatmapPanel(b)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
})
};
LoadComponentAsync(newPanels, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token);
foundContent = newPanels;
LoadComponentAsync(foundContent, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token);
}
//Pagination case
else
{
beatmaps.ForEach(x =>
{
LoadComponentAsync(new GridBeatmapPanel(x)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
}, loaded =>
{
foundContent.Add(loaded);
loaded.FadeIn(200, Easing.OutQuint);
});
});
}
}
private void addContentToPlaceholder(Drawable content)
@ -149,13 +187,18 @@ namespace osu.Game.Overlays
// 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);
lastContent.Delay(25).Schedule(() => lastContent.BypassAutoSizeAxes = Axes.Y)
.Then().Schedule(() => panelTarget.Remove(lastContent));
}
panelTarget.Add(currentContent = content);
currentContent.FadeIn(200, Easing.OutQuint);
if (!content.IsAlive)
panelTarget.Add(content);
content.FadeIn(200, Easing.OutQuint);
currentContent = content;
}
protected override void Dispose(bool isDisposing)
{
cancellationToken?.Cancel();
@ -203,5 +246,14 @@ namespace osu.Game.Overlays
});
}
}
protected override void Update()
{
base.Update();
if (shouldAddNextPage)
filterControl.AddPageToResult();
}
}
}