mirror of
https://github.com/osukey/osukey.git
synced 2025-08-05 07:33:55 +09:00
Merge branch 'master' into fix-draining-in-between-break-sections
This commit is contained in:
@ -149,7 +149,17 @@ namespace osu.Game.Beatmaps
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString() => $"{Metadata} [{Version}]".Trim();
|
||||
public string[] SearchableTerms => new[]
|
||||
{
|
||||
Version
|
||||
}.Concat(Metadata?.SearchableTerms ?? Enumerable.Empty<string>()).Where(s => !string.IsNullOrEmpty(s)).ToArray();
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
string version = string.IsNullOrEmpty(Version) ? string.Empty : $"[{Version}]";
|
||||
|
||||
return $"{Metadata} {version}".Trim();
|
||||
}
|
||||
|
||||
public bool Equals(BeatmapInfo other)
|
||||
{
|
||||
|
@ -17,7 +17,6 @@ using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Lists;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.IO;
|
||||
@ -61,7 +60,7 @@ namespace osu.Game.Beatmaps
|
||||
private readonly BeatmapStore beatmaps;
|
||||
private readonly AudioManager audioManager;
|
||||
private readonly GameHost host;
|
||||
private readonly BeatmapUpdateQueue updateQueue;
|
||||
private readonly BeatmapOnlineLookupQueue onlineLookupQueue;
|
||||
private readonly Storage exportStorage;
|
||||
|
||||
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, AudioManager audioManager, GameHost host = null,
|
||||
@ -78,7 +77,7 @@ namespace osu.Game.Beatmaps
|
||||
beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b);
|
||||
beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b);
|
||||
|
||||
updateQueue = new BeatmapUpdateQueue(api);
|
||||
onlineLookupQueue = new BeatmapOnlineLookupQueue(api, storage);
|
||||
exportStorage = storage.GetStorageForDirectory("exports");
|
||||
}
|
||||
|
||||
@ -105,7 +104,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
bool hadOnlineBeatmapIDs = beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0);
|
||||
|
||||
await updateQueue.UpdateAsync(beatmapSet, cancellationToken);
|
||||
await onlineLookupQueue.UpdateAsync(beatmapSet, cancellationToken);
|
||||
|
||||
// ensure at least one beatmap was able to retrieve or keep an online ID, else drop the set ID.
|
||||
if (hadOnlineBeatmapIDs && !beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID > 0))
|
||||
@ -141,7 +140,7 @@ namespace osu.Game.Beatmaps
|
||||
{
|
||||
var beatmapIds = beatmapSet.Beatmaps.Where(b => b.OnlineBeatmapID.HasValue).Select(b => b.OnlineBeatmapID).ToList();
|
||||
|
||||
LogForModel(beatmapSet, "Validating online IDs...");
|
||||
LogForModel(beatmapSet, $"Validating online IDs for {beatmapSet.Beatmaps.Count} beatmaps...");
|
||||
|
||||
// ensure all IDs are unique
|
||||
if (beatmapIds.GroupBy(b => b).Any(g => g.Count() > 1))
|
||||
@ -246,6 +245,12 @@ namespace osu.Game.Beatmaps
|
||||
if (beatmapInfo?.BeatmapSet == null || beatmapInfo == DefaultBeatmap?.BeatmapInfo)
|
||||
return DefaultBeatmap;
|
||||
|
||||
if (beatmapInfo.BeatmapSet.Files == null)
|
||||
{
|
||||
var info = beatmapInfo;
|
||||
beatmapInfo = QueryBeatmap(b => b.ID == info.ID);
|
||||
}
|
||||
|
||||
lock (workingCache)
|
||||
{
|
||||
var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == beatmapInfo.ID);
|
||||
@ -287,13 +292,37 @@ namespace osu.Game.Beatmaps
|
||||
/// Returns a list of all usable <see cref="BeatmapSetInfo"/>s.
|
||||
/// </summary>
|
||||
/// <returns>A list of available <see cref="BeatmapSetInfo"/>.</returns>
|
||||
public List<BeatmapSetInfo> GetAllUsableBeatmapSets() => GetAllUsableBeatmapSetsEnumerable().ToList();
|
||||
public List<BeatmapSetInfo> GetAllUsableBeatmapSets(IncludedDetails includes = IncludedDetails.All) => GetAllUsableBeatmapSetsEnumerable(includes).ToList();
|
||||
|
||||
/// <summary>
|
||||
/// Returns a list of all usable <see cref="BeatmapSetInfo"/>s.
|
||||
/// Returns a list of all usable <see cref="BeatmapSetInfo"/>s. Note that files are not populated.
|
||||
/// </summary>
|
||||
/// <param name="includes">The level of detail to include in the returned objects.</param>
|
||||
/// <returns>A list of available <see cref="BeatmapSetInfo"/>.</returns>
|
||||
public IQueryable<BeatmapSetInfo> GetAllUsableBeatmapSetsEnumerable() => beatmaps.ConsumableItems.Where(s => !s.DeletePending && !s.Protected);
|
||||
public IEnumerable<BeatmapSetInfo> GetAllUsableBeatmapSetsEnumerable(IncludedDetails includes)
|
||||
{
|
||||
IQueryable<BeatmapSetInfo> queryable;
|
||||
|
||||
switch (includes)
|
||||
{
|
||||
case IncludedDetails.Minimal:
|
||||
queryable = beatmaps.BeatmapSetsOverview;
|
||||
break;
|
||||
|
||||
case IncludedDetails.AllButFiles:
|
||||
queryable = beatmaps.BeatmapSetsWithoutFiles;
|
||||
break;
|
||||
|
||||
default:
|
||||
queryable = beatmaps.ConsumableItems;
|
||||
break;
|
||||
}
|
||||
|
||||
// AsEnumerable used here to avoid applying the WHERE in sql. When done so, ef core 2.x uses an incorrect ORDER BY
|
||||
// clause which causes queries to take 5-10x longer.
|
||||
// TODO: remove if upgrading to EF core 3.x.
|
||||
return queryable.AsEnumerable().Where(s => !s.DeletePending && !s.Protected);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform a lookup query on available <see cref="BeatmapSetInfo"/>s.
|
||||
@ -352,7 +381,7 @@ namespace osu.Game.Beatmaps
|
||||
foreach (var file in files.Where(f => f.Filename.EndsWith(".osu")))
|
||||
{
|
||||
using (var raw = Files.Store.GetStream(file.FileInfo.StoragePath))
|
||||
using (var ms = new MemoryStream()) //we need a memory stream so we can seek
|
||||
using (var ms = new MemoryStream()) // we need a memory stream so we can seek
|
||||
using (var sr = new LineBufferedReader(ms))
|
||||
{
|
||||
raw.CopyTo(ms);
|
||||
@ -416,70 +445,26 @@ namespace osu.Game.Beatmaps
|
||||
protected override Texture GetBackground() => null;
|
||||
protected override Track GetTrack() => null;
|
||||
}
|
||||
}
|
||||
|
||||
private class BeatmapUpdateQueue
|
||||
{
|
||||
private readonly IAPIProvider api;
|
||||
/// <summary>
|
||||
/// The level of detail to include in database results.
|
||||
/// </summary>
|
||||
public enum IncludedDetails
|
||||
{
|
||||
/// <summary>
|
||||
/// Only include beatmap difficulties and set level metadata.
|
||||
/// </summary>
|
||||
Minimal,
|
||||
|
||||
private const int update_queue_request_concurrency = 4;
|
||||
/// <summary>
|
||||
/// Include all difficulties, rulesets, difficulty metadata but no files.
|
||||
/// </summary>
|
||||
AllButFiles,
|
||||
|
||||
private readonly ThreadedTaskScheduler updateScheduler = new ThreadedTaskScheduler(update_queue_request_concurrency, nameof(BeatmapUpdateQueue));
|
||||
|
||||
public BeatmapUpdateQueue(IAPIProvider api)
|
||||
{
|
||||
this.api = api;
|
||||
}
|
||||
|
||||
public Task UpdateAsync(BeatmapSetInfo beatmapSet, CancellationToken cancellationToken)
|
||||
{
|
||||
if (api?.State != APIState.Online)
|
||||
return Task.CompletedTask;
|
||||
|
||||
LogForModel(beatmapSet, "Performing online lookups...");
|
||||
return Task.WhenAll(beatmapSet.Beatmaps.Select(b => UpdateAsync(beatmapSet, b, cancellationToken)).ToArray());
|
||||
}
|
||||
|
||||
// todo: expose this when we need to do individual difficulty lookups.
|
||||
protected Task UpdateAsync(BeatmapSetInfo beatmapSet, BeatmapInfo beatmap, CancellationToken cancellationToken)
|
||||
=> Task.Factory.StartNew(() => update(beatmapSet, beatmap), cancellationToken, TaskCreationOptions.HideScheduler, updateScheduler);
|
||||
|
||||
private void update(BeatmapSetInfo set, BeatmapInfo beatmap)
|
||||
{
|
||||
if (api?.State != APIState.Online)
|
||||
return;
|
||||
|
||||
var req = new GetBeatmapRequest(beatmap);
|
||||
|
||||
req.Failure += fail;
|
||||
|
||||
try
|
||||
{
|
||||
// intentionally blocking to limit web request concurrency
|
||||
api.Perform(req);
|
||||
|
||||
var res = req.Result;
|
||||
|
||||
if (res != null)
|
||||
{
|
||||
beatmap.Status = res.Status;
|
||||
beatmap.BeatmapSet.Status = res.BeatmapSet.Status;
|
||||
beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID;
|
||||
beatmap.OnlineBeatmapID = res.OnlineBeatmapID;
|
||||
|
||||
LogForModel(set, $"Online retrieval mapped {beatmap} to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}.");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
fail(e);
|
||||
}
|
||||
|
||||
void fail(Exception e)
|
||||
{
|
||||
beatmap.OnlineBeatmapID = null;
|
||||
LogForModel(set, $"Online retrieval failed for {beatmap} ({e.Message})");
|
||||
}
|
||||
}
|
||||
}
|
||||
/// <summary>
|
||||
/// Include everything.
|
||||
/// </summary>
|
||||
All
|
||||
}
|
||||
}
|
||||
|
195
osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs
Normal file
195
osu.Game/Beatmaps/BeatmapManager_BeatmapOnlineLookupQueue.cs
Normal file
@ -0,0 +1,195 @@
|
||||
// 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;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Dapper;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using osu.Framework.Development;
|
||||
using osu.Framework.IO.Network;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using SharpCompress.Compressors;
|
||||
using SharpCompress.Compressors.BZip2;
|
||||
|
||||
namespace osu.Game.Beatmaps
|
||||
{
|
||||
public partial class BeatmapManager
|
||||
{
|
||||
private class BeatmapOnlineLookupQueue
|
||||
{
|
||||
private readonly IAPIProvider api;
|
||||
private readonly Storage storage;
|
||||
|
||||
private const int update_queue_request_concurrency = 4;
|
||||
|
||||
private readonly ThreadedTaskScheduler updateScheduler = new ThreadedTaskScheduler(update_queue_request_concurrency, nameof(BeatmapOnlineLookupQueue));
|
||||
|
||||
private FileWebRequest cacheDownloadRequest;
|
||||
|
||||
private const string cache_database_name = "online.db";
|
||||
|
||||
public BeatmapOnlineLookupQueue(IAPIProvider api, Storage storage)
|
||||
{
|
||||
this.api = api;
|
||||
this.storage = storage;
|
||||
|
||||
// avoid downloading / using cache for unit tests.
|
||||
if (!DebugUtils.IsNUnitRunning && !storage.Exists(cache_database_name))
|
||||
prepareLocalCache();
|
||||
}
|
||||
|
||||
public Task UpdateAsync(BeatmapSetInfo beatmapSet, CancellationToken cancellationToken)
|
||||
{
|
||||
if (api?.State != APIState.Online)
|
||||
return Task.CompletedTask;
|
||||
|
||||
LogForModel(beatmapSet, "Performing online lookups...");
|
||||
return Task.WhenAll(beatmapSet.Beatmaps.Select(b => UpdateAsync(beatmapSet, b, cancellationToken)).ToArray());
|
||||
}
|
||||
|
||||
// todo: expose this when we need to do individual difficulty lookups.
|
||||
protected Task UpdateAsync(BeatmapSetInfo beatmapSet, BeatmapInfo beatmap, CancellationToken cancellationToken)
|
||||
=> Task.Factory.StartNew(() => lookup(beatmapSet, beatmap), cancellationToken, TaskCreationOptions.HideScheduler, updateScheduler);
|
||||
|
||||
private void lookup(BeatmapSetInfo set, BeatmapInfo beatmap)
|
||||
{
|
||||
if (checkLocalCache(set, beatmap))
|
||||
return;
|
||||
|
||||
if (api?.State != APIState.Online)
|
||||
return;
|
||||
|
||||
var req = new GetBeatmapRequest(beatmap);
|
||||
|
||||
req.Failure += fail;
|
||||
|
||||
try
|
||||
{
|
||||
// intentionally blocking to limit web request concurrency
|
||||
api.Perform(req);
|
||||
|
||||
var res = req.Result;
|
||||
|
||||
if (res != null)
|
||||
{
|
||||
beatmap.Status = res.Status;
|
||||
beatmap.BeatmapSet.Status = res.BeatmapSet.Status;
|
||||
beatmap.BeatmapSet.OnlineBeatmapSetID = res.OnlineBeatmapSetID;
|
||||
beatmap.OnlineBeatmapID = res.OnlineBeatmapID;
|
||||
|
||||
LogForModel(set, $"Online retrieval mapped {beatmap} to {res.OnlineBeatmapSetID} / {res.OnlineBeatmapID}.");
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
fail(e);
|
||||
}
|
||||
|
||||
void fail(Exception e)
|
||||
{
|
||||
beatmap.OnlineBeatmapID = null;
|
||||
LogForModel(set, $"Online retrieval failed for {beatmap} ({e.Message})");
|
||||
}
|
||||
}
|
||||
|
||||
private void prepareLocalCache()
|
||||
{
|
||||
string cacheFilePath = storage.GetFullPath(cache_database_name);
|
||||
string compressedCacheFilePath = $"{cacheFilePath}.bz2";
|
||||
|
||||
cacheDownloadRequest = new FileWebRequest(compressedCacheFilePath, $"https://assets.ppy.sh/client-resources/{cache_database_name}.bz2");
|
||||
|
||||
cacheDownloadRequest.Failed += ex =>
|
||||
{
|
||||
File.Delete(compressedCacheFilePath);
|
||||
File.Delete(cacheFilePath);
|
||||
|
||||
Logger.Log($"{nameof(BeatmapOnlineLookupQueue)}'s online cache download failed: {ex}", LoggingTarget.Database);
|
||||
};
|
||||
|
||||
cacheDownloadRequest.Finished += () =>
|
||||
{
|
||||
try
|
||||
{
|
||||
using (var stream = File.OpenRead(cacheDownloadRequest.Filename))
|
||||
using (var outStream = File.OpenWrite(cacheFilePath))
|
||||
using (var bz2 = new BZip2Stream(stream, CompressionMode.Decompress, false))
|
||||
bz2.CopyTo(outStream);
|
||||
|
||||
// set to null on completion to allow lookups to begin using the new source
|
||||
cacheDownloadRequest = null;
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Logger.Log($"{nameof(BeatmapOnlineLookupQueue)}'s online cache extraction failed: {ex}", LoggingTarget.Database);
|
||||
File.Delete(cacheFilePath);
|
||||
}
|
||||
finally
|
||||
{
|
||||
File.Delete(compressedCacheFilePath);
|
||||
}
|
||||
};
|
||||
|
||||
cacheDownloadRequest.PerformAsync();
|
||||
}
|
||||
|
||||
private bool checkLocalCache(BeatmapSetInfo set, BeatmapInfo beatmap)
|
||||
{
|
||||
// download is in progress (or was, and failed).
|
||||
if (cacheDownloadRequest != null)
|
||||
return false;
|
||||
|
||||
// database is unavailable.
|
||||
if (!storage.Exists(cache_database_name))
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
using (var db = new SqliteConnection(storage.GetDatabaseConnectionString("online")))
|
||||
{
|
||||
var found = db.QuerySingleOrDefault<CachedOnlineBeatmapLookup>(
|
||||
"SELECT * FROM osu_beatmaps WHERE checksum = @MD5Hash OR beatmap_id = @OnlineBeatmapID OR filename = @Path", beatmap);
|
||||
|
||||
if (found != null)
|
||||
{
|
||||
var status = (BeatmapSetOnlineStatus)found.approved;
|
||||
|
||||
beatmap.Status = status;
|
||||
beatmap.BeatmapSet.Status = status;
|
||||
beatmap.BeatmapSet.OnlineBeatmapSetID = found.beatmapset_id;
|
||||
beatmap.OnlineBeatmapID = found.beatmap_id;
|
||||
|
||||
LogForModel(set, $"Cached local retrieval for {beatmap}.");
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
LogForModel(set, $"Cached local retrieval for {beatmap} failed with {ex}.");
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
[SuppressMessage("ReSharper", "InconsistentNaming")]
|
||||
private class CachedOnlineBeatmapLookup
|
||||
{
|
||||
public int approved { get; set; }
|
||||
|
||||
public int? beatmapset_id { get; set; }
|
||||
|
||||
public int? beatmap_id { get; set; }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -53,7 +53,11 @@ namespace osu.Game.Beatmaps
|
||||
public string AudioFile { get; set; }
|
||||
public string BackgroundFile { get; set; }
|
||||
|
||||
public override string ToString() => $"{Artist} - {Title} ({Author})";
|
||||
public override string ToString()
|
||||
{
|
||||
string author = Author == null ? string.Empty : $"({Author})";
|
||||
return $"{Artist} - {Title} {author}".Trim();
|
||||
}
|
||||
|
||||
[JsonIgnore]
|
||||
public string[] SearchableTerms => new[]
|
||||
|
@ -87,6 +87,18 @@ namespace osu.Game.Beatmaps
|
||||
base.Purge(items, context);
|
||||
}
|
||||
|
||||
public IQueryable<BeatmapSetInfo> BeatmapSetsOverview => ContextFactory.Get().BeatmapSetInfo
|
||||
.Include(s => s.Metadata)
|
||||
.Include(s => s.Beatmaps)
|
||||
.AsNoTracking();
|
||||
|
||||
public IQueryable<BeatmapSetInfo> BeatmapSetsWithoutFiles => ContextFactory.Get().BeatmapSetInfo
|
||||
.Include(s => s.Metadata)
|
||||
.Include(s => s.Beatmaps).ThenInclude(s => s.Ruleset)
|
||||
.Include(s => s.Beatmaps).ThenInclude(b => b.BaseDifficulty)
|
||||
.Include(s => s.Beatmaps).ThenInclude(b => b.Metadata)
|
||||
.AsNoTracking();
|
||||
|
||||
public IQueryable<BeatmapInfo> Beatmaps =>
|
||||
ContextFactory.Get().BeatmapInfo
|
||||
.Include(b => b.BeatmapSet).ThenInclude(s => s.Metadata)
|
||||
|
@ -5,7 +5,7 @@ using System;
|
||||
|
||||
namespace osu.Game.Beatmaps.ControlPoints
|
||||
{
|
||||
public abstract class ControlPoint : IComparable<ControlPoint>, IEquatable<ControlPoint>
|
||||
public abstract class ControlPoint : IComparable<ControlPoint>
|
||||
{
|
||||
/// <summary>
|
||||
/// The time at which the control point takes effect.
|
||||
@ -19,12 +19,10 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time);
|
||||
|
||||
/// <summary>
|
||||
/// Whether this control point is equivalent to another, ignoring time.
|
||||
/// Determines whether this <see cref="ControlPoint"/> results in a meaningful change when placed alongside another.
|
||||
/// </summary>
|
||||
/// <param name="other">Another control point to compare with.</param>
|
||||
/// <returns>Whether equivalent.</returns>
|
||||
public abstract bool EquivalentTo(ControlPoint other);
|
||||
|
||||
public bool Equals(ControlPoint other) => Time == other?.Time && EquivalentTo(other);
|
||||
/// <param name="existing">An existing control point to compare with.</param>
|
||||
/// <returns>Whether this <see cref="ControlPoint"/> is redundant when placed alongside <paramref name="existing"/>.</returns>
|
||||
public abstract bool IsRedundant(ControlPoint existing);
|
||||
}
|
||||
}
|
||||
|
@ -56,6 +56,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
/// <summary>
|
||||
/// All control points, of all types.
|
||||
/// </summary>
|
||||
[JsonIgnore]
|
||||
public IEnumerable<ControlPoint> AllControlPoints => Groups.SelectMany(g => g.ControlPoints).ToArray();
|
||||
|
||||
/// <summary>
|
||||
@ -247,7 +248,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
break;
|
||||
}
|
||||
|
||||
return existing?.EquivalentTo(newPoint) == true;
|
||||
return newPoint?.IsRedundant(existing) == true;
|
||||
}
|
||||
|
||||
private void groupItemAdded(ControlPoint controlPoint)
|
||||
|
@ -27,7 +27,8 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
set => SpeedMultiplierBindable.Value = value;
|
||||
}
|
||||
|
||||
public override bool EquivalentTo(ControlPoint other) =>
|
||||
other is DifficultyControlPoint otherTyped && otherTyped.SpeedMultiplier.Equals(SpeedMultiplier);
|
||||
public override bool IsRedundant(ControlPoint existing)
|
||||
=> existing is DifficultyControlPoint existingDifficulty
|
||||
&& SpeedMultiplier == existingDifficulty.SpeedMultiplier;
|
||||
}
|
||||
}
|
||||
|
@ -35,8 +35,10 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
set => KiaiModeBindable.Value = value;
|
||||
}
|
||||
|
||||
public override bool EquivalentTo(ControlPoint other) =>
|
||||
other is EffectControlPoint otherTyped &&
|
||||
KiaiMode == otherTyped.KiaiMode && OmitFirstBarLine == otherTyped.OmitFirstBarLine;
|
||||
public override bool IsRedundant(ControlPoint existing)
|
||||
=> !OmitFirstBarLine
|
||||
&& existing is EffectControlPoint existingEffect
|
||||
&& KiaiMode == existingEffect.KiaiMode
|
||||
&& OmitFirstBarLine == existingEffect.OmitFirstBarLine;
|
||||
}
|
||||
}
|
||||
|
@ -68,8 +68,9 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
return newSampleInfo;
|
||||
}
|
||||
|
||||
public override bool EquivalentTo(ControlPoint other) =>
|
||||
other is SampleControlPoint otherTyped &&
|
||||
SampleBank == otherTyped.SampleBank && SampleVolume == otherTyped.SampleVolume;
|
||||
public override bool IsRedundant(ControlPoint existing)
|
||||
=> existing is SampleControlPoint existingSample
|
||||
&& SampleBank == existingSample.SampleBank
|
||||
&& SampleVolume == existingSample.SampleVolume;
|
||||
}
|
||||
}
|
||||
|
@ -48,8 +48,7 @@ namespace osu.Game.Beatmaps.ControlPoints
|
||||
/// </summary>
|
||||
public double BPM => 60000 / BeatLength;
|
||||
|
||||
public override bool EquivalentTo(ControlPoint other) =>
|
||||
other is TimingControlPoint otherTyped
|
||||
&& TimeSignature == otherTyped.TimeSignature && BeatLength.Equals(otherTyped.BeatLength);
|
||||
// Timing points are never redundant as they can change the time signature.
|
||||
public override bool IsRedundant(ControlPoint existing) => false;
|
||||
}
|
||||
}
|
||||
|
@ -386,17 +386,10 @@ namespace osu.Game.Beatmaps.Formats
|
||||
SampleVolume = sampleVolume,
|
||||
CustomSampleBank = customSampleBank,
|
||||
}, timingChange);
|
||||
|
||||
// To handle the scenario where a non-timing line shares the same time value as a subsequent timing line but
|
||||
// appears earlier in the file, we buffer non-timing control points and rewrite them *after* control points from the timing line
|
||||
// with the same time value (allowing them to overwrite as necessary).
|
||||
//
|
||||
// The expected outcome is that we prefer the non-timing line's adjustments over the timing line's adjustments when time is equal.
|
||||
if (timingChange)
|
||||
flushPendingPoints();
|
||||
}
|
||||
|
||||
private readonly List<ControlPoint> pendingControlPoints = new List<ControlPoint>();
|
||||
private readonly HashSet<Type> pendingControlPointTypes = new HashSet<Type>();
|
||||
private double pendingControlPointsTime;
|
||||
|
||||
private void addControlPoint(double time, ControlPoint point, bool timingChange)
|
||||
@ -405,21 +398,28 @@ namespace osu.Game.Beatmaps.Formats
|
||||
flushPendingPoints();
|
||||
|
||||
if (timingChange)
|
||||
{
|
||||
beatmap.ControlPointInfo.Add(time, point);
|
||||
return;
|
||||
}
|
||||
pendingControlPoints.Insert(0, point);
|
||||
else
|
||||
pendingControlPoints.Add(point);
|
||||
|
||||
pendingControlPoints.Add(point);
|
||||
pendingControlPointsTime = time;
|
||||
}
|
||||
|
||||
private void flushPendingPoints()
|
||||
{
|
||||
foreach (var p in pendingControlPoints)
|
||||
beatmap.ControlPointInfo.Add(pendingControlPointsTime, p);
|
||||
// Changes from non-timing-points are added to the end of the list (see addControlPoint()) and should override any changes from timing-points (added to the start of the list).
|
||||
for (int i = pendingControlPoints.Count - 1; i >= 0; i--)
|
||||
{
|
||||
var type = pendingControlPoints[i].GetType();
|
||||
if (pendingControlPointTypes.Contains(type))
|
||||
continue;
|
||||
|
||||
pendingControlPointTypes.Add(type);
|
||||
beatmap.ControlPointInfo.Add(pendingControlPointsTime, pendingControlPoints[i]);
|
||||
}
|
||||
|
||||
pendingControlPoints.Clear();
|
||||
pendingControlPointTypes.Clear();
|
||||
}
|
||||
|
||||
private void handleHitObject(string line)
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
@ -10,7 +11,9 @@ using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.Beatmaps.Legacy;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
@ -48,7 +51,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
handleEvents(writer);
|
||||
|
||||
writer.WriteLine();
|
||||
handleTimingPoints(writer);
|
||||
handleControlPoints(writer);
|
||||
|
||||
writer.WriteLine();
|
||||
handleHitObjects(writer);
|
||||
@ -58,7 +61,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
writer.WriteLine("[General]");
|
||||
|
||||
writer.WriteLine(FormattableString.Invariant($"AudioFilename: {Path.GetFileName(beatmap.Metadata.AudioFile)}"));
|
||||
if (beatmap.Metadata.AudioFile != null) writer.WriteLine(FormattableString.Invariant($"AudioFilename: {Path.GetFileName(beatmap.Metadata.AudioFile)}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"AudioLeadIn: {beatmap.BeatmapInfo.AudioLeadIn}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"PreviewTime: {beatmap.Metadata.PreviewTime}"));
|
||||
// Todo: Not all countdown types are supported by lazer yet
|
||||
@ -103,15 +106,15 @@ namespace osu.Game.Beatmaps.Formats
|
||||
writer.WriteLine("[Metadata]");
|
||||
|
||||
writer.WriteLine(FormattableString.Invariant($"Title: {beatmap.Metadata.Title}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"TitleUnicode: {beatmap.Metadata.TitleUnicode}"));
|
||||
if (beatmap.Metadata.TitleUnicode != null) writer.WriteLine(FormattableString.Invariant($"TitleUnicode: {beatmap.Metadata.TitleUnicode}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"Artist: {beatmap.Metadata.Artist}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"ArtistUnicode: {beatmap.Metadata.ArtistUnicode}"));
|
||||
if (beatmap.Metadata.ArtistUnicode != null) writer.WriteLine(FormattableString.Invariant($"ArtistUnicode: {beatmap.Metadata.ArtistUnicode}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"Creator: {beatmap.Metadata.AuthorString}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"Version: {beatmap.BeatmapInfo.Version}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"Source: {beatmap.Metadata.Source}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"Tags: {beatmap.Metadata.Tags}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"BeatmapID: {beatmap.BeatmapInfo.OnlineBeatmapID ?? 0}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"BeatmapSetID: {beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID ?? -1}"));
|
||||
if (beatmap.Metadata.Source != null) writer.WriteLine(FormattableString.Invariant($"Source: {beatmap.Metadata.Source}"));
|
||||
if (beatmap.Metadata.Tags != null) writer.WriteLine(FormattableString.Invariant($"Tags: {beatmap.Metadata.Tags}"));
|
||||
if (beatmap.BeatmapInfo.OnlineBeatmapID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapID: {beatmap.BeatmapInfo.OnlineBeatmapID}"));
|
||||
if (beatmap.BeatmapInfo.BeatmapSet?.OnlineBeatmapSetID != null) writer.WriteLine(FormattableString.Invariant($"BeatmapSetID: {beatmap.BeatmapInfo.BeatmapSet.OnlineBeatmapSetID}"));
|
||||
}
|
||||
|
||||
private void handleDifficulty(TextWriter writer)
|
||||
@ -122,7 +125,12 @@ namespace osu.Game.Beatmaps.Formats
|
||||
writer.WriteLine(FormattableString.Invariant($"CircleSize: {beatmap.BeatmapInfo.BaseDifficulty.CircleSize}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"OverallDifficulty: {beatmap.BeatmapInfo.BaseDifficulty.OverallDifficulty}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"ApproachRate: {beatmap.BeatmapInfo.BaseDifficulty.ApproachRate}"));
|
||||
writer.WriteLine(FormattableString.Invariant($"SliderMultiplier: {beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier}"));
|
||||
|
||||
// Taiko adjusts the slider multiplier (see: TaikoBeatmapConverter.LEGACY_VELOCITY_MULTIPLIER)
|
||||
writer.WriteLine(beatmap.BeatmapInfo.RulesetID == 1
|
||||
? FormattableString.Invariant($"SliderMultiplier: {beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier / 1.4f}")
|
||||
: FormattableString.Invariant($"SliderMultiplier: {beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier}"));
|
||||
|
||||
writer.WriteLine(FormattableString.Invariant($"SliderTickRate: {beatmap.BeatmapInfo.BaseDifficulty.SliderTickRate}"));
|
||||
}
|
||||
|
||||
@ -137,7 +145,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
writer.WriteLine(FormattableString.Invariant($"{(int)LegacyEventType.Break},{b.StartTime},{b.EndTime}"));
|
||||
}
|
||||
|
||||
private void handleTimingPoints(TextWriter writer)
|
||||
private void handleControlPoints(TextWriter writer)
|
||||
{
|
||||
if (beatmap.ControlPointInfo.Groups.Count == 0)
|
||||
return;
|
||||
@ -146,20 +154,30 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
foreach (var group in beatmap.ControlPointInfo.Groups)
|
||||
{
|
||||
var timingPoint = group.ControlPoints.OfType<TimingControlPoint>().FirstOrDefault();
|
||||
var difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(group.Time);
|
||||
var samplePoint = beatmap.ControlPointInfo.SamplePointAt(group.Time);
|
||||
var effectPoint = beatmap.ControlPointInfo.EffectPointAt(group.Time);
|
||||
var groupTimingPoint = group.ControlPoints.OfType<TimingControlPoint>().FirstOrDefault();
|
||||
|
||||
// Convert beat length the legacy format
|
||||
double beatLength;
|
||||
if (timingPoint != null)
|
||||
beatLength = timingPoint.BeatLength;
|
||||
else
|
||||
beatLength = -100 / difficultyPoint.SpeedMultiplier;
|
||||
// If the group contains a timing control point, it needs to be output separately.
|
||||
if (groupTimingPoint != null)
|
||||
{
|
||||
writer.Write(FormattableString.Invariant($"{groupTimingPoint.Time},"));
|
||||
writer.Write(FormattableString.Invariant($"{groupTimingPoint.BeatLength},"));
|
||||
outputControlPointEffectsAt(groupTimingPoint.Time, true);
|
||||
}
|
||||
|
||||
// Output any remaining effects as secondary non-timing control point.
|
||||
var difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(group.Time);
|
||||
writer.Write(FormattableString.Invariant($"{group.Time},"));
|
||||
writer.Write(FormattableString.Invariant($"{-100 / difficultyPoint.SpeedMultiplier},"));
|
||||
outputControlPointEffectsAt(group.Time, false);
|
||||
}
|
||||
|
||||
void outputControlPointEffectsAt(double time, bool isTimingPoint)
|
||||
{
|
||||
var samplePoint = beatmap.ControlPointInfo.SamplePointAt(time);
|
||||
var effectPoint = beatmap.ControlPointInfo.EffectPointAt(time);
|
||||
|
||||
// Apply the control point to a hit sample to uncover legacy properties (e.g. suffix)
|
||||
HitSampleInfo tempHitSample = samplePoint.ApplyTo(new HitSampleInfo());
|
||||
HitSampleInfo tempHitSample = samplePoint.ApplyTo(new ConvertHitObjectParser.LegacyHitSampleInfo());
|
||||
|
||||
// Convert effect flags to the legacy format
|
||||
LegacyEffectFlags effectFlags = LegacyEffectFlags.None;
|
||||
@ -168,13 +186,11 @@ namespace osu.Game.Beatmaps.Formats
|
||||
if (effectPoint.OmitFirstBarLine)
|
||||
effectFlags |= LegacyEffectFlags.OmitFirstBarLine;
|
||||
|
||||
writer.Write(FormattableString.Invariant($"{group.Time},"));
|
||||
writer.Write(FormattableString.Invariant($"{beatLength},"));
|
||||
writer.Write(FormattableString.Invariant($"{(int)beatmap.ControlPointInfo.TimingPointAt(group.Time).TimeSignature},"));
|
||||
writer.Write(FormattableString.Invariant($"{(int)beatmap.ControlPointInfo.TimingPointAt(time).TimeSignature},"));
|
||||
writer.Write(FormattableString.Invariant($"{(int)toLegacySampleBank(tempHitSample.Bank)},"));
|
||||
writer.Write(FormattableString.Invariant($"{toLegacyCustomSampleBank(tempHitSample.Suffix)},"));
|
||||
writer.Write(FormattableString.Invariant($"{toLegacyCustomSampleBank(tempHitSample)},"));
|
||||
writer.Write(FormattableString.Invariant($"{tempHitSample.Volume},"));
|
||||
writer.Write(FormattableString.Invariant($"{(timingPoint != null ? '1' : '0')},"));
|
||||
writer.Write(FormattableString.Invariant($"{(isTimingPoint ? '1' : '0')},"));
|
||||
writer.Write(FormattableString.Invariant($"{(int)effectFlags}"));
|
||||
writer.WriteLine();
|
||||
}
|
||||
@ -187,65 +203,63 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
writer.WriteLine("[HitObjects]");
|
||||
|
||||
foreach (var h in beatmap.HitObjects)
|
||||
handleHitObject(writer, h);
|
||||
}
|
||||
|
||||
private void handleHitObject(TextWriter writer, HitObject hitObject)
|
||||
{
|
||||
Vector2 position = new Vector2(256, 192);
|
||||
|
||||
switch (beatmap.BeatmapInfo.RulesetID)
|
||||
{
|
||||
case 0:
|
||||
foreach (var h in beatmap.HitObjects)
|
||||
handleOsuHitObject(writer, h);
|
||||
break;
|
||||
|
||||
case 1:
|
||||
foreach (var h in beatmap.HitObjects)
|
||||
handleTaikoHitObject(writer, h);
|
||||
position = ((IHasPosition)hitObject).Position;
|
||||
break;
|
||||
|
||||
case 2:
|
||||
foreach (var h in beatmap.HitObjects)
|
||||
handleCatchHitObject(writer, h);
|
||||
position.X = ((IHasXPosition)hitObject).X * 512;
|
||||
break;
|
||||
|
||||
case 3:
|
||||
foreach (var h in beatmap.HitObjects)
|
||||
handleManiaHitObject(writer, h);
|
||||
int totalColumns = (int)Math.Max(1, beatmap.BeatmapInfo.BaseDifficulty.CircleSize);
|
||||
position.X = (int)Math.Ceiling(((IHasXPosition)hitObject).X * (512f / totalColumns));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private void handleOsuHitObject(TextWriter writer, HitObject hitObject)
|
||||
{
|
||||
var positionData = (IHasPosition)hitObject;
|
||||
|
||||
writer.Write(FormattableString.Invariant($"{positionData.X},"));
|
||||
writer.Write(FormattableString.Invariant($"{positionData.Y},"));
|
||||
writer.Write(FormattableString.Invariant($"{position.X},"));
|
||||
writer.Write(FormattableString.Invariant($"{position.Y},"));
|
||||
writer.Write(FormattableString.Invariant($"{hitObject.StartTime},"));
|
||||
writer.Write(FormattableString.Invariant($"{(int)getObjectType(hitObject)},"));
|
||||
|
||||
writer.Write(hitObject is IHasCurve
|
||||
? FormattableString.Invariant($"0,")
|
||||
: FormattableString.Invariant($"{(int)toLegacyHitSoundType(hitObject.Samples)},"));
|
||||
writer.Write(FormattableString.Invariant($"{(int)toLegacyHitSoundType(hitObject.Samples)},"));
|
||||
|
||||
if (hitObject is IHasCurve curveData)
|
||||
{
|
||||
addCurveData(writer, curveData, positionData);
|
||||
addCurveData(writer, curveData, position);
|
||||
writer.Write(getSampleBank(hitObject.Samples, zeroBanks: true));
|
||||
}
|
||||
else
|
||||
{
|
||||
if (hitObject is IHasEndTime endTimeData)
|
||||
writer.Write(FormattableString.Invariant($"{endTimeData.EndTime},"));
|
||||
if (hitObject is IHasEndTime)
|
||||
addEndTimeData(writer, hitObject);
|
||||
|
||||
writer.Write(getSampleBank(hitObject.Samples));
|
||||
}
|
||||
|
||||
writer.WriteLine();
|
||||
}
|
||||
|
||||
private static LegacyHitObjectType getObjectType(HitObject hitObject)
|
||||
private LegacyHitObjectType getObjectType(HitObject hitObject)
|
||||
{
|
||||
var comboData = (IHasCombo)hitObject;
|
||||
LegacyHitObjectType type = 0;
|
||||
|
||||
var type = (LegacyHitObjectType)(comboData.ComboOffset << 4);
|
||||
if (hitObject is IHasCombo combo)
|
||||
{
|
||||
type = (LegacyHitObjectType)(combo.ComboOffset << 4);
|
||||
|
||||
if (comboData.NewCombo) type |= LegacyHitObjectType.NewCombo;
|
||||
if (combo.NewCombo)
|
||||
type |= LegacyHitObjectType.NewCombo;
|
||||
}
|
||||
|
||||
switch (hitObject)
|
||||
{
|
||||
@ -254,7 +268,10 @@ namespace osu.Game.Beatmaps.Formats
|
||||
break;
|
||||
|
||||
case IHasEndTime _:
|
||||
type |= LegacyHitObjectType.Spinner | LegacyHitObjectType.NewCombo;
|
||||
if (beatmap.BeatmapInfo.RulesetID == 3)
|
||||
type |= LegacyHitObjectType.Hold;
|
||||
else
|
||||
type |= LegacyHitObjectType.Spinner;
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -265,7 +282,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
return type;
|
||||
}
|
||||
|
||||
private void addCurveData(TextWriter writer, IHasCurve curveData, IHasPosition positionData)
|
||||
private void addCurveData(TextWriter writer, IHasCurve curveData, Vector2 position)
|
||||
{
|
||||
PathType? lastType = null;
|
||||
|
||||
@ -301,13 +318,13 @@ namespace osu.Game.Beatmaps.Formats
|
||||
else
|
||||
{
|
||||
// New segment with the same type - duplicate the control point
|
||||
writer.Write(FormattableString.Invariant($"{positionData.X + point.Position.Value.X}:{positionData.Y + point.Position.Value.Y}|"));
|
||||
writer.Write(FormattableString.Invariant($"{position.X + point.Position.Value.X}:{position.Y + point.Position.Value.Y}|"));
|
||||
}
|
||||
}
|
||||
|
||||
if (i != 0)
|
||||
{
|
||||
writer.Write(FormattableString.Invariant($"{positionData.X + point.Position.Value.X}:{positionData.Y + point.Position.Value.Y}"));
|
||||
writer.Write(FormattableString.Invariant($"{position.X + point.Position.Value.X}:{position.Y + point.Position.Value.Y}"));
|
||||
writer.Write(i != curveData.Path.ControlPoints.Count - 1 ? "|" : ",");
|
||||
}
|
||||
}
|
||||
@ -328,11 +345,19 @@ namespace osu.Game.Beatmaps.Formats
|
||||
}
|
||||
}
|
||||
|
||||
private void handleTaikoHitObject(TextWriter writer, HitObject hitObject) => throw new NotImplementedException();
|
||||
private void addEndTimeData(TextWriter writer, HitObject hitObject)
|
||||
{
|
||||
var endTimeData = (IHasEndTime)hitObject;
|
||||
var type = getObjectType(hitObject);
|
||||
|
||||
private void handleCatchHitObject(TextWriter writer, HitObject hitObject) => throw new NotImplementedException();
|
||||
char suffix = ',';
|
||||
|
||||
private void handleManiaHitObject(TextWriter writer, HitObject hitObject) => throw new NotImplementedException();
|
||||
// Holds write the end time as if it's part of sample data.
|
||||
if (type == LegacyHitObjectType.Hold)
|
||||
suffix = ':';
|
||||
|
||||
writer.Write(FormattableString.Invariant($"{endTimeData.EndTime}{suffix}"));
|
||||
}
|
||||
|
||||
private string getSampleBank(IList<HitSampleInfo> samples, bool banksOnly = false, bool zeroBanks = false)
|
||||
{
|
||||
@ -346,7 +371,7 @@ namespace osu.Game.Beatmaps.Formats
|
||||
|
||||
if (!banksOnly)
|
||||
{
|
||||
string customSampleBank = toLegacyCustomSampleBank(samples.FirstOrDefault(s => !string.IsNullOrEmpty(s.Name))?.Suffix);
|
||||
string customSampleBank = toLegacyCustomSampleBank(samples.FirstOrDefault(s => !string.IsNullOrEmpty(s.Name)));
|
||||
string sampleFilename = samples.FirstOrDefault(s => string.IsNullOrEmpty(s.Name))?.LookupNames.First() ?? string.Empty;
|
||||
int volume = samples.FirstOrDefault()?.Volume ?? 100;
|
||||
|
||||
@ -402,6 +427,15 @@ namespace osu.Game.Beatmaps.Formats
|
||||
}
|
||||
}
|
||||
|
||||
private string toLegacyCustomSampleBank(string sampleSuffix) => string.IsNullOrEmpty(sampleSuffix) ? "0" : sampleSuffix;
|
||||
private string toLegacyCustomSampleBank(HitSampleInfo hitSampleInfo)
|
||||
{
|
||||
if (hitSampleInfo == null)
|
||||
return "0";
|
||||
|
||||
if (hitSampleInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy)
|
||||
return legacy.CustomSampleBank.ToString(CultureInfo.InvariantCulture);
|
||||
|
||||
return "0";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ using osu.Framework.Logging;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps.ControlPoints;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Rulesets.Objects.Legacy;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Beatmaps.Formats
|
||||
@ -149,7 +150,8 @@ namespace osu.Game.Beatmaps.Formats
|
||||
HitObjects,
|
||||
Variables,
|
||||
Fonts,
|
||||
Mania
|
||||
CatchTheBeat,
|
||||
Mania,
|
||||
}
|
||||
|
||||
internal class LegacyDifficultyControlPoint : DifficultyControlPoint
|
||||
@ -168,15 +170,19 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
var baseInfo = base.ApplyTo(hitSampleInfo);
|
||||
|
||||
if (string.IsNullOrEmpty(baseInfo.Suffix) && CustomSampleBank > 1)
|
||||
baseInfo.Suffix = CustomSampleBank.ToString();
|
||||
if (baseInfo is ConvertHitObjectParser.LegacyHitSampleInfo legacy
|
||||
&& legacy.CustomSampleBank == 0)
|
||||
{
|
||||
legacy.CustomSampleBank = CustomSampleBank;
|
||||
}
|
||||
|
||||
return baseInfo;
|
||||
}
|
||||
|
||||
public override bool EquivalentTo(ControlPoint other) =>
|
||||
base.EquivalentTo(other) && other is LegacySampleControlPoint otherTyped &&
|
||||
CustomSampleBank == otherTyped.CustomSampleBank;
|
||||
public override bool IsRedundant(ControlPoint existing)
|
||||
=> base.IsRedundant(existing)
|
||||
&& existing is LegacySampleControlPoint existingSample
|
||||
&& CustomSampleBank == existingSample.CustomSampleBank;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -197,7 +197,7 @@ namespace osu.Game.Beatmaps
|
||||
|
||||
public override string ToString() => BeatmapInfo.ToString();
|
||||
|
||||
public bool BeatmapLoaded => beatmapLoadTask?.IsCompleted ?? false;
|
||||
public virtual bool BeatmapLoaded => beatmapLoadTask?.IsCompleted ?? false;
|
||||
|
||||
public IBeatmap Beatmap
|
||||
{
|
||||
@ -233,7 +233,7 @@ namespace osu.Game.Beatmaps
|
||||
protected abstract Texture GetBackground();
|
||||
private readonly RecyclableLazy<Texture> background;
|
||||
|
||||
public bool TrackLoaded => track.IsResultAvailable;
|
||||
public virtual bool TrackLoaded => track.IsResultAvailable;
|
||||
public Track Track => track.Value;
|
||||
protected abstract Track GetTrack();
|
||||
private RecyclableLazy<Track> track;
|
||||
|
@ -49,6 +49,7 @@ namespace osu.Game.Configuration
|
||||
};
|
||||
|
||||
Set(OsuSetting.ExternalLinkWarning, true);
|
||||
Set(OsuSetting.PreferNoVideo, false);
|
||||
|
||||
// Audio
|
||||
Set(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01);
|
||||
@ -87,7 +88,9 @@ namespace osu.Game.Configuration
|
||||
Set(OsuSetting.ShowInterface, true);
|
||||
Set(OsuSetting.ShowProgressGraph, true);
|
||||
Set(OsuSetting.ShowHealthDisplayWhenCantFail, true);
|
||||
Set(OsuSetting.FadePlayfieldWhenHealthLow, true);
|
||||
Set(OsuSetting.KeyOverlay, false);
|
||||
Set(OsuSetting.PositionalHitSounds, true);
|
||||
Set(OsuSetting.ScoreMeter, ScoreMeterType.HitErrorBoth);
|
||||
|
||||
Set(OsuSetting.FloatingComments, false);
|
||||
@ -176,11 +179,13 @@ namespace osu.Game.Configuration
|
||||
LightenDuringBreaks,
|
||||
ShowStoryboard,
|
||||
KeyOverlay,
|
||||
PositionalHitSounds,
|
||||
ScoreMeter,
|
||||
FloatingComments,
|
||||
ShowInterface,
|
||||
ShowProgressGraph,
|
||||
ShowHealthDisplayWhenCantFail,
|
||||
FadePlayfieldWhenHealthLow,
|
||||
MouseDisableButtons,
|
||||
MouseDisableWheel,
|
||||
AudioOffset,
|
||||
@ -212,6 +217,7 @@ namespace osu.Game.Configuration
|
||||
IncreaseFirstObjectVisibility,
|
||||
ScoreDisplayMode,
|
||||
ExternalLinkWarning,
|
||||
PreferNoVideo,
|
||||
Scaling,
|
||||
ScalingPositionX,
|
||||
ScalingPositionY,
|
||||
|
@ -245,7 +245,7 @@ namespace osu.Game.Database
|
||||
/// </summary>
|
||||
protected abstract string[] HashableFileTypes { get; }
|
||||
|
||||
protected static void LogForModel(TModel model, string message, Exception e = null)
|
||||
internal static void LogForModel(TModel model, string message, Exception e = null)
|
||||
{
|
||||
string prefix = $"[{(model?.Hash ?? "?????").Substring(0, 5)}]";
|
||||
|
||||
|
@ -193,8 +193,8 @@ namespace osu.Game.Graphics.Backgrounds
|
||||
|
||||
float u1 = 1 - RNG.NextSingle(); //uniform(0,1] random floats
|
||||
float u2 = 1 - RNG.NextSingle();
|
||||
float randStdNormal = (float)(Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2)); //random normal(0,1)
|
||||
var scale = Math.Max(triangleScale * (mean + std_dev * randStdNormal), 0.1f); //random normal(mean,stdDev^2)
|
||||
float randStdNormal = (float)(Math.Sqrt(-2.0 * Math.Log(u1)) * Math.Sin(2.0 * Math.PI * u2)); // random normal(0,1)
|
||||
var scale = Math.Max(triangleScale * (mean + std_dev * randStdNormal), 0.1f); // random normal(mean,stdDev^2)
|
||||
|
||||
return new TriangleParticle { Scale = scale };
|
||||
}
|
||||
|
@ -103,7 +103,7 @@ namespace osu.Game.Graphics.Containers
|
||||
|
||||
TimeSinceLastBeat = beatLength - TimeUntilNextBeat;
|
||||
|
||||
if (timingPoint.Equals(lastTimingPoint) && beatIndex == lastBeat)
|
||||
if (timingPoint == lastTimingPoint && beatIndex == lastBeat)
|
||||
return;
|
||||
|
||||
using (BeginDelayedSequence(-TimeSinceLastBeat, true))
|
||||
|
@ -158,7 +158,7 @@ namespace osu.Game.Graphics.Containers
|
||||
{
|
||||
if (!base.OnMouseDown(e)) return false;
|
||||
|
||||
//note that we are changing the colour of the box here as to not interfere with the hover effect.
|
||||
// note that we are changing the colour of the box here as to not interfere with the hover effect.
|
||||
box.FadeColour(highlightColour, 100);
|
||||
return true;
|
||||
}
|
||||
|
@ -3,6 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
@ -16,12 +17,7 @@ namespace osu.Game.Graphics.Containers
|
||||
public class SectionsContainer<T> : Container<T>
|
||||
where T : Drawable
|
||||
{
|
||||
private Drawable expandableHeader, fixedHeader, footer, headerBackground;
|
||||
private readonly OsuScrollContainer scrollContainer;
|
||||
private readonly Container headerBackgroundContainer;
|
||||
private readonly FlowContainer<T> scrollContentContainer;
|
||||
|
||||
protected override Container<T> Content => scrollContentContainer;
|
||||
public Bindable<T> SelectedSection { get; } = new Bindable<T>();
|
||||
|
||||
public Drawable ExpandableHeader
|
||||
{
|
||||
@ -83,6 +79,7 @@ namespace osu.Game.Graphics.Containers
|
||||
|
||||
headerBackgroundContainer.Clear();
|
||||
headerBackground = value;
|
||||
|
||||
if (value == null) return;
|
||||
|
||||
headerBackgroundContainer.Add(headerBackground);
|
||||
@ -91,15 +88,37 @@ namespace osu.Game.Graphics.Containers
|
||||
}
|
||||
}
|
||||
|
||||
public Bindable<T> SelectedSection { get; } = new Bindable<T>();
|
||||
protected override Container<T> Content => scrollContentContainer;
|
||||
|
||||
protected virtual FlowContainer<T> CreateScrollContentContainer()
|
||||
=> new FillFlowContainer<T>
|
||||
private readonly OsuScrollContainer scrollContainer;
|
||||
private readonly Container headerBackgroundContainer;
|
||||
private readonly MarginPadding originalSectionsMargin;
|
||||
private Drawable expandableHeader, fixedHeader, footer, headerBackground;
|
||||
private FlowContainer<T> scrollContentContainer;
|
||||
|
||||
private float headerHeight, footerHeight;
|
||||
|
||||
private float lastKnownScroll;
|
||||
|
||||
public SectionsContainer()
|
||||
{
|
||||
AddRangeInternal(new Drawable[]
|
||||
{
|
||||
Direction = FillDirection.Vertical,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
};
|
||||
scrollContainer = CreateScrollContainer().With(s =>
|
||||
{
|
||||
s.RelativeSizeAxes = Axes.Both;
|
||||
s.Masking = true;
|
||||
s.ScrollbarVisible = false;
|
||||
s.Child = scrollContentContainer = CreateScrollContentContainer();
|
||||
}),
|
||||
headerBackgroundContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X
|
||||
}
|
||||
});
|
||||
|
||||
originalSectionsMargin = scrollContentContainer.Margin;
|
||||
}
|
||||
|
||||
public override void Add(T drawable)
|
||||
{
|
||||
@ -109,40 +128,23 @@ namespace osu.Game.Graphics.Containers
|
||||
footerHeight = float.NaN;
|
||||
}
|
||||
|
||||
private float headerHeight, footerHeight;
|
||||
private readonly MarginPadding originalSectionsMargin;
|
||||
|
||||
private void updateSectionsMargin()
|
||||
{
|
||||
if (!Children.Any()) return;
|
||||
|
||||
var newMargin = originalSectionsMargin;
|
||||
newMargin.Top += headerHeight;
|
||||
newMargin.Bottom += footerHeight;
|
||||
|
||||
scrollContentContainer.Margin = newMargin;
|
||||
}
|
||||
|
||||
public SectionsContainer()
|
||||
{
|
||||
AddInternal(scrollContainer = new OsuScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
ScrollbarVisible = false,
|
||||
Children = new Drawable[] { scrollContentContainer = CreateScrollContentContainer() }
|
||||
});
|
||||
AddInternal(headerBackgroundContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X
|
||||
});
|
||||
originalSectionsMargin = scrollContentContainer.Margin;
|
||||
}
|
||||
|
||||
public void ScrollTo(Drawable section) => scrollContainer.ScrollTo(scrollContainer.GetChildPosInContent(section) - (FixedHeader?.BoundingBox.Height ?? 0));
|
||||
public void ScrollTo(Drawable section) =>
|
||||
scrollContainer.ScrollTo(scrollContainer.GetChildPosInContent(section) - (FixedHeader?.BoundingBox.Height ?? 0));
|
||||
|
||||
public void ScrollToTop() => scrollContainer.ScrollTo(0);
|
||||
|
||||
[NotNull]
|
||||
protected virtual OsuScrollContainer CreateScrollContainer() => new OsuScrollContainer();
|
||||
|
||||
[NotNull]
|
||||
protected virtual FlowContainer<T> CreateScrollContentContainer() =>
|
||||
new FillFlowContainer<T>
|
||||
{
|
||||
Direction = FillDirection.Vertical,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
};
|
||||
|
||||
protected override bool OnInvalidate(Invalidation invalidation, InvalidationSource source)
|
||||
{
|
||||
var result = base.OnInvalidate(invalidation, source);
|
||||
@ -156,8 +158,6 @@ namespace osu.Game.Graphics.Containers
|
||||
return result;
|
||||
}
|
||||
|
||||
private float lastKnownScroll;
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
@ -208,5 +208,16 @@ namespace osu.Game.Graphics.Containers
|
||||
SelectedSection.Value = bestMatch;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateSectionsMargin()
|
||||
{
|
||||
if (!Children.Any()) return;
|
||||
|
||||
var newMargin = originalSectionsMargin;
|
||||
newMargin.Top += headerHeight;
|
||||
newMargin.Bottom += footerHeight;
|
||||
|
||||
scrollContentContainer.Margin = newMargin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -139,7 +139,7 @@ namespace osu.Game.Graphics
|
||||
return false;
|
||||
|
||||
dateText.Text = $"{date:d MMMM yyyy} ";
|
||||
timeText.Text = $"{date:hh:mm:ss \"UTC\"z}";
|
||||
timeText.Text = $"{date:HH:mm:ss \"UTC\"z}";
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -42,6 +42,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
BackgroundColourHover = Color4Extensions.FromHex(@"172023");
|
||||
|
||||
updateTextColour();
|
||||
|
||||
Item.Action.BindDisabledChanged(_ => updateState(), true);
|
||||
}
|
||||
|
||||
private void updateTextColour()
|
||||
@ -65,19 +67,33 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
sampleHover.Play();
|
||||
text.BoldText.FadeIn(transition_length, Easing.OutQuint);
|
||||
text.NormalText.FadeOut(transition_length, Easing.OutQuint);
|
||||
updateState();
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
text.BoldText.FadeOut(transition_length, Easing.OutQuint);
|
||||
text.NormalText.FadeIn(transition_length, Easing.OutQuint);
|
||||
updateState();
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
Alpha = Item.Action.Disabled ? 0.2f : 1;
|
||||
|
||||
if (IsHovered && !Item.Action.Disabled)
|
||||
{
|
||||
sampleHover.Play();
|
||||
text.BoldText.FadeIn(transition_length, Easing.OutQuint);
|
||||
text.NormalText.FadeOut(transition_length, Easing.OutQuint);
|
||||
}
|
||||
else
|
||||
{
|
||||
text.BoldText.FadeOut(transition_length, Easing.OutQuint);
|
||||
text.NormalText.FadeIn(transition_length, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
sampleClick.Play();
|
||||
|
@ -113,13 +113,13 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
private const float transition_length = 500;
|
||||
|
||||
private void fadeActive()
|
||||
protected void FadeHovered()
|
||||
{
|
||||
Bar.FadeIn(transition_length, Easing.OutQuint);
|
||||
Text.FadeColour(Color4.White, transition_length, Easing.OutQuint);
|
||||
}
|
||||
|
||||
private void fadeInactive()
|
||||
protected void FadeUnhovered()
|
||||
{
|
||||
Bar.FadeOut(transition_length, Easing.OutQuint);
|
||||
Text.FadeColour(AccentColour, transition_length, Easing.OutQuint);
|
||||
@ -128,14 +128,14 @@ namespace osu.Game.Graphics.UserInterface
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
if (!Active.Value)
|
||||
fadeActive();
|
||||
FadeHovered();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
if (!Active.Value)
|
||||
fadeInactive();
|
||||
FadeUnhovered();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -172,13 +172,19 @@ namespace osu.Game.Graphics.UserInterface
|
||||
},
|
||||
new HoverClickSounds()
|
||||
};
|
||||
|
||||
Active.BindValueChanged(active => Text.Font = Text.Font.With(Typeface.Torus, weight: active.NewValue ? FontWeight.Bold : FontWeight.Medium), true);
|
||||
}
|
||||
|
||||
protected override void OnActivated() => fadeActive();
|
||||
protected override void OnActivated()
|
||||
{
|
||||
Text.Font = Text.Font.With(weight: FontWeight.Bold);
|
||||
FadeHovered();
|
||||
}
|
||||
|
||||
protected override void OnDeactivated() => fadeInactive();
|
||||
protected override void OnDeactivated()
|
||||
{
|
||||
Text.Font = Text.Font.With(weight: FontWeight.Medium);
|
||||
FadeUnhovered();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,102 +0,0 @@
|
||||
// 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;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
public abstract class ScreenTitle : CompositeDrawable, IHasAccentColour
|
||||
{
|
||||
public const float ICON_WIDTH = ICON_SIZE + spacing;
|
||||
|
||||
public const float ICON_SIZE = 25;
|
||||
private const float spacing = 6;
|
||||
private const int text_offset = 2;
|
||||
|
||||
private SpriteIcon iconSprite;
|
||||
private readonly OsuSpriteText titleText, pageText;
|
||||
|
||||
protected IconUsage Icon
|
||||
{
|
||||
set
|
||||
{
|
||||
if (iconSprite == null)
|
||||
throw new InvalidOperationException($"Cannot use {nameof(Icon)} with a custom {nameof(CreateIcon)} function.");
|
||||
|
||||
iconSprite.Icon = value;
|
||||
}
|
||||
}
|
||||
|
||||
protected string Title
|
||||
{
|
||||
set => titleText.Text = value;
|
||||
}
|
||||
|
||||
protected string Section
|
||||
{
|
||||
set => pageText.Text = value;
|
||||
}
|
||||
|
||||
public Color4 AccentColour
|
||||
{
|
||||
get => pageText.Colour;
|
||||
set => pageText.Colour = value;
|
||||
}
|
||||
|
||||
protected virtual Drawable CreateIcon() => iconSprite = new SpriteIcon
|
||||
{
|
||||
Size = new Vector2(ICON_SIZE),
|
||||
};
|
||||
|
||||
protected ScreenTitle()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Spacing = new Vector2(spacing, 0),
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new[]
|
||||
{
|
||||
CreateIcon().With(t =>
|
||||
{
|
||||
t.Anchor = Anchor.Centre;
|
||||
t.Origin = Anchor.Centre;
|
||||
}),
|
||||
titleText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Bold),
|
||||
Margin = new MarginPadding { Bottom = text_offset }
|
||||
},
|
||||
new Circle
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(4),
|
||||
Colour = Color4.Gray,
|
||||
},
|
||||
pageText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Font = OsuFont.GetFont(size: 20),
|
||||
Margin = new MarginPadding { Bottom = text_offset }
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -1,40 +0,0 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom icon class for use with <see cref="ScreenTitle.CreateIcon()"/> based off a texture resource.
|
||||
/// </summary>
|
||||
public class ScreenTitleTextureIcon : CompositeDrawable
|
||||
{
|
||||
private readonly string textureName;
|
||||
|
||||
public ScreenTitleTextureIcon(string textureName)
|
||||
{
|
||||
this.textureName = textureName;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(TextureStore textures)
|
||||
{
|
||||
Size = new Vector2(ScreenTitle.ICON_SIZE);
|
||||
|
||||
InternalChild = new Sprite
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Texture = textures.Get(textureName),
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
FillMode = FillMode.Fit
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -43,13 +43,13 @@ namespace osu.Game.IO.Serialization.Converters
|
||||
var list = new List<T>();
|
||||
|
||||
var obj = JObject.Load(reader);
|
||||
var lookupTable = serializer.Deserialize<List<string>>(obj["lookup_table"].CreateReader());
|
||||
var lookupTable = serializer.Deserialize<List<string>>(obj["$lookup_table"].CreateReader());
|
||||
|
||||
foreach (var tok in obj["items"])
|
||||
foreach (var tok in obj["$items"])
|
||||
{
|
||||
var itemReader = tok.CreateReader();
|
||||
|
||||
var typeName = lookupTable[(int)tok["type"]];
|
||||
var typeName = lookupTable[(int)tok["$type"]];
|
||||
var instance = (T)Activator.CreateInstance(Type.GetType(typeName));
|
||||
serializer.Populate(itemReader, instance);
|
||||
|
||||
@ -61,7 +61,7 @@ namespace osu.Game.IO.Serialization.Converters
|
||||
|
||||
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
|
||||
{
|
||||
var list = (List<T>)value;
|
||||
var list = (IEnumerable<T>)value;
|
||||
|
||||
var lookupTable = new List<string>();
|
||||
var objects = new List<JObject>();
|
||||
@ -84,16 +84,16 @@ namespace osu.Game.IO.Serialization.Converters
|
||||
}
|
||||
|
||||
var itemObject = JObject.FromObject(item, serializer);
|
||||
itemObject.AddFirst(new JProperty("type", typeId));
|
||||
itemObject.AddFirst(new JProperty("$type", typeId));
|
||||
objects.Add(itemObject);
|
||||
}
|
||||
|
||||
writer.WriteStartObject();
|
||||
|
||||
writer.WritePropertyName("lookup_table");
|
||||
writer.WritePropertyName("$lookup_table");
|
||||
serializer.Serialize(writer, lookupTable);
|
||||
|
||||
writer.WritePropertyName("items");
|
||||
writer.WritePropertyName("$items");
|
||||
serializer.Serialize(writer, objects);
|
||||
|
||||
writer.WriteEndObject();
|
||||
|
@ -32,7 +32,7 @@ namespace osu.Game.IPC
|
||||
{
|
||||
if (importer == null)
|
||||
{
|
||||
//we want to contact a remote osu! to handle the import.
|
||||
// we want to contact a remote osu! to handle the import.
|
||||
await SendMessageAsync(new ArchiveImportMessage { Path = path });
|
||||
return;
|
||||
}
|
||||
|
@ -62,6 +62,14 @@ namespace osu.Game.Input.Bindings
|
||||
store.KeyBindingChanged -= ReloadMappings;
|
||||
}
|
||||
|
||||
protected override void ReloadMappings() => KeyBindings = store.Query(ruleset?.ID, variant).ToList();
|
||||
protected override void ReloadMappings()
|
||||
{
|
||||
if (ruleset != null && !ruleset.ID.HasValue)
|
||||
// if the provided ruleset is not stored to the database, we have no way to retrieve custom bindings.
|
||||
// fallback to defaults instead.
|
||||
KeyBindings = DefaultKeyBindings;
|
||||
else
|
||||
KeyBindings = store.Query(ruleset?.ID, variant).ToList();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -127,7 +127,7 @@ namespace osu.Game.Online.API
|
||||
|
||||
case APIState.Offline:
|
||||
case APIState.Connecting:
|
||||
//work to restore a connection...
|
||||
// work to restore a connection...
|
||||
if (!HasLogin)
|
||||
{
|
||||
State = APIState.Offline;
|
||||
@ -180,7 +180,7 @@ namespace osu.Game.Online.API
|
||||
break;
|
||||
}
|
||||
|
||||
//hard bail if we can't get a valid access token.
|
||||
// hard bail if we can't get a valid access token.
|
||||
if (authentication.RequestAccessToken() == null)
|
||||
{
|
||||
Logout();
|
||||
@ -274,7 +274,7 @@ namespace osu.Game.Online.API
|
||||
{
|
||||
req.Perform(this);
|
||||
|
||||
//we could still be in initialisation, at which point we don't want to say we're Online yet.
|
||||
// we could still be in initialisation, at which point we don't want to say we're Online yet.
|
||||
if (IsLoggedIn) State = APIState.Online;
|
||||
|
||||
failureCount = 0;
|
||||
@ -339,7 +339,7 @@ namespace osu.Game.Online.API
|
||||
log.Add($@"API failure count is now {failureCount}");
|
||||
|
||||
if (failureCount < 3)
|
||||
//we might try again at an api level.
|
||||
// we might try again at an api level.
|
||||
return false;
|
||||
|
||||
if (State == APIState.Online)
|
||||
|
@ -16,20 +16,35 @@ namespace osu.Game.Online.API
|
||||
{
|
||||
protected override WebRequest CreateWebRequest() => new OsuJsonWebRequest<T>(Uri);
|
||||
|
||||
public T Result => ((OsuJsonWebRequest<T>)WebRequest)?.ResponseObject;
|
||||
|
||||
protected APIRequest()
|
||||
{
|
||||
base.Success += onSuccess;
|
||||
}
|
||||
|
||||
private void onSuccess() => Success?.Invoke(Result);
|
||||
public T Result { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked on successful completion of an API request.
|
||||
/// This will be scheduled to the API's internal scheduler (run on update thread automatically).
|
||||
/// </summary>
|
||||
public new event APISuccessHandler<T> Success;
|
||||
|
||||
protected override void PostProcess()
|
||||
{
|
||||
base.PostProcess();
|
||||
Result = ((OsuJsonWebRequest<T>)WebRequest)?.ResponseObject;
|
||||
}
|
||||
|
||||
internal void TriggerSuccess(T result)
|
||||
{
|
||||
if (Result != null)
|
||||
throw new InvalidOperationException("Attempted to trigger success more than once");
|
||||
|
||||
Result = result;
|
||||
|
||||
TriggerSuccess();
|
||||
}
|
||||
|
||||
internal override void TriggerSuccess()
|
||||
{
|
||||
base.TriggerSuccess();
|
||||
Success?.Invoke(Result);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -83,7 +98,7 @@ namespace osu.Game.Online.API
|
||||
if (checkAndScheduleFailure())
|
||||
return;
|
||||
|
||||
if (!WebRequest.Aborted) //could have been aborted by a Cancel() call
|
||||
if (!WebRequest.Aborted) // could have been aborted by a Cancel() call
|
||||
{
|
||||
Logger.Log($@"Performing request {this}", LoggingTarget.Network);
|
||||
WebRequest.Perform();
|
||||
@ -92,14 +107,28 @@ namespace osu.Game.Online.API
|
||||
if (checkAndScheduleFailure())
|
||||
return;
|
||||
|
||||
PostProcess();
|
||||
|
||||
API.Schedule(delegate
|
||||
{
|
||||
if (cancelled) return;
|
||||
|
||||
Success?.Invoke();
|
||||
TriggerSuccess();
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Perform any post-processing actions after a successful request.
|
||||
/// </summary>
|
||||
protected virtual void PostProcess()
|
||||
{
|
||||
}
|
||||
|
||||
internal virtual void TriggerSuccess()
|
||||
{
|
||||
Success?.Invoke();
|
||||
}
|
||||
|
||||
public void Cancel() => Fail(new OperationCanceledException(@"Request cancelled"));
|
||||
|
||||
public void Fail(Exception e)
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
@ -30,6 +31,11 @@ namespace osu.Game.Online.API
|
||||
|
||||
private readonly List<IOnlineComponent> components = new List<IOnlineComponent>();
|
||||
|
||||
/// <summary>
|
||||
/// Provide handling logic for an arbitrary API request.
|
||||
/// </summary>
|
||||
public Action<APIRequest> HandleRequest;
|
||||
|
||||
public APIState State
|
||||
{
|
||||
get => state;
|
||||
@ -55,11 +61,16 @@ namespace osu.Game.Online.API
|
||||
|
||||
public virtual void Queue(APIRequest request)
|
||||
{
|
||||
HandleRequest?.Invoke(request);
|
||||
}
|
||||
|
||||
public void Perform(APIRequest request) { }
|
||||
public void Perform(APIRequest request) => HandleRequest?.Invoke(request);
|
||||
|
||||
public Task PerformAsync(APIRequest request) => Task.CompletedTask;
|
||||
public Task PerformAsync(APIRequest request)
|
||||
{
|
||||
HandleRequest?.Invoke(request);
|
||||
return Task.CompletedTask;
|
||||
}
|
||||
|
||||
public void Register(IOnlineComponent component)
|
||||
{
|
||||
|
@ -1,30 +1,40 @@
|
||||
// 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.ComponentModel;
|
||||
using osu.Framework.IO.Network;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Direct;
|
||||
using osu.Game.Overlays.BeatmapListing;
|
||||
using osu.Game.Rulesets;
|
||||
|
||||
namespace osu.Game.Online.API.Requests
|
||||
{
|
||||
public class SearchBeatmapSetsRequest : APIRequest<SearchBeatmapSetsResponse>
|
||||
{
|
||||
public SearchCategory SearchCategory { get; set; }
|
||||
|
||||
public SortCriteria SortCriteria { get; set; }
|
||||
|
||||
public SortDirection SortDirection { get; set; }
|
||||
|
||||
public SearchGenre Genre { get; set; }
|
||||
|
||||
public SearchLanguage Language { get; set; }
|
||||
|
||||
private readonly string query;
|
||||
private readonly RulesetInfo ruleset;
|
||||
private readonly BeatmapSearchCategory searchCategory;
|
||||
private readonly DirectSortCriteria sortCriteria;
|
||||
private readonly SortDirection direction;
|
||||
private string directionString => direction == SortDirection.Descending ? @"desc" : @"asc";
|
||||
|
||||
public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset, BeatmapSearchCategory searchCategory = BeatmapSearchCategory.Any, DirectSortCriteria sortCriteria = DirectSortCriteria.Ranked, SortDirection direction = SortDirection.Descending)
|
||||
private string directionString => SortDirection == SortDirection.Descending ? @"desc" : @"asc";
|
||||
|
||||
public SearchBeatmapSetsRequest(string query, RulesetInfo ruleset)
|
||||
{
|
||||
this.query = string.IsNullOrEmpty(query) ? string.Empty : System.Uri.EscapeDataString(query);
|
||||
this.ruleset = ruleset;
|
||||
this.searchCategory = searchCategory;
|
||||
this.sortCriteria = sortCriteria;
|
||||
this.direction = direction;
|
||||
|
||||
SearchCategory = SearchCategory.Any;
|
||||
SortCriteria = SortCriteria.Ranked;
|
||||
SortDirection = SortDirection.Descending;
|
||||
Genre = SearchGenre.Any;
|
||||
Language = SearchLanguage.Any;
|
||||
}
|
||||
|
||||
protected override WebRequest CreateWebRequest()
|
||||
@ -35,31 +45,19 @@ namespace osu.Game.Online.API.Requests
|
||||
if (ruleset.ID.HasValue)
|
||||
req.AddParameter("m", ruleset.ID.Value.ToString());
|
||||
|
||||
req.AddParameter("s", searchCategory.ToString().ToLowerInvariant());
|
||||
req.AddParameter("sort", $"{sortCriteria.ToString().ToLowerInvariant()}_{directionString}");
|
||||
req.AddParameter("s", SearchCategory.ToString().ToLowerInvariant());
|
||||
|
||||
if (Genre != SearchGenre.Any)
|
||||
req.AddParameter("g", ((int)Genre).ToString());
|
||||
|
||||
if (Language != SearchLanguage.Any)
|
||||
req.AddParameter("l", ((int)Language).ToString());
|
||||
|
||||
req.AddParameter("sort", $"{SortCriteria.ToString().ToLowerInvariant()}_{directionString}");
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
protected override string Target => @"beatmapsets/search";
|
||||
}
|
||||
|
||||
public enum BeatmapSearchCategory
|
||||
{
|
||||
Any,
|
||||
|
||||
[Description("Has Leaderboard")]
|
||||
Leaderboard,
|
||||
Ranked,
|
||||
Qualified,
|
||||
Loved,
|
||||
Favourites,
|
||||
|
||||
[Description("Pending & WIP")]
|
||||
Pending,
|
||||
Graveyard,
|
||||
|
||||
[Description("My Maps")]
|
||||
Mine,
|
||||
}
|
||||
}
|
||||
|
@ -61,7 +61,7 @@ namespace osu.Game.Online.Chat
|
||||
/// </summary>
|
||||
public event Action<Message> MessageRemoved;
|
||||
|
||||
public bool ReadOnly => false; //todo not yet used.
|
||||
public bool ReadOnly => false; // todo: not yet used.
|
||||
|
||||
public override string ToString() => Name;
|
||||
|
||||
|
@ -18,7 +18,7 @@ namespace osu.Game.Online.Chat
|
||||
/// <summary>
|
||||
/// Manages everything channel related
|
||||
/// </summary>
|
||||
public class ChannelManager : PollingComponent
|
||||
public class ChannelManager : PollingComponent, IChannelPostTarget
|
||||
{
|
||||
/// <summary>
|
||||
/// The channels the player joins on startup
|
||||
@ -93,6 +93,12 @@ namespace osu.Game.Online.Chat
|
||||
{
|
||||
if (!(e.NewValue is ChannelSelectorTabItem.ChannelSelectorTabChannel))
|
||||
JoinChannel(e.NewValue);
|
||||
|
||||
if (e.NewValue?.MessagesLoaded == false)
|
||||
{
|
||||
// let's fetch a small number of messages to bring us up-to-date with the backlog.
|
||||
fetchInitalMessages(e.NewValue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -204,6 +210,10 @@ namespace osu.Game.Online.Chat
|
||||
|
||||
switch (command)
|
||||
{
|
||||
case "np":
|
||||
AddInternal(new NowPlayingCommand());
|
||||
break;
|
||||
|
||||
case "me":
|
||||
if (string.IsNullOrWhiteSpace(content))
|
||||
{
|
||||
@ -234,7 +244,7 @@ namespace osu.Game.Online.Chat
|
||||
break;
|
||||
|
||||
case "help":
|
||||
target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action], /join [channel]"));
|
||||
target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action], /join [channel], /np"));
|
||||
break;
|
||||
|
||||
default:
|
||||
@ -371,12 +381,6 @@ namespace osu.Game.Online.Chat
|
||||
if (CurrentChannel.Value == null)
|
||||
CurrentChannel.Value = channel;
|
||||
|
||||
if (!channel.MessagesLoaded)
|
||||
{
|
||||
// let's fetch a small number of messages to bring us up-to-date with the backlog.
|
||||
fetchInitalMessages(channel);
|
||||
}
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
|
19
osu.Game/Online/Chat/IChannelPostTarget.cs
Normal file
19
osu.Game/Online/Chat/IChannelPostTarget.cs
Normal file
@ -0,0 +1,19 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
{
|
||||
[Cached(typeof(IChannelPostTarget))]
|
||||
public interface IChannelPostTarget
|
||||
{
|
||||
/// <summary>
|
||||
/// Posts a message to the currently opened channel.
|
||||
/// </summary>
|
||||
/// <param name="text">The message text that is going to be posted</param>
|
||||
/// <param name="isAction">Is true if the message is an action, e.g.: user is currently eating </param>
|
||||
/// <param name="target">An optional target channel. If null, <see cref="ChannelManager.CurrentChannel"/> will be used.</param>
|
||||
void PostMessage(string text, bool isAction = false, Channel target = null);
|
||||
}
|
||||
}
|
@ -78,13 +78,13 @@ namespace osu.Game.Online.Chat
|
||||
{
|
||||
result.Text = result.Text.Remove(index, m.Length).Insert(index, displayText);
|
||||
|
||||
//since we just changed the line display text, offset any already processed links.
|
||||
// since we just changed the line display text, offset any already processed links.
|
||||
result.Links.ForEach(l => l.Index -= l.Index > index ? m.Length - displayText.Length : 0);
|
||||
|
||||
var details = GetLinkDetails(linkText);
|
||||
result.Links.Add(new Link(linkText, index, displayText.Length, linkActionOverride ?? details.Action, details.Argument));
|
||||
|
||||
//adjust the offset for processing the current matches group.
|
||||
// adjust the offset for processing the current matches group.
|
||||
captureOffset += m.Length - displayText.Length;
|
||||
}
|
||||
}
|
||||
|
55
osu.Game/Online/Chat/NowPlayingCommand.cs
Normal file
55
osu.Game/Online/Chat/NowPlayingCommand.cs
Normal file
@ -0,0 +1,55 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Users;
|
||||
|
||||
namespace osu.Game.Online.Chat
|
||||
{
|
||||
public class NowPlayingCommand : Component
|
||||
{
|
||||
[Resolved]
|
||||
private IChannelPostTarget channelManager { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private Bindable<WorkingBeatmap> currentBeatmap { get; set; }
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
string verb;
|
||||
BeatmapInfo beatmap;
|
||||
|
||||
switch (api.Activity.Value)
|
||||
{
|
||||
case UserActivity.SoloGame solo:
|
||||
verb = "playing";
|
||||
beatmap = solo.Beatmap;
|
||||
break;
|
||||
|
||||
case UserActivity.Editing edit:
|
||||
verb = "editing";
|
||||
beatmap = edit.Beatmap;
|
||||
break;
|
||||
|
||||
default:
|
||||
verb = "listening to";
|
||||
beatmap = currentBeatmap.Value.BeatmapInfo;
|
||||
break;
|
||||
}
|
||||
|
||||
var beatmapString = beatmap.OnlineBeatmapID.HasValue ? $"[https://osu.ppy.sh/b/{beatmap.OnlineBeatmapID} {beatmap}]" : beatmap.ToString();
|
||||
|
||||
channelManager.PostMessage($"is {verb} {beatmapString}", true);
|
||||
Expire();
|
||||
}
|
||||
}
|
||||
}
|
@ -7,23 +7,31 @@ using osu.Game.Scoring;
|
||||
|
||||
namespace osu.Game.Online.Leaderboards
|
||||
{
|
||||
public class UpdateableRank : ModelBackedDrawable<ScoreRank>
|
||||
public class UpdateableRank : ModelBackedDrawable<ScoreRank?>
|
||||
{
|
||||
public ScoreRank Rank
|
||||
public ScoreRank? Rank
|
||||
{
|
||||
get => Model;
|
||||
set => Model = value;
|
||||
}
|
||||
|
||||
public UpdateableRank(ScoreRank rank)
|
||||
public UpdateableRank(ScoreRank? rank)
|
||||
{
|
||||
Rank = rank;
|
||||
}
|
||||
|
||||
protected override Drawable CreateDrawable(ScoreRank rank) => new DrawableRank(rank)
|
||||
protected override Drawable CreateDrawable(ScoreRank? rank)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
};
|
||||
if (rank.HasValue)
|
||||
{
|
||||
return new DrawableRank(rank.Value)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
};
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,7 +3,7 @@
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Threading;
|
||||
|
||||
namespace osu.Game.Online
|
||||
@ -11,7 +11,7 @@ namespace osu.Game.Online
|
||||
/// <summary>
|
||||
/// A component which requires a constant polling process.
|
||||
/// </summary>
|
||||
public abstract class PollingComponent : Component
|
||||
public abstract class PollingComponent : CompositeDrawable // switch away from Component because InternalChildren are used in usages.
|
||||
{
|
||||
private double? lastTimePolled;
|
||||
|
||||
|
@ -18,6 +18,7 @@ using osu.Game.Screens.Menu;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Development;
|
||||
@ -65,9 +66,9 @@ namespace osu.Game
|
||||
|
||||
private NowPlayingOverlay nowPlaying;
|
||||
|
||||
private DirectOverlay direct;
|
||||
private BeatmapListingOverlay beatmapListing;
|
||||
|
||||
private SocialOverlay social;
|
||||
private DashboardOverlay dashboard;
|
||||
|
||||
private UserProfileOverlay userProfile;
|
||||
|
||||
@ -97,6 +98,7 @@ namespace osu.Game
|
||||
|
||||
private MainMenu menuScreen;
|
||||
|
||||
[CanBeNull]
|
||||
private IntroScreen introScreen;
|
||||
|
||||
private Bindable<int> configRuleset;
|
||||
@ -315,8 +317,15 @@ namespace osu.Game
|
||||
/// The user should have already requested this interactively.
|
||||
/// </summary>
|
||||
/// <param name="beatmap">The beatmap to select.</param>
|
||||
public void PresentBeatmap(BeatmapSetInfo beatmap)
|
||||
/// <param name="difficultyCriteria">
|
||||
/// Optional predicate used to try and find a difficulty to select.
|
||||
/// If omitted, this will try to present the first beatmap from the current ruleset.
|
||||
/// In case of failure the first difficulty of the set will be presented, ignoring the predicate.
|
||||
/// </param>
|
||||
public void PresentBeatmap(BeatmapSetInfo beatmap, Predicate<BeatmapInfo> difficultyCriteria = null)
|
||||
{
|
||||
difficultyCriteria ??= b => b.Ruleset.Equals(Ruleset.Value);
|
||||
|
||||
var databasedSet = beatmap.OnlineBeatmapSetID != null
|
||||
? BeatmapManager.QueryBeatmapSet(s => s.OnlineBeatmapSetID == beatmap.OnlineBeatmapSetID)
|
||||
: BeatmapManager.QueryBeatmapSet(s => s.Hash == beatmap.Hash);
|
||||
@ -334,13 +343,13 @@ namespace osu.Game
|
||||
menuScreen.LoadToSolo();
|
||||
|
||||
// we might even already be at the song
|
||||
if (Beatmap.Value.BeatmapSetInfo.Hash == databasedSet.Hash)
|
||||
if (Beatmap.Value.BeatmapSetInfo.Hash == databasedSet.Hash && difficultyCriteria(Beatmap.Value.BeatmapInfo))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
// Use first beatmap available for current ruleset, else switch ruleset.
|
||||
var first = databasedSet.Beatmaps.Find(b => b.Ruleset.Equals(Ruleset.Value)) ?? databasedSet.Beatmaps.First();
|
||||
// Find first beatmap that matches our predicate.
|
||||
var first = databasedSet.Beatmaps.Find(difficultyCriteria) ?? databasedSet.Beatmaps.First();
|
||||
|
||||
Ruleset.Value = first.Ruleset;
|
||||
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(first);
|
||||
@ -602,9 +611,9 @@ namespace osu.Game
|
||||
|
||||
loadComponentSingleFile(screenshotManager, Add);
|
||||
|
||||
//overlay elements
|
||||
loadComponentSingleFile(direct = new DirectOverlay(), overlayContent.Add, true);
|
||||
loadComponentSingleFile(social = new SocialOverlay(), overlayContent.Add, true);
|
||||
// overlay elements
|
||||
loadComponentSingleFile(beatmapListing = new BeatmapListingOverlay(), overlayContent.Add, true);
|
||||
loadComponentSingleFile(dashboard = new DashboardOverlay(), overlayContent.Add, true);
|
||||
var rankingsOverlay = loadComponentSingleFile(new RankingsOverlay(), overlayContent.Add, true);
|
||||
loadComponentSingleFile(channelManager = new ChannelManager(), AddInternal, true);
|
||||
loadComponentSingleFile(chatOverlay = new ChatOverlay(), overlayContent.Add, true);
|
||||
@ -663,7 +672,7 @@ namespace osu.Game
|
||||
}
|
||||
|
||||
// ensure only one of these overlays are open at once.
|
||||
var singleDisplayOverlays = new OverlayContainer[] { chatOverlay, social, direct, changelogOverlay, rankingsOverlay };
|
||||
var singleDisplayOverlays = new OverlayContainer[] { chatOverlay, dashboard, beatmapListing, changelogOverlay, rankingsOverlay };
|
||||
|
||||
foreach (var overlay in singleDisplayOverlays)
|
||||
{
|
||||
@ -774,7 +783,7 @@ namespace osu.Game
|
||||
{
|
||||
var previousLoadStream = asyncLoadStream;
|
||||
|
||||
//chain with existing load stream
|
||||
// chain with existing load stream
|
||||
asyncLoadStream = Task.Run(async () =>
|
||||
{
|
||||
if (previousLoadStream != null)
|
||||
@ -835,7 +844,7 @@ namespace osu.Game
|
||||
return true;
|
||||
|
||||
case GlobalAction.ToggleSocial:
|
||||
social.ToggleVisibility();
|
||||
dashboard.ToggleVisibility();
|
||||
return true;
|
||||
|
||||
case GlobalAction.ResetInputSettings:
|
||||
@ -858,7 +867,7 @@ namespace osu.Game
|
||||
return true;
|
||||
|
||||
case GlobalAction.ToggleDirect:
|
||||
direct.ToggleVisibility();
|
||||
beatmapListing.ToggleVisibility();
|
||||
return true;
|
||||
|
||||
case GlobalAction.ToggleGameplayMouseButtons:
|
||||
@ -907,10 +916,7 @@ namespace osu.Game
|
||||
if (ScreenStack.CurrentScreen is Loader)
|
||||
return false;
|
||||
|
||||
if (introScreen == null)
|
||||
return true;
|
||||
|
||||
if (!introScreen.DidLoadMenu || !(ScreenStack.CurrentScreen is IntroScreen))
|
||||
if (introScreen?.DidLoadMenu == true && !(ScreenStack.CurrentScreen is IntroScreen))
|
||||
{
|
||||
Scheduler.Add(introScreen.MakeCurrent);
|
||||
return true;
|
||||
|
@ -168,7 +168,7 @@ namespace osu.Game
|
||||
|
||||
var defaultBeatmap = new DummyWorkingBeatmap(Audio, Textures);
|
||||
|
||||
dependencies.Cache(RulesetStore = new RulesetStore(contextFactory));
|
||||
dependencies.Cache(RulesetStore = new RulesetStore(contextFactory, Storage));
|
||||
dependencies.Cache(FileStore = new FileStore(contextFactory, Storage));
|
||||
|
||||
// ordering is important here to ensure foreign keys rules are not broken in ModelStore.Cleanup()
|
||||
|
@ -5,7 +5,7 @@ using osu.Framework.Bindables;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Online;
|
||||
|
||||
namespace osu.Game.Overlays.Direct
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
public abstract class BeatmapDownloadTrackingComposite : DownloadTrackingComposite<BeatmapSetInfo, BeatmapManager>
|
||||
{
|
164
osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs
Normal file
164
osu.Game/Overlays/BeatmapListing/BeatmapListingFilterControl.cs
Normal file
@ -0,0 +1,164 @@
|
||||
// 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;
|
||||
using System.Collections.Generic;
|
||||
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;
|
||||
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;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
public class BeatmapListingFilterControl : CompositeDrawable
|
||||
{
|
||||
public Action<List<BeatmapSetInfo>> SearchFinished;
|
||||
public Action SearchStarted;
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; }
|
||||
|
||||
private readonly BeatmapListingSearchControl searchControl;
|
||||
private readonly BeatmapListingSortTabControl sortControl;
|
||||
private readonly Box sortControlBackground;
|
||||
|
||||
private SearchBeatmapSetsRequest getSetsRequest;
|
||||
|
||||
public BeatmapListingFilterControl()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
InternalChild = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Masking = true,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Colour = Color4.Black.Opacity(0.25f),
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Radius = 3,
|
||||
Offset = new Vector2(0f, 1f),
|
||||
},
|
||||
Child = searchControl = new BeatmapListingSearchControl(),
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 40,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
sortControlBackground = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
sortControl = new BeatmapListingSortTabControl
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Margin = new MarginPadding { Left = 20 }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
sortControlBackground.Colour = colourProvider.Background5;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
var sortCriteria = sortControl.Current;
|
||||
var sortDirection = sortControl.SortDirection;
|
||||
|
||||
searchControl.Query.BindValueChanged(query =>
|
||||
{
|
||||
sortCriteria.Value = string.IsNullOrEmpty(query.NewValue) ? SortCriteria.Ranked : SortCriteria.Relevance;
|
||||
sortDirection.Value = SortDirection.Descending;
|
||||
queueUpdateSearch(true);
|
||||
});
|
||||
|
||||
searchControl.Ruleset.BindValueChanged(_ => queueUpdateSearch());
|
||||
searchControl.Category.BindValueChanged(_ => queueUpdateSearch());
|
||||
searchControl.Genre.BindValueChanged(_ => queueUpdateSearch());
|
||||
searchControl.Language.BindValueChanged(_ => queueUpdateSearch());
|
||||
|
||||
sortCriteria.BindValueChanged(_ => queueUpdateSearch());
|
||||
sortDirection.BindValueChanged(_ => queueUpdateSearch());
|
||||
}
|
||||
|
||||
private ScheduledDelegate queryChangedDebounce;
|
||||
|
||||
private void queueUpdateSearch(bool queryTextChanged = false)
|
||||
{
|
||||
SearchStarted?.Invoke();
|
||||
|
||||
getSetsRequest?.Cancel();
|
||||
|
||||
queryChangedDebounce?.Cancel();
|
||||
queryChangedDebounce = Scheduler.AddDelayed(updateSearch, queryTextChanged ? 500 : 100);
|
||||
}
|
||||
|
||||
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
|
||||
};
|
||||
|
||||
getSetsRequest.Success += response => Schedule(() => onSearchFinished(response));
|
||||
|
||||
api.Queue(getSetsRequest);
|
||||
}
|
||||
|
||||
private void onSearchFinished(SearchBeatmapSetsResponse response)
|
||||
{
|
||||
var beatmaps = response.BeatmapSets.Select(r => r.ToBeatmapSet(rulesets)).ToList();
|
||||
|
||||
searchControl.BeatmapSet = response.Total == 0 ? null : beatmaps.First();
|
||||
|
||||
SearchFinished?.Invoke(beatmaps);
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
getSetsRequest?.Cancel();
|
||||
queryChangedDebounce?.Cancel();
|
||||
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
|
||||
public void TakeFocus() => searchControl.TakeFocus();
|
||||
}
|
||||
}
|
@ -1,24 +1,19 @@
|
||||
// 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 osu.Framework.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
public class BeatmapListingHeader : OverlayHeader
|
||||
{
|
||||
protected override ScreenTitle CreateTitle() => new BeatmapListingTitle();
|
||||
protected override OverlayTitle CreateTitle() => new BeatmapListingTitle();
|
||||
|
||||
private class BeatmapListingTitle : ScreenTitle
|
||||
private class BeatmapListingTitle : OverlayTitle
|
||||
{
|
||||
public BeatmapListingTitle()
|
||||
{
|
||||
Title = @"beatmap";
|
||||
Section = @"listing";
|
||||
Title = "beatmap listing";
|
||||
IconTexture = "Icons/changelog";
|
||||
}
|
||||
|
||||
protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/changelog");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -5,8 +5,6 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Rulesets;
|
||||
using osuTK;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
@ -14,16 +12,21 @@ using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osuTK.Graphics;
|
||||
using osu.Game.Rulesets;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
public class BeatmapListingSearchSection : CompositeDrawable
|
||||
public class BeatmapListingSearchControl : CompositeDrawable
|
||||
{
|
||||
public Bindable<string> Query => textBox.Current;
|
||||
|
||||
public Bindable<RulesetInfo> Ruleset => modeFilter.Current;
|
||||
|
||||
public Bindable<BeatmapSearchCategory> Category => categoryFilter.Current;
|
||||
public Bindable<SearchCategory> Category => categoryFilter.Current;
|
||||
|
||||
public Bindable<SearchGenre> Genre => genreFilter.Current;
|
||||
|
||||
public Bindable<SearchLanguage> Language => languageFilter.Current;
|
||||
|
||||
public BeatmapSetInfo BeatmapSet
|
||||
{
|
||||
@ -42,12 +45,14 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
|
||||
private readonly BeatmapSearchTextBox textBox;
|
||||
private readonly BeatmapSearchRulesetFilterRow modeFilter;
|
||||
private readonly BeatmapSearchFilterRow<BeatmapSearchCategory> categoryFilter;
|
||||
private readonly BeatmapSearchFilterRow<SearchCategory> categoryFilter;
|
||||
private readonly BeatmapSearchFilterRow<SearchGenre> genreFilter;
|
||||
private readonly BeatmapSearchFilterRow<SearchLanguage> languageFilter;
|
||||
|
||||
private readonly Box background;
|
||||
private readonly UpdateableBeatmapSetCover beatmapCover;
|
||||
|
||||
public BeatmapListingSearchSection()
|
||||
public BeatmapListingSearchControl()
|
||||
{
|
||||
AutoSizeAxes = Axes.Y;
|
||||
RelativeSizeAxes = Axes.X;
|
||||
@ -97,7 +102,9 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
Children = new Drawable[]
|
||||
{
|
||||
modeFilter = new BeatmapSearchRulesetFilterRow(),
|
||||
categoryFilter = new BeatmapSearchFilterRow<BeatmapSearchCategory>(@"Categories"),
|
||||
categoryFilter = new BeatmapSearchFilterRow<SearchCategory>(@"Categories"),
|
||||
genreFilter = new BeatmapSearchFilterRow<SearchGenre>(@"Genre"),
|
||||
languageFilter = new BeatmapSearchFilterRow<SearchLanguage>(@"Language"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -105,7 +112,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
}
|
||||
});
|
||||
|
||||
Category.Value = BeatmapSearchCategory.Leaderboard;
|
||||
categoryFilter.Current.Value = SearchCategory.Leaderboard;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -114,6 +121,8 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
background.Colour = colourProvider.Dark6;
|
||||
}
|
||||
|
||||
public void TakeFocus() => textBox.TakeFocus();
|
||||
|
||||
private class BeatmapSearchTextBox : SearchTextBox
|
||||
{
|
||||
protected override Color4 SelectionColour => Color4.Gray;
|
@ -8,17 +8,16 @@ using osu.Framework.Graphics;
|
||||
using osuTK.Graphics;
|
||||
using osuTK;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Overlays.Direct;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
public class BeatmapListingSortTabControl : OverlaySortTabControl<DirectSortCriteria>
|
||||
public class BeatmapListingSortTabControl : OverlaySortTabControl<SortCriteria>
|
||||
{
|
||||
public readonly Bindable<SortDirection> SortDirection = new Bindable<SortDirection>(Overlays.SortDirection.Descending);
|
||||
|
||||
public BeatmapListingSortTabControl()
|
||||
{
|
||||
Current.Value = DirectSortCriteria.Ranked;
|
||||
Current.Value = SortCriteria.Ranked;
|
||||
}
|
||||
|
||||
protected override SortTabControl CreateControl() => new BeatmapSortTabControl
|
||||
@ -30,7 +29,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
public readonly Bindable<SortDirection> SortDirection = new Bindable<SortDirection>();
|
||||
|
||||
protected override TabItem<DirectSortCriteria> CreateTabItem(DirectSortCriteria value) => new BeatmapSortTabItem(value)
|
||||
protected override TabItem<SortCriteria> CreateTabItem(SortCriteria value) => new BeatmapSortTabItem(value)
|
||||
{
|
||||
SortDirection = { BindTarget = SortDirection }
|
||||
};
|
||||
@ -40,12 +39,12 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
public readonly Bindable<SortDirection> SortDirection = new Bindable<SortDirection>();
|
||||
|
||||
public BeatmapSortTabItem(DirectSortCriteria value)
|
||||
public BeatmapSortTabItem(SortCriteria value)
|
||||
: base(value)
|
||||
{
|
||||
}
|
||||
|
||||
protected override TabButton CreateTabButton(DirectSortCriteria value) => new BeatmapTabButton(value)
|
||||
protected override TabButton CreateTabButton(SortCriteria value) => new BeatmapTabButton(value)
|
||||
{
|
||||
Active = { BindTarget = Active },
|
||||
SortDirection = { BindTarget = SortDirection }
|
||||
@ -67,7 +66,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
|
||||
private readonly SpriteIcon icon;
|
||||
|
||||
public BeatmapTabButton(DirectSortCriteria value)
|
||||
public BeatmapTabButton(SortCriteria value)
|
||||
: base(value)
|
||||
{
|
||||
Add(icon = new SpriteIcon
|
||||
|
@ -15,6 +15,8 @@ using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using Humanizer;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
@ -53,8 +55,8 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Font = OsuFont.GetFont(size: 10),
|
||||
Text = headerName.ToUpper()
|
||||
Font = OsuFont.GetFont(size: 13),
|
||||
Text = headerName.Titleize()
|
||||
},
|
||||
CreateFilter().With(f =>
|
||||
{
|
||||
@ -81,7 +83,7 @@ namespace osu.Game.Overlays.BeatmapListing
|
||||
|
||||
if (typeof(T).IsEnum)
|
||||
{
|
||||
foreach (var val in (T[])Enum.GetValues(typeof(T)))
|
||||
foreach (var val in OrderAttributeUtils.GetValuesInOrder<T>())
|
||||
AddItem(val);
|
||||
}
|
||||
}
|
||||
|
@ -1,32 +0,0 @@
|
||||
// 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 osu.Framework.Graphics.UserInterface;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
public class BeatmapSearchSmallFilterRow<T> : BeatmapSearchFilterRow<T>
|
||||
{
|
||||
public BeatmapSearchSmallFilterRow(string headerName)
|
||||
: base(headerName)
|
||||
{
|
||||
}
|
||||
|
||||
protected override BeatmapSearchFilter CreateFilter() => new SmallBeatmapSearchFilter();
|
||||
|
||||
private class SmallBeatmapSearchFilter : BeatmapSearchFilter
|
||||
{
|
||||
protected override TabItem<T> CreateTabItem(T value) => new SmallTabItem(value);
|
||||
|
||||
private class SmallTabItem : FilterTabItem
|
||||
{
|
||||
public SmallTabItem(T value)
|
||||
: base(value)
|
||||
{
|
||||
}
|
||||
|
||||
protected override float TextSize => 10;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -26,9 +26,9 @@ using osu.Game.Graphics.UserInterface;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Direct
|
||||
namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
{
|
||||
public abstract class DirectPanel : OsuClickableContainer, IHasContextMenu
|
||||
public abstract class BeatmapPanel : OsuClickableContainer, IHasContextMenu
|
||||
{
|
||||
public readonly BeatmapSetInfo SetInfo;
|
||||
|
||||
@ -49,7 +49,7 @@ namespace osu.Game.Overlays.Direct
|
||||
|
||||
protected Action ViewBeatmap;
|
||||
|
||||
protected DirectPanel(BeatmapSetInfo setInfo)
|
||||
protected BeatmapPanel(BeatmapSetInfo setInfo)
|
||||
{
|
||||
Debug.Assert(setInfo.OnlineBeatmapSetID != null);
|
||||
|
||||
@ -148,7 +148,7 @@ namespace osu.Game.Overlays.Direct
|
||||
if (SetInfo.Beatmaps.Count > maximum_difficulty_icons)
|
||||
{
|
||||
foreach (var ruleset in SetInfo.Beatmaps.Select(b => b.Ruleset).Distinct())
|
||||
icons.Add(new GroupedDifficultyIcon(SetInfo.Beatmaps.FindAll(b => b.Ruleset.Equals(ruleset)), ruleset, this is DirectListPanel ? Color4.White : colours.Gray5));
|
||||
icons.Add(new GroupedDifficultyIcon(SetInfo.Beatmaps.FindAll(b => b.Ruleset.Equals(ruleset)), ruleset, this is ListBeatmapPanel ? Color4.White : colours.Gray5));
|
||||
}
|
||||
else
|
||||
{
|
@ -0,0 +1,99 @@
|
||||
// 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;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
{
|
||||
public class BeatmapPanelDownloadButton : BeatmapDownloadTrackingComposite
|
||||
{
|
||||
protected bool DownloadEnabled => button.Enabled.Value;
|
||||
|
||||
/// <summary>
|
||||
/// Currently selected beatmap. Used to present the correct difficulty after completing a download.
|
||||
/// </summary>
|
||||
public readonly IBindable<BeatmapInfo> SelectedBeatmap = new Bindable<BeatmapInfo>();
|
||||
|
||||
private readonly ShakeContainer shakeContainer;
|
||||
private readonly DownloadButton button;
|
||||
private Bindable<bool> noVideoSetting;
|
||||
|
||||
public BeatmapPanelDownloadButton(BeatmapSetInfo beatmapSet)
|
||||
: base(beatmapSet)
|
||||
{
|
||||
InternalChild = shakeContainer = new ShakeContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = button = new DownloadButton
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
button.State.BindTo(State);
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OsuGame game, BeatmapManager beatmaps, OsuConfigManager osuConfig)
|
||||
{
|
||||
noVideoSetting = osuConfig.GetBindable<bool>(OsuSetting.PreferNoVideo);
|
||||
|
||||
button.Action = () =>
|
||||
{
|
||||
switch (State.Value)
|
||||
{
|
||||
case DownloadState.Downloading:
|
||||
case DownloadState.Downloaded:
|
||||
shakeContainer.Shake();
|
||||
break;
|
||||
|
||||
case DownloadState.LocallyAvailable:
|
||||
Predicate<BeatmapInfo> findPredicate = null;
|
||||
if (SelectedBeatmap.Value != null)
|
||||
findPredicate = b => b.OnlineBeatmapID == SelectedBeatmap.Value.OnlineBeatmapID;
|
||||
|
||||
game?.PresentBeatmap(BeatmapSet.Value, findPredicate);
|
||||
break;
|
||||
|
||||
default:
|
||||
beatmaps.Download(BeatmapSet.Value, noVideoSetting.Value);
|
||||
break;
|
||||
}
|
||||
};
|
||||
|
||||
State.BindValueChanged(state =>
|
||||
{
|
||||
switch (state.NewValue)
|
||||
{
|
||||
case DownloadState.LocallyAvailable:
|
||||
button.Enabled.Value = true;
|
||||
button.TooltipText = string.Empty;
|
||||
break;
|
||||
|
||||
default:
|
||||
if (BeatmapSet.Value?.OnlineInfo?.Availability?.DownloadDisabled ?? false)
|
||||
{
|
||||
button.Enabled.Value = false;
|
||||
button.TooltipText = "this beatmap is currently not available for download.";
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
}
|
@ -10,7 +10,7 @@ using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Direct
|
||||
namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
{
|
||||
public class DownloadProgressBar : BeatmapDownloadTrackingComposite
|
||||
{
|
@ -1,25 +1,25 @@
|
||||
// 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 osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Direct
|
||||
namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
{
|
||||
public class DirectGridPanel : DirectPanel
|
||||
public class GridBeatmapPanel : BeatmapPanel
|
||||
{
|
||||
private const float horizontal_padding = 10;
|
||||
private const float vertical_padding = 5;
|
||||
@ -31,11 +31,11 @@ namespace osu.Game.Overlays.Direct
|
||||
protected override PlayButton PlayButton => playButton;
|
||||
protected override Box PreviewBar => progressBar;
|
||||
|
||||
public DirectGridPanel(BeatmapSetInfo beatmap)
|
||||
public GridBeatmapPanel(BeatmapSetInfo beatmap)
|
||||
: base(beatmap)
|
||||
{
|
||||
Width = 380;
|
||||
Height = 140 + vertical_padding; //full height of all the elements plus vertical padding (autosize uses the image)
|
||||
Height = 140 + vertical_padding; // full height of all the elements plus vertical padding (autosize uses the image)
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -156,7 +156,7 @@ namespace osu.Game.Overlays.Direct
|
||||
},
|
||||
},
|
||||
},
|
||||
new PanelDownloadButton(SetInfo)
|
||||
new BeatmapPanelDownloadButton(SetInfo)
|
||||
{
|
||||
Size = new Vector2(50, 30),
|
||||
Margin = new MarginPadding(horizontal_padding),
|
@ -8,7 +8,7 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Direct
|
||||
namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
{
|
||||
public class IconPill : CircularContainer
|
||||
{
|
@ -1,25 +1,25 @@
|
||||
// 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 osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Direct
|
||||
namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
{
|
||||
public class DirectListPanel : DirectPanel
|
||||
public class ListBeatmapPanel : BeatmapPanel
|
||||
{
|
||||
private const float transition_duration = 120;
|
||||
private const float horizontal_padding = 10;
|
||||
@ -27,7 +27,7 @@ namespace osu.Game.Overlays.Direct
|
||||
private const float height = 70;
|
||||
|
||||
private FillFlowContainer statusContainer;
|
||||
protected PanelDownloadButton DownloadButton;
|
||||
protected BeatmapPanelDownloadButton DownloadButton;
|
||||
private PlayButton playButton;
|
||||
private Box progressBar;
|
||||
|
||||
@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Direct
|
||||
protected override PlayButton PlayButton => playButton;
|
||||
protected override Box PreviewBar => progressBar;
|
||||
|
||||
public DirectListPanel(BeatmapSetInfo beatmap)
|
||||
public ListBeatmapPanel(BeatmapSetInfo beatmap)
|
||||
: base(beatmap)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
@ -151,7 +151,7 @@ namespace osu.Game.Overlays.Direct
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Child = DownloadButton = new PanelDownloadButton(SetInfo)
|
||||
Child = DownloadButton = new BeatmapPanelDownloadButton(SetInfo)
|
||||
{
|
||||
Size = new Vector2(height - vertical_padding * 3),
|
||||
Margin = new MarginPadding { Left = vertical_padding * 2, Right = vertical_padding },
|
@ -14,7 +14,7 @@ using osu.Game.Graphics.UserInterface;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Direct
|
||||
namespace osu.Game.Overlays.BeatmapListing.Panels
|
||||
{
|
||||
public class PlayButton : Container
|
||||
{
|
26
osu.Game/Overlays/BeatmapListing/SearchCategory.cs
Normal file
26
osu.Game/Overlays/BeatmapListing/SearchCategory.cs
Normal file
@ -0,0 +1,26 @@
|
||||
// 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.ComponentModel;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
public enum SearchCategory
|
||||
{
|
||||
Any,
|
||||
|
||||
[Description("Has Leaderboard")]
|
||||
Leaderboard,
|
||||
Ranked,
|
||||
Qualified,
|
||||
Loved,
|
||||
Favourites,
|
||||
|
||||
[Description("Pending & WIP")]
|
||||
Pending,
|
||||
Graveyard,
|
||||
|
||||
[Description("My Maps")]
|
||||
Mine,
|
||||
}
|
||||
}
|
25
osu.Game/Overlays/BeatmapListing/SearchGenre.cs
Normal file
25
osu.Game/Overlays/BeatmapListing/SearchGenre.cs
Normal file
@ -0,0 +1,25 @@
|
||||
// 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.ComponentModel;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
public enum SearchGenre
|
||||
{
|
||||
Any = 0,
|
||||
Unspecified = 1,
|
||||
|
||||
[Description("Video Game")]
|
||||
VideoGame = 2,
|
||||
Anime = 3,
|
||||
Rock = 4,
|
||||
Pop = 5,
|
||||
Other = 6,
|
||||
Novelty = 7,
|
||||
|
||||
[Description("Hip Hop")]
|
||||
HipHop = 9,
|
||||
Electronic = 10
|
||||
}
|
||||
}
|
47
osu.Game/Overlays/BeatmapListing/SearchLanguage.cs
Normal file
47
osu.Game/Overlays/BeatmapListing/SearchLanguage.cs
Normal file
@ -0,0 +1,47 @@
|
||||
// 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 osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
[HasOrderedElements]
|
||||
public enum SearchLanguage
|
||||
{
|
||||
[Order(0)]
|
||||
Any,
|
||||
|
||||
[Order(11)]
|
||||
Other,
|
||||
|
||||
[Order(1)]
|
||||
English,
|
||||
|
||||
[Order(6)]
|
||||
Japanese,
|
||||
|
||||
[Order(2)]
|
||||
Chinese,
|
||||
|
||||
[Order(10)]
|
||||
Instrumental,
|
||||
|
||||
[Order(7)]
|
||||
Korean,
|
||||
|
||||
[Order(3)]
|
||||
French,
|
||||
|
||||
[Order(4)]
|
||||
German,
|
||||
|
||||
[Order(9)]
|
||||
Swedish,
|
||||
|
||||
[Order(8)]
|
||||
Spanish,
|
||||
|
||||
[Order(5)]
|
||||
Italian
|
||||
}
|
||||
}
|
17
osu.Game/Overlays/BeatmapListing/SortCriteria.cs
Normal file
17
osu.Game/Overlays/BeatmapListing/SortCriteria.cs
Normal file
@ -0,0 +1,17 @@
|
||||
// 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.
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapListing
|
||||
{
|
||||
public enum SortCriteria
|
||||
{
|
||||
Title,
|
||||
Artist,
|
||||
Difficulty,
|
||||
Ranked,
|
||||
Rating,
|
||||
Plays,
|
||||
Favourites,
|
||||
Relevance
|
||||
}
|
||||
}
|
@ -1,27 +1,24 @@
|
||||
// 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 System.Threading;
|
||||
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;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Overlays.BeatmapListing;
|
||||
using osu.Game.Overlays.Direct;
|
||||
using osu.Game.Rulesets;
|
||||
using osu.Game.Overlays.BeatmapListing.Panels;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
@ -30,20 +27,17 @@ namespace osu.Game.Overlays
|
||||
[Resolved]
|
||||
private PreviewTrackManager previewTrackManager { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; }
|
||||
|
||||
private SearchBeatmapSetsRequest getSetsRequest;
|
||||
|
||||
private Drawable currentContent;
|
||||
private BeatmapListingSearchSection searchSection;
|
||||
private BeatmapListingSortTabControl sortControl;
|
||||
private LoadingLayer loadingLayer;
|
||||
private Container panelTarget;
|
||||
|
||||
public BeatmapListingOverlay()
|
||||
: base(OverlayColourScheme.Blue)
|
||||
{
|
||||
}
|
||||
|
||||
private BeatmapListingFilterControl filterControl;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
@ -54,7 +48,7 @@ namespace osu.Game.Overlays
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourProvider.Background6
|
||||
},
|
||||
new BasicScrollContainer
|
||||
new OverlayScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ScrollbarVisible = false,
|
||||
@ -63,27 +57,13 @@ namespace osu.Game.Overlays
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
new BeatmapListingHeader(),
|
||||
filterControl = new BeatmapListingFilterControl
|
||||
{
|
||||
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(),
|
||||
}
|
||||
SearchStarted = onSearchStarted,
|
||||
SearchFinished = onSearchFinished,
|
||||
},
|
||||
new Container
|
||||
{
|
||||
@ -96,154 +76,70 @@ namespace osu.Game.Overlays
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourProvider.Background4,
|
||||
},
|
||||
new FillFlowContainer
|
||||
panelTarget = new Container
|
||||
{
|
||||
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 }
|
||||
}
|
||||
}
|
||||
},
|
||||
new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Padding = new MarginPadding { Horizontal = 20 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
panelTarget = new Container
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
},
|
||||
loadingLayer = new LoadingLayer(panelTarget),
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Padding = new MarginPadding { Horizontal = 20 }
|
||||
},
|
||||
loadingLayer = new LoadingLayer(panelTarget)
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
protected override void OnFocus(FocusEvent e)
|
||||
{
|
||||
base.LoadComplete();
|
||||
base.OnFocus(e);
|
||||
|
||||
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;
|
||||
|
||||
queueUpdateSearch(true);
|
||||
});
|
||||
|
||||
searchSection.Ruleset.BindValueChanged(_ => queueUpdateSearch());
|
||||
searchSection.Category.BindValueChanged(_ => queueUpdateSearch());
|
||||
sortCriteria.BindValueChanged(_ => queueUpdateSearch());
|
||||
sortDirection.BindValueChanged(_ => queueUpdateSearch());
|
||||
filterControl.TakeFocus();
|
||||
}
|
||||
|
||||
private ScheduledDelegate queryChangedDebounce;
|
||||
private CancellationTokenSource cancellationToken;
|
||||
|
||||
private LoadingLayer loadingLayer;
|
||||
private Container panelTarget;
|
||||
|
||||
private void queueUpdateSearch(bool queryTextChanged = false)
|
||||
private void onSearchStarted()
|
||||
{
|
||||
getSetsRequest?.Cancel();
|
||||
|
||||
queryChangedDebounce?.Cancel();
|
||||
queryChangedDebounce = Scheduler.AddDelayed(updateSearch, queryTextChanged ? 500 : 100);
|
||||
}
|
||||
|
||||
private void updateSearch()
|
||||
{
|
||||
if (!IsLoaded)
|
||||
return;
|
||||
|
||||
if (State.Value == Visibility.Hidden)
|
||||
return;
|
||||
|
||||
if (API == null)
|
||||
return;
|
||||
cancellationToken?.Cancel();
|
||||
|
||||
previewTrackManager.StopAnyPlaying(this);
|
||||
|
||||
loadingLayer.Show();
|
||||
|
||||
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);
|
||||
if (panelTarget.Any())
|
||||
loadingLayer.Show();
|
||||
}
|
||||
|
||||
private void recreatePanels(SearchBeatmapSetsResponse response)
|
||||
private void onSearchFinished(List<BeatmapSetInfo> beatmaps)
|
||||
{
|
||||
if (response.Total == 0)
|
||||
if (!beatmaps.Any())
|
||||
{
|
||||
searchSection.BeatmapSet = null;
|
||||
LoadComponentAsync(new NotFoundDrawable(), addContentToPlaceholder);
|
||||
LoadComponentAsync(new NotFoundDrawable(), addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token);
|
||||
return;
|
||||
}
|
||||
|
||||
var beatmaps = response.BeatmapSets.Select(r => r.ToBeatmapSet(rulesets)).ToList();
|
||||
|
||||
var newPanels = new FillFlowContainer<DirectPanel>
|
||||
var newPanels = new FillFlowContainer<BeatmapPanel>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(10),
|
||||
Alpha = 0,
|
||||
Margin = new MarginPadding { Vertical = 15 },
|
||||
ChildrenEnumerable = beatmaps.Select<BeatmapSetInfo, DirectPanel>(b => new DirectGridPanel(b)
|
||||
ChildrenEnumerable = beatmaps.Select<BeatmapSetInfo, BeatmapPanel>(b => new GridBeatmapPanel(b)
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
})
|
||||
};
|
||||
|
||||
LoadComponentAsync(newPanels, loaded =>
|
||||
{
|
||||
addContentToPlaceholder(loaded);
|
||||
searchSection.BeatmapSet = beatmaps.First();
|
||||
});
|
||||
LoadComponentAsync(newPanels, addContentToPlaceholder, (cancellationToken = new CancellationTokenSource()).Token);
|
||||
}
|
||||
|
||||
private void addContentToPlaceholder(Drawable content)
|
||||
{
|
||||
loadingLayer.Hide();
|
||||
|
||||
Drawable lastContent = currentContent;
|
||||
var lastContent = currentContent;
|
||||
|
||||
if (lastContent != null)
|
||||
{
|
||||
@ -262,9 +158,7 @@ namespace osu.Game.Overlays
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
getSetsRequest?.Cancel();
|
||||
queryChangedDebounce?.Cancel();
|
||||
|
||||
cancellationToken?.Cancel();
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapSet
|
||||
@ -14,22 +13,20 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
|
||||
public BeatmapRulesetSelector RulesetSelector { get; private set; }
|
||||
|
||||
protected override ScreenTitle CreateTitle() => new BeatmapHeaderTitle();
|
||||
protected override OverlayTitle CreateTitle() => new BeatmapHeaderTitle();
|
||||
|
||||
protected override Drawable CreateTitleContent() => RulesetSelector = new BeatmapRulesetSelector
|
||||
{
|
||||
Current = Ruleset
|
||||
};
|
||||
|
||||
private class BeatmapHeaderTitle : ScreenTitle
|
||||
private class BeatmapHeaderTitle : OverlayTitle
|
||||
{
|
||||
public BeatmapHeaderTitle()
|
||||
{
|
||||
Title = @"beatmap";
|
||||
Section = @"info";
|
||||
Title = "beatmap info";
|
||||
IconTexture = "Icons/changelog";
|
||||
}
|
||||
|
||||
protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/changelog");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Overlays.Direct;
|
||||
using osu.Game.Overlays.BeatmapListing.Panels;
|
||||
using osu.Game.Users;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
@ -11,7 +11,7 @@ using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Overlays.Direct;
|
||||
using osu.Game.Overlays.BeatmapListing.Panels;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.BeatmapSet.Buttons
|
||||
|
@ -15,8 +15,8 @@ using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online;
|
||||
using osu.Game.Overlays.BeatmapListing.Panels;
|
||||
using osu.Game.Overlays.BeatmapSet.Buttons;
|
||||
using osu.Game.Overlays.Direct;
|
||||
using osu.Game.Rulesets;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
@ -140,7 +140,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Margin = new MarginPadding { Left = 3, Bottom = 4 }, //To better lineup with the font
|
||||
Margin = new MarginPadding { Left = 3, Bottom = 4 }, // To better lineup with the font
|
||||
},
|
||||
}
|
||||
},
|
||||
@ -264,7 +264,7 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
{
|
||||
if (BeatmapSet.Value == null) return;
|
||||
|
||||
if (BeatmapSet.Value.OnlineInfo.Availability?.DownloadDisabled ?? false)
|
||||
if ((BeatmapSet.Value.OnlineInfo.Availability?.DownloadDisabled ?? false) && State.Value != DownloadState.LocallyAvailable)
|
||||
{
|
||||
downloadButtonsContainer.Clear();
|
||||
return;
|
||||
@ -274,10 +274,11 @@ namespace osu.Game.Overlays.BeatmapSet
|
||||
{
|
||||
case DownloadState.LocallyAvailable:
|
||||
// temporary for UX until new design is implemented.
|
||||
downloadButtonsContainer.Child = new PanelDownloadButton(BeatmapSet.Value)
|
||||
downloadButtonsContainer.Child = new BeatmapPanelDownloadButton(BeatmapSet.Value)
|
||||
{
|
||||
Width = 50,
|
||||
RelativeSizeAxes = Axes.Y
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
SelectedBeatmap = { BindTarget = Picker.Beatmap }
|
||||
};
|
||||
break;
|
||||
|
||||
|
@ -39,7 +39,7 @@ namespace osu.Game.Overlays
|
||||
public BeatmapSetOverlay()
|
||||
: base(OverlayColourScheme.Blue)
|
||||
{
|
||||
OsuScrollContainer scroll;
|
||||
OverlayScrollContainer scroll;
|
||||
Info info;
|
||||
CommentsSection comments;
|
||||
|
||||
@ -49,7 +49,7 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
scroll = new OsuScrollContainer
|
||||
scroll = new OverlayScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ScrollbarVisible = false,
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
@ -16,6 +17,13 @@ namespace osu.Game.Overlays
|
||||
public OverlayHeaderBreadcrumbControl()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = 47;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
AccentColour = colourProvider.Light2;
|
||||
}
|
||||
|
||||
protected override TabItem<string> CreateTabItem(string value) => new ControlTabItem(value);
|
||||
@ -27,10 +35,18 @@ namespace osu.Game.Overlays
|
||||
public ControlTabItem(string value)
|
||||
: base(value)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
Text.Font = Text.Font.With(size: 14);
|
||||
Chevron.Y = 3;
|
||||
Text.Anchor = Anchor.CentreLeft;
|
||||
Text.Origin = Anchor.CentreLeft;
|
||||
Chevron.Y = 1;
|
||||
Bar.Height = 0;
|
||||
}
|
||||
|
||||
// base OsuTabItem makes font bold on activation, we don't want that here
|
||||
protected override void OnActivated() => FadeHovered();
|
||||
|
||||
protected override void OnDeactivated() => FadeUnhovered();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,6 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
|
||||
namespace osu.Game.Overlays.Changelog
|
||||
@ -50,8 +49,6 @@ namespace osu.Game.Overlays.Changelog
|
||||
streamsBackground.Colour = colourProvider.Background5;
|
||||
}
|
||||
|
||||
private ChangelogHeaderTitle title;
|
||||
|
||||
private void showBuild(ValueChangedEvent<APIChangelogBuild> e)
|
||||
{
|
||||
if (e.OldValue != null)
|
||||
@ -63,14 +60,11 @@ namespace osu.Game.Overlays.Changelog
|
||||
Current.Value = e.NewValue.ToString();
|
||||
|
||||
updateCurrentStream();
|
||||
|
||||
title.Version = e.NewValue.UpdateStream.DisplayName;
|
||||
}
|
||||
else
|
||||
{
|
||||
Current.Value = listing_string;
|
||||
Streams.Current.Value = null;
|
||||
title.Version = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -100,7 +94,7 @@ namespace osu.Game.Overlays.Changelog
|
||||
}
|
||||
};
|
||||
|
||||
protected override ScreenTitle CreateTitle() => title = new ChangelogHeaderTitle();
|
||||
protected override OverlayTitle CreateTitle() => new ChangelogHeaderTitle();
|
||||
|
||||
public void Populate(List<APIUpdateStream> streams)
|
||||
{
|
||||
@ -116,20 +110,13 @@ namespace osu.Game.Overlays.Changelog
|
||||
Streams.Current.Value = Streams.Items.FirstOrDefault(s => s.Name == Build.Value.UpdateStream.Name);
|
||||
}
|
||||
|
||||
private class ChangelogHeaderTitle : ScreenTitle
|
||||
private class ChangelogHeaderTitle : OverlayTitle
|
||||
{
|
||||
public string Version
|
||||
{
|
||||
set => Section = value ?? listing_string;
|
||||
}
|
||||
|
||||
public ChangelogHeaderTitle()
|
||||
{
|
||||
Title = "changelog";
|
||||
Version = null;
|
||||
IconTexture = "Icons/changelog";
|
||||
}
|
||||
|
||||
protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/changelog");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,79 +0,0 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Changelog
|
||||
{
|
||||
public class Comments : CompositeDrawable
|
||||
{
|
||||
private readonly APIChangelogBuild build;
|
||||
|
||||
public Comments(APIChangelogBuild build)
|
||||
{
|
||||
this.build = build;
|
||||
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Horizontal = 50,
|
||||
Vertical = 20,
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
LinkFlowContainer text;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
CornerRadius = 10,
|
||||
Child = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colours.GreyVioletDarker
|
||||
},
|
||||
},
|
||||
text = new LinkFlowContainer(t =>
|
||||
{
|
||||
t.Colour = colours.PinkLighter;
|
||||
t.Font = OsuFont.Default.With(size: 14);
|
||||
})
|
||||
{
|
||||
Padding = new MarginPadding(20),
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
}
|
||||
};
|
||||
|
||||
text.AddParagraph("Got feedback?", t =>
|
||||
{
|
||||
t.Colour = Color4.White;
|
||||
t.Font = OsuFont.Default.With(italics: true, size: 20);
|
||||
t.Padding = new MarginPadding { Bottom = 20 };
|
||||
});
|
||||
|
||||
text.AddParagraph("We would love to hear what you think of this update! ");
|
||||
text.AddIcon(FontAwesome.Regular.GrinHearts);
|
||||
|
||||
text.AddParagraph("Please visit the ");
|
||||
text.AddLink("web version", $"{build.Url}#comments");
|
||||
text.AddText(" of this changelog to leave any comments.");
|
||||
}
|
||||
}
|
||||
}
|
@ -50,7 +50,7 @@ namespace osu.Game.Overlays
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = ColourProvider.Background4,
|
||||
},
|
||||
new OsuScrollContainer
|
||||
new OverlayScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ScrollbarVisible = false,
|
||||
|
@ -105,6 +105,14 @@ namespace osu.Game.Overlays.Chat
|
||||
|
||||
private void newMessagesArrived(IEnumerable<Message> newMessages)
|
||||
{
|
||||
if (newMessages.Min(m => m.Id) < chatLines.Max(c => c.Message.Id))
|
||||
{
|
||||
// there is a case (on initial population) that we may receive past messages and need to reorder.
|
||||
// easiest way is to just combine messages and recreate drawables (less worrying about day separators etc.)
|
||||
newMessages = newMessages.Concat(chatLines.Select(c => c.Message)).OrderBy(m => m.Id).ToList();
|
||||
ChatLineFlow.Clear();
|
||||
}
|
||||
|
||||
bool shouldScrollToEnd = scroll.IsScrolledToEnd(10) || !chatLines.Any() || newMessages.Any(m => m is LocalMessage);
|
||||
|
||||
// Add up to last Channel.MAX_HISTORY messages
|
||||
|
@ -358,7 +358,7 @@ namespace osu.Game.Overlays
|
||||
|
||||
protected override void OnFocus(FocusEvent e)
|
||||
{
|
||||
//this is necessary as textbox is masked away and therefore can't get focus :(
|
||||
// this is necessary as textbox is masked away and therefore can't get focus :(
|
||||
textbox.TakeFocus();
|
||||
base.OnFocus(e);
|
||||
}
|
||||
|
@ -153,7 +153,7 @@ namespace osu.Game.Overlays.Comments
|
||||
request?.Cancel();
|
||||
loadCancellation?.Cancel();
|
||||
request = new GetCommentsRequest(id.Value, type.Value, Sort.Value, currentPage++, 0);
|
||||
request.Success += onSuccess;
|
||||
request.Success += res => Schedule(() => onSuccess(res));
|
||||
api.PerformAsync(request);
|
||||
}
|
||||
|
||||
|
24
osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs
Normal file
24
osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs
Normal file
@ -0,0 +1,24 @@
|
||||
// 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.
|
||||
|
||||
namespace osu.Game.Overlays.Dashboard
|
||||
{
|
||||
public class DashboardOverlayHeader : TabControlOverlayHeader<DashboardOverlayTabs>
|
||||
{
|
||||
protected override OverlayTitle CreateTitle() => new DashboardTitle();
|
||||
|
||||
private class DashboardTitle : OverlayTitle
|
||||
{
|
||||
public DashboardTitle()
|
||||
{
|
||||
Title = "dashboard";
|
||||
IconTexture = "Icons/changelog";
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public enum DashboardOverlayTabs
|
||||
{
|
||||
Friends
|
||||
}
|
||||
}
|
@ -16,7 +16,7 @@ using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Dashboard.Friends
|
||||
{
|
||||
public class FriendDisplay : CompositeDrawable
|
||||
public class FriendDisplay : OverlayView<List<User>>
|
||||
{
|
||||
private List<User> users = new List<User>();
|
||||
|
||||
@ -26,34 +26,29 @@ namespace osu.Game.Overlays.Dashboard.Friends
|
||||
set
|
||||
{
|
||||
users = value;
|
||||
|
||||
onlineStreamControl.Populate(value);
|
||||
}
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private IAPIProvider api { get; set; }
|
||||
|
||||
private GetFriendsRequest request;
|
||||
private CancellationTokenSource cancellationToken;
|
||||
|
||||
private Drawable currentContent;
|
||||
|
||||
private readonly FriendOnlineStreamControl onlineStreamControl;
|
||||
private readonly Box background;
|
||||
private readonly Box controlBackground;
|
||||
private readonly UserListToolbar userListToolbar;
|
||||
private readonly Container itemsPlaceholder;
|
||||
private readonly LoadingLayer loading;
|
||||
private FriendOnlineStreamControl onlineStreamControl;
|
||||
private Box background;
|
||||
private Box controlBackground;
|
||||
private UserListToolbar userListToolbar;
|
||||
private Container itemsPlaceholder;
|
||||
private LoadingLayer loading;
|
||||
|
||||
public FriendDisplay()
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
InternalChild = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
@ -134,11 +129,7 @@ namespace osu.Game.Overlays.Dashboard.Friends
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
background.Colour = colourProvider.Background4;
|
||||
controlBackground.Colour = colourProvider.Background5;
|
||||
}
|
||||
@ -152,14 +143,11 @@ namespace osu.Game.Overlays.Dashboard.Friends
|
||||
userListToolbar.SortCriteria.BindValueChanged(_ => recreatePanels());
|
||||
}
|
||||
|
||||
public void Fetch()
|
||||
{
|
||||
if (!api.IsLoggedIn)
|
||||
return;
|
||||
protected override APIRequest<List<User>> CreateRequest() => new GetFriendsRequest();
|
||||
|
||||
request = new GetFriendsRequest();
|
||||
request.Success += response => Schedule(() => Users = response);
|
||||
api.Queue(request);
|
||||
protected override void OnSuccess(List<User> response)
|
||||
{
|
||||
Users = response;
|
||||
}
|
||||
|
||||
private void recreatePanels()
|
||||
@ -258,9 +246,7 @@ namespace osu.Game.Overlays.Dashboard.Friends
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
request?.Cancel();
|
||||
cancellationToken?.Cancel();
|
||||
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
}
|
||||
|
150
osu.Game/Overlays/DashboardOverlay.cs
Normal file
150
osu.Game/Overlays/DashboardOverlay.cs
Normal file
@ -0,0 +1,150 @@
|
||||
// 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;
|
||||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Overlays.Dashboard;
|
||||
using osu.Game.Overlays.Dashboard.Friends;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
public class DashboardOverlay : FullscreenOverlay
|
||||
{
|
||||
private CancellationTokenSource cancellationToken;
|
||||
|
||||
private Box background;
|
||||
private Container content;
|
||||
private DashboardOverlayHeader header;
|
||||
private LoadingLayer loading;
|
||||
private OverlayScrollContainer scrollFlow;
|
||||
|
||||
public DashboardOverlay()
|
||||
: base(OverlayColourScheme.Purple)
|
||||
{
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
scrollFlow = new OverlayScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ScrollbarVisible = false,
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Direction = FillDirection.Vertical,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
header = new DashboardOverlayHeader
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Depth = -float.MaxValue
|
||||
},
|
||||
content = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
loading = new LoadingLayer(content),
|
||||
};
|
||||
|
||||
background.Colour = ColourProvider.Background5;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
header.Current.BindValueChanged(onTabChanged);
|
||||
}
|
||||
|
||||
private bool displayUpdateRequired = true;
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
base.PopIn();
|
||||
|
||||
// We don't want to create a new display on every call, only when exiting from fully closed state.
|
||||
if (displayUpdateRequired)
|
||||
{
|
||||
header.Current.TriggerChange();
|
||||
displayUpdateRequired = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void PopOutComplete()
|
||||
{
|
||||
base.PopOutComplete();
|
||||
loadDisplay(Empty());
|
||||
displayUpdateRequired = true;
|
||||
}
|
||||
|
||||
private void loadDisplay(Drawable display)
|
||||
{
|
||||
scrollFlow.ScrollToStart();
|
||||
|
||||
LoadComponentAsync(display, loaded =>
|
||||
{
|
||||
if (API.IsLoggedIn)
|
||||
loading.Hide();
|
||||
|
||||
content.Child = loaded;
|
||||
}, (cancellationToken = new CancellationTokenSource()).Token);
|
||||
}
|
||||
|
||||
private void onTabChanged(ValueChangedEvent<DashboardOverlayTabs> tab)
|
||||
{
|
||||
cancellationToken?.Cancel();
|
||||
loading.Show();
|
||||
|
||||
if (!API.IsLoggedIn)
|
||||
{
|
||||
loadDisplay(Empty());
|
||||
return;
|
||||
}
|
||||
|
||||
switch (tab.NewValue)
|
||||
{
|
||||
case DashboardOverlayTabs.Friends:
|
||||
loadDisplay(new FriendDisplay());
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new NotImplementedException($"Display for {tab.NewValue} tab is not implemented");
|
||||
}
|
||||
}
|
||||
|
||||
public override void APIStateChanged(IAPIProvider api, APIState state)
|
||||
{
|
||||
if (State.Value == Visibility.Hidden)
|
||||
return;
|
||||
|
||||
header.Current.TriggerChange();
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
cancellationToken?.Cancel();
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
}
|
||||
}
|
@ -50,7 +50,7 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
if (v != Visibility.Hidden) return;
|
||||
|
||||
//handle the dialog being dismissed.
|
||||
// handle the dialog being dismissed.
|
||||
dialog.Delay(PopupDialog.EXIT_DURATION).Expire();
|
||||
|
||||
if (dialog == CurrentDialog)
|
||||
|
@ -1,93 +0,0 @@
|
||||
// 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 osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Direct
|
||||
{
|
||||
public class DirectRulesetSelector : RulesetSelector
|
||||
{
|
||||
public override bool HandleNonPositionalInput => !Current.Disabled && base.HandleNonPositionalInput;
|
||||
|
||||
public override bool HandlePositionalInput => !Current.Disabled && base.HandlePositionalInput;
|
||||
|
||||
public override bool PropagatePositionalInputSubTree => !Current.Disabled && base.PropagatePositionalInputSubTree;
|
||||
|
||||
public DirectRulesetSelector()
|
||||
{
|
||||
TabContainer.Masking = false;
|
||||
TabContainer.Spacing = new Vector2(10, 0);
|
||||
AutoSizeAxes = Axes.Both;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Current.BindDisabledChanged(value => SelectedTab.FadeColour(value ? Color4.DarkGray : Color4.White, 200, Easing.OutQuint), true);
|
||||
}
|
||||
|
||||
protected override TabItem<RulesetInfo> CreateTabItem(RulesetInfo value) => new DirectRulesetTabItem(value);
|
||||
|
||||
protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer
|
||||
{
|
||||
Direction = FillDirection.Horizontal,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
};
|
||||
|
||||
private class DirectRulesetTabItem : TabItem<RulesetInfo>
|
||||
{
|
||||
private readonly ConstrainedIconContainer iconContainer;
|
||||
|
||||
public DirectRulesetTabItem(RulesetInfo value)
|
||||
: base(value)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
iconContainer = new ConstrainedIconContainer
|
||||
{
|
||||
Icon = value.CreateInstance().CreateIcon(),
|
||||
Size = new Vector2(32),
|
||||
},
|
||||
new HoverClickSounds()
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
updateState();
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
base.OnHover(e);
|
||||
updateState();
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
base.OnHoverLost(e);
|
||||
updateState();
|
||||
}
|
||||
|
||||
protected override void OnActivated() => updateState();
|
||||
|
||||
protected override void OnDeactivated() => updateState();
|
||||
|
||||
private void updateState() => iconContainer.FadeColour(IsHovered || Active.Value ? Color4.White : Color4.Gray, 120, Easing.InQuad);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,47 +0,0 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Overlays.SearchableList;
|
||||
using osu.Game.Rulesets;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Direct
|
||||
{
|
||||
public class FilterControl : SearchableListFilterControl<DirectSortCriteria, BeatmapSearchCategory>
|
||||
{
|
||||
private DirectRulesetSelector rulesetSelector;
|
||||
|
||||
protected override Color4 BackgroundColour => Color4Extensions.FromHex(@"384552");
|
||||
protected override DirectSortCriteria DefaultTab => DirectSortCriteria.Ranked;
|
||||
protected override BeatmapSearchCategory DefaultCategory => BeatmapSearchCategory.Leaderboard;
|
||||
|
||||
protected override Drawable CreateSupplementaryControls() => rulesetSelector = new DirectRulesetSelector();
|
||||
|
||||
public Bindable<RulesetInfo> Ruleset => rulesetSelector.Current;
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OsuColour colours, Bindable<RulesetInfo> ruleset)
|
||||
{
|
||||
DisplayStyleControl.Dropdown.AccentColour = colours.BlueDark;
|
||||
rulesetSelector.Current.BindTo(ruleset);
|
||||
}
|
||||
}
|
||||
|
||||
public enum DirectSortCriteria
|
||||
{
|
||||
Title,
|
||||
Artist,
|
||||
Difficulty,
|
||||
Ranked,
|
||||
Rating,
|
||||
Plays,
|
||||
Favourites,
|
||||
Relevance,
|
||||
}
|
||||
}
|
@ -1,43 +0,0 @@
|
||||
// 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.ComponentModel;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osuTK.Graphics;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays.SearchableList;
|
||||
|
||||
namespace osu.Game.Overlays.Direct
|
||||
{
|
||||
public class Header : SearchableListHeader<DirectTab>
|
||||
{
|
||||
protected override Color4 BackgroundColour => Color4Extensions.FromHex(@"252f3a");
|
||||
|
||||
protected override DirectTab DefaultTab => DirectTab.Search;
|
||||
protected override Drawable CreateHeaderText() => new OsuSpriteText { Text = @"osu!direct", Font = OsuFont.GetFont(size: 25) };
|
||||
protected override IconUsage Icon => OsuIcon.ChevronDownCircle;
|
||||
|
||||
public Header()
|
||||
{
|
||||
Tabs.Current.Value = DirectTab.NewestMaps;
|
||||
Tabs.Current.TriggerChange();
|
||||
}
|
||||
}
|
||||
|
||||
public enum DirectTab
|
||||
{
|
||||
Search,
|
||||
|
||||
[Description("Newest Maps")]
|
||||
NewestMaps = DirectSortCriteria.Ranked,
|
||||
|
||||
[Description("Top Rated")]
|
||||
TopRated = DirectSortCriteria.Rating,
|
||||
|
||||
[Description("Most Played")]
|
||||
MostPlayed = DirectSortCriteria.Plays,
|
||||
}
|
||||
}
|
@ -1,75 +0,0 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Online;
|
||||
|
||||
namespace osu.Game.Overlays.Direct
|
||||
{
|
||||
public class PanelDownloadButton : BeatmapDownloadTrackingComposite
|
||||
{
|
||||
protected bool DownloadEnabled => button.Enabled.Value;
|
||||
|
||||
private readonly bool noVideo;
|
||||
|
||||
private readonly ShakeContainer shakeContainer;
|
||||
private readonly DownloadButton button;
|
||||
|
||||
public PanelDownloadButton(BeatmapSetInfo beatmapSet, bool noVideo = false)
|
||||
: base(beatmapSet)
|
||||
{
|
||||
this.noVideo = noVideo;
|
||||
|
||||
InternalChild = shakeContainer = new ShakeContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = button = new DownloadButton
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
button.State.BindTo(State);
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader(true)]
|
||||
private void load(OsuGame game, BeatmapManager beatmaps)
|
||||
{
|
||||
if (BeatmapSet.Value?.OnlineInfo?.Availability?.DownloadDisabled ?? false)
|
||||
{
|
||||
button.Enabled.Value = false;
|
||||
button.TooltipText = "this beatmap is currently not available for download.";
|
||||
return;
|
||||
}
|
||||
|
||||
button.Action = () =>
|
||||
{
|
||||
switch (State.Value)
|
||||
{
|
||||
case DownloadState.Downloading:
|
||||
case DownloadState.Downloaded:
|
||||
shakeContainer.Shake();
|
||||
break;
|
||||
|
||||
case DownloadState.LocallyAvailable:
|
||||
game?.PresentBeatmap(BeatmapSet.Value);
|
||||
break;
|
||||
|
||||
default:
|
||||
beatmaps.Download(BeatmapSet.Value, noVideo);
|
||||
break;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -1,298 +0,0 @@
|
||||
// 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 System.Threading.Tasks;
|
||||
using Humanizer;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Audio;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Online.API.Requests;
|
||||
using osu.Game.Overlays.Direct;
|
||||
using osu.Game.Overlays.SearchableList;
|
||||
using osu.Game.Rulesets;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
public class DirectOverlay : SearchableListOverlay<DirectTab, DirectSortCriteria, BeatmapSearchCategory>
|
||||
{
|
||||
private const float panel_padding = 10f;
|
||||
|
||||
[Resolved]
|
||||
private RulesetStore rulesets { get; set; }
|
||||
|
||||
private readonly FillFlowContainer resultCountsContainer;
|
||||
private readonly OsuSpriteText resultCountsText;
|
||||
private FillFlowContainer<DirectPanel> panels;
|
||||
|
||||
protected override Color4 BackgroundColour => Color4Extensions.FromHex(@"485e74");
|
||||
protected override Color4 TrianglesColourLight => Color4Extensions.FromHex(@"465b71");
|
||||
protected override Color4 TrianglesColourDark => Color4Extensions.FromHex(@"3f5265");
|
||||
|
||||
protected override SearchableListHeader<DirectTab> CreateHeader() => new Header();
|
||||
protected override SearchableListFilterControl<DirectSortCriteria, BeatmapSearchCategory> CreateFilterControl() => new FilterControl();
|
||||
|
||||
private IEnumerable<BeatmapSetInfo> beatmapSets;
|
||||
|
||||
public IEnumerable<BeatmapSetInfo> BeatmapSets
|
||||
{
|
||||
get => beatmapSets;
|
||||
set
|
||||
{
|
||||
if (ReferenceEquals(beatmapSets, value)) return;
|
||||
|
||||
beatmapSets = value?.ToList();
|
||||
|
||||
if (beatmapSets == null) return;
|
||||
|
||||
var artists = new List<string>();
|
||||
var songs = new List<string>();
|
||||
var tags = new List<string>();
|
||||
|
||||
foreach (var s in beatmapSets)
|
||||
{
|
||||
artists.Add(s.Metadata.Artist);
|
||||
songs.Add(s.Metadata.Title);
|
||||
tags.AddRange(s.Metadata.Tags.Split(' '));
|
||||
}
|
||||
|
||||
ResultAmounts = new ResultCounts(distinctCount(artists), distinctCount(songs), distinctCount(tags));
|
||||
}
|
||||
}
|
||||
|
||||
private ResultCounts resultAmounts;
|
||||
|
||||
public ResultCounts ResultAmounts
|
||||
{
|
||||
get => resultAmounts;
|
||||
set
|
||||
{
|
||||
if (value == ResultAmounts) return;
|
||||
|
||||
resultAmounts = value;
|
||||
|
||||
updateResultCounts();
|
||||
}
|
||||
}
|
||||
|
||||
public DirectOverlay()
|
||||
: base(OverlayColourScheme.Blue)
|
||||
{
|
||||
ScrollFlow.Children = new Drawable[]
|
||||
{
|
||||
resultCountsContainer = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Margin = new MarginPadding { Top = 5 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = "Found ",
|
||||
Font = OsuFont.GetFont(size: 15)
|
||||
},
|
||||
resultCountsText = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold)
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
Filter.Search.Current.ValueChanged += text =>
|
||||
{
|
||||
if (!string.IsNullOrEmpty(text.NewValue))
|
||||
{
|
||||
Header.Tabs.Current.Value = DirectTab.Search;
|
||||
|
||||
if (Filter.Tabs.Current.Value == DirectSortCriteria.Ranked)
|
||||
Filter.Tabs.Current.Value = DirectSortCriteria.Relevance;
|
||||
}
|
||||
else
|
||||
{
|
||||
Header.Tabs.Current.Value = DirectTab.NewestMaps;
|
||||
|
||||
if (Filter.Tabs.Current.Value == DirectSortCriteria.Relevance)
|
||||
Filter.Tabs.Current.Value = DirectSortCriteria.Ranked;
|
||||
}
|
||||
};
|
||||
((FilterControl)Filter).Ruleset.ValueChanged += _ => queueUpdateSearch();
|
||||
Filter.DisplayStyleControl.DisplayStyle.ValueChanged += style => recreatePanels(style.NewValue);
|
||||
Filter.DisplayStyleControl.Dropdown.Current.ValueChanged += _ => queueUpdateSearch();
|
||||
|
||||
Header.Tabs.Current.ValueChanged += tab =>
|
||||
{
|
||||
if (tab.NewValue != DirectTab.Search)
|
||||
{
|
||||
currentQuery.Value = string.Empty;
|
||||
Filter.Tabs.Current.Value = (DirectSortCriteria)Header.Tabs.Current.Value;
|
||||
queueUpdateSearch();
|
||||
}
|
||||
};
|
||||
|
||||
currentQuery.ValueChanged += text => queueUpdateSearch(!string.IsNullOrEmpty(text.NewValue));
|
||||
|
||||
currentQuery.BindTo(Filter.Search.Current);
|
||||
|
||||
Filter.Tabs.Current.ValueChanged += tab =>
|
||||
{
|
||||
if (Header.Tabs.Current.Value != DirectTab.Search && tab.NewValue != (DirectSortCriteria)Header.Tabs.Current.Value)
|
||||
Header.Tabs.Current.Value = DirectTab.Search;
|
||||
|
||||
queueUpdateSearch();
|
||||
};
|
||||
|
||||
updateResultCounts();
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
resultCountsContainer.Colour = colours.Yellow;
|
||||
}
|
||||
|
||||
private void updateResultCounts()
|
||||
{
|
||||
resultCountsContainer.FadeTo(ResultAmounts == null ? 0f : 1f, 200, Easing.OutQuint);
|
||||
if (ResultAmounts == null) return;
|
||||
|
||||
resultCountsText.Text = "Artist".ToQuantity(ResultAmounts.Artists) + ", " +
|
||||
"Song".ToQuantity(ResultAmounts.Songs) + ", " +
|
||||
"Tag".ToQuantity(ResultAmounts.Tags);
|
||||
}
|
||||
|
||||
private void recreatePanels(PanelDisplayStyle displayStyle)
|
||||
{
|
||||
if (panels != null)
|
||||
{
|
||||
panels.FadeOut(200);
|
||||
panels.Expire();
|
||||
panels = null;
|
||||
}
|
||||
|
||||
if (BeatmapSets == null) return;
|
||||
|
||||
var newPanels = new FillFlowContainer<DirectPanel>
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(panel_padding),
|
||||
Margin = new MarginPadding { Top = 10 },
|
||||
ChildrenEnumerable = BeatmapSets.Select<BeatmapSetInfo, DirectPanel>(b =>
|
||||
{
|
||||
switch (displayStyle)
|
||||
{
|
||||
case PanelDisplayStyle.Grid:
|
||||
return new DirectGridPanel(b)
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
};
|
||||
|
||||
default:
|
||||
return new DirectListPanel(b);
|
||||
}
|
||||
})
|
||||
};
|
||||
|
||||
LoadComponentAsync(newPanels, p =>
|
||||
{
|
||||
if (panels != null) ScrollFlow.Remove(panels);
|
||||
ScrollFlow.Add(panels = newPanels);
|
||||
});
|
||||
}
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
base.PopIn();
|
||||
|
||||
// Queries are allowed to be run only on the first pop-in
|
||||
if (getSetsRequest == null)
|
||||
queueUpdateSearch();
|
||||
}
|
||||
|
||||
private SearchBeatmapSetsRequest getSetsRequest;
|
||||
|
||||
private readonly Bindable<string> currentQuery = new Bindable<string>(string.Empty);
|
||||
|
||||
private ScheduledDelegate queryChangedDebounce;
|
||||
|
||||
[Resolved]
|
||||
private PreviewTrackManager previewTrackManager { get; set; }
|
||||
|
||||
private void queueUpdateSearch(bool queryTextChanged = false)
|
||||
{
|
||||
BeatmapSets = null;
|
||||
ResultAmounts = null;
|
||||
|
||||
getSetsRequest?.Cancel();
|
||||
|
||||
queryChangedDebounce?.Cancel();
|
||||
queryChangedDebounce = Scheduler.AddDelayed(updateSearch, queryTextChanged ? 500 : 100);
|
||||
}
|
||||
|
||||
private void updateSearch()
|
||||
{
|
||||
if (!IsLoaded)
|
||||
return;
|
||||
|
||||
if (State.Value == Visibility.Hidden)
|
||||
return;
|
||||
|
||||
if (API == null)
|
||||
return;
|
||||
|
||||
previewTrackManager.StopAnyPlaying(this);
|
||||
|
||||
getSetsRequest = new SearchBeatmapSetsRequest(
|
||||
currentQuery.Value,
|
||||
((FilterControl)Filter).Ruleset.Value,
|
||||
Filter.DisplayStyleControl.Dropdown.Current.Value,
|
||||
Filter.Tabs.Current.Value); //todo: sort direction (?)
|
||||
|
||||
getSetsRequest.Success += response =>
|
||||
{
|
||||
Task.Run(() =>
|
||||
{
|
||||
var sets = response.BeatmapSets.Select(r => r.ToBeatmapSet(rulesets)).ToList();
|
||||
|
||||
// may not need scheduling; loads async internally.
|
||||
Schedule(() =>
|
||||
{
|
||||
BeatmapSets = sets;
|
||||
recreatePanels(Filter.DisplayStyleControl.DisplayStyle.Value);
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
API.Queue(getSetsRequest);
|
||||
}
|
||||
|
||||
private int distinctCount(List<string> list) => list.Distinct().ToArray().Length;
|
||||
|
||||
public class ResultCounts
|
||||
{
|
||||
public readonly int Artists;
|
||||
public readonly int Songs;
|
||||
public readonly int Tags;
|
||||
|
||||
public ResultCounts(int artists, int songs, int tags)
|
||||
{
|
||||
Artists = artists;
|
||||
Songs = songs;
|
||||
Tags = tags;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
// 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 osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Overlays.Settings;
|
||||
@ -9,7 +10,11 @@ namespace osu.Game.Overlays.KeyBinding
|
||||
{
|
||||
public class GlobalKeyBindingsSection : SettingsSection
|
||||
{
|
||||
public override IconUsage Icon => FontAwesome.Solid.Globe;
|
||||
public override Drawable CreateIcon() => new SpriteIcon
|
||||
{
|
||||
Icon = FontAwesome.Solid.Globe
|
||||
};
|
||||
|
||||
public override string Header => "Global";
|
||||
|
||||
public GlobalKeyBindingsSection(GlobalActionContainer manager)
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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 osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Overlays.Settings;
|
||||
@ -10,7 +11,11 @@ namespace osu.Game.Overlays.KeyBinding
|
||||
{
|
||||
public class RulesetBindingsSection : SettingsSection
|
||||
{
|
||||
public override IconUsage Icon => (ruleset.CreateInstance().CreateIcon() as SpriteIcon)?.Icon ?? OsuIcon.Hot;
|
||||
public override Drawable CreateIcon() => ruleset?.CreateInstance()?.CreateIcon() ?? new SpriteIcon
|
||||
{
|
||||
Icon = OsuIcon.Hot
|
||||
};
|
||||
|
||||
public override string Header => ruleset.Name;
|
||||
|
||||
private readonly RulesetInfo ruleset;
|
||||
|
@ -37,7 +37,6 @@ namespace osu.Game.Overlays.Mods
|
||||
protected readonly TriangleButton CloseButton;
|
||||
|
||||
protected readonly OsuSpriteText MultiplierLabel;
|
||||
protected readonly OsuSpriteText UnrankedLabel;
|
||||
|
||||
protected override bool BlockNonPositionalInput => false;
|
||||
|
||||
@ -57,6 +56,8 @@ namespace osu.Game.Overlays.Mods
|
||||
protected Color4 HighMultiplierColour;
|
||||
|
||||
private const float content_width = 0.8f;
|
||||
private const float footer_button_spacing = 20;
|
||||
|
||||
private readonly FillFlowContainer footerContainer;
|
||||
|
||||
private SampleChannel sampleOn, sampleOff;
|
||||
@ -103,7 +104,7 @@ namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
new Dimension(GridSizeMode.Absolute, 90),
|
||||
new Dimension(GridSizeMode.Distributed),
|
||||
new Dimension(GridSizeMode.Absolute, 70),
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
@ -197,7 +198,8 @@ namespace osu.Game.Overlays.Mods
|
||||
// Footer
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Origin = Anchor.TopCentre,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Children = new Drawable[]
|
||||
@ -215,7 +217,9 @@ namespace osu.Game.Overlays.Mods
|
||||
AutoSizeAxes = Axes.Y,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = content_width,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(footer_button_spacing, footer_button_spacing / 2),
|
||||
LayoutDuration = 100,
|
||||
LayoutEasing = Easing.OutQuint,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Vertical = 15,
|
||||
@ -228,10 +232,8 @@ namespace osu.Game.Overlays.Mods
|
||||
Width = 180,
|
||||
Text = "Deselect All",
|
||||
Action = DeselectAll,
|
||||
Margin = new MarginPadding
|
||||
{
|
||||
Right = 20
|
||||
}
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
},
|
||||
CustomiseButton = new TriangleButton
|
||||
{
|
||||
@ -239,49 +241,41 @@ namespace osu.Game.Overlays.Mods
|
||||
Text = "Customisation",
|
||||
Action = () => ModSettingsContainer.Alpha = ModSettingsContainer.Alpha == 1 ? 0 : 1,
|
||||
Enabled = { Value = false },
|
||||
Margin = new MarginPadding
|
||||
{
|
||||
Right = 20
|
||||
}
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
},
|
||||
CloseButton = new TriangleButton
|
||||
{
|
||||
Width = 180,
|
||||
Text = "Close",
|
||||
Action = Hide,
|
||||
Margin = new MarginPadding
|
||||
{
|
||||
Right = 20
|
||||
}
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
},
|
||||
new OsuSpriteText
|
||||
new FillFlowContainer
|
||||
{
|
||||
Text = @"Score Multiplier:",
|
||||
Font = OsuFont.GetFont(size: 30),
|
||||
Margin = new MarginPadding
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Spacing = new Vector2(footer_button_spacing / 2, 0),
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Top = 5,
|
||||
Right = 10
|
||||
}
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = @"Score Multiplier:",
|
||||
Font = OsuFont.GetFont(size: 30),
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
},
|
||||
MultiplierLabel = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold),
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Width = 70, // make width fixed so reflow doesn't occur when multiplier number changes.
|
||||
},
|
||||
},
|
||||
},
|
||||
MultiplierLabel = new OsuSpriteText
|
||||
{
|
||||
Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold),
|
||||
Margin = new MarginPadding
|
||||
{
|
||||
Top = 5
|
||||
}
|
||||
},
|
||||
UnrankedLabel = new OsuSpriteText
|
||||
{
|
||||
Text = @"(Unranked)",
|
||||
Font = OsuFont.GetFont(size: 30, weight: FontWeight.Bold),
|
||||
Margin = new MarginPadding
|
||||
{
|
||||
Top = 5,
|
||||
Left = 10
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -327,7 +321,6 @@ namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
LowMultiplierColour = colours.Red;
|
||||
HighMultiplierColour = colours.Green;
|
||||
UnrankedLabel.Colour = colours.Blue;
|
||||
|
||||
availableMods = osu.AvailableMods.GetBoundCopy();
|
||||
|
||||
@ -431,12 +424,10 @@ namespace osu.Game.Overlays.Mods
|
||||
private void updateMods()
|
||||
{
|
||||
var multiplier = 1.0;
|
||||
var ranked = true;
|
||||
|
||||
foreach (var mod in SelectedMods.Value)
|
||||
{
|
||||
multiplier *= mod.ScoreMultiplier;
|
||||
ranked &= mod.Ranked;
|
||||
}
|
||||
|
||||
MultiplierLabel.Text = $"{multiplier:N2}x";
|
||||
@ -446,8 +437,6 @@ namespace osu.Game.Overlays.Mods
|
||||
MultiplierLabel.FadeColour(LowMultiplierColour, 200);
|
||||
else
|
||||
MultiplierLabel.FadeColour(Color4.White, 200);
|
||||
|
||||
UnrankedLabel.FadeTo(ranked ? 0 : 1, 200);
|
||||
}
|
||||
|
||||
private void updateModSettings(ValueChangedEvent<IReadOnlyList<Mod>> selectedMods)
|
||||
|
@ -29,14 +29,8 @@ namespace osu.Game.Overlays.Music
|
||||
{
|
||||
public CollectionsMenu()
|
||||
{
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Colour = Color4.Black.Opacity(0.3f),
|
||||
Radius = 3,
|
||||
Offset = new Vector2(0f, 1f),
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
@ -78,7 +78,7 @@ namespace osu.Game.Overlays.Music
|
||||
{
|
||||
text.Clear();
|
||||
|
||||
//space after the title to put a space between the title and artist
|
||||
// space after the title to put a space between the title and artist
|
||||
titleSprites = text.AddText(title.Value + @" ", sprite => sprite.Font = OsuFont.GetFont(weight: FontWeight.Regular)).OfType<SpriteText>();
|
||||
|
||||
text.AddText(artist.Value, sprite =>
|
||||
|
@ -66,7 +66,7 @@ namespace osu.Game.Overlays
|
||||
beatmaps.ItemAdded += handleBeatmapAdded;
|
||||
beatmaps.ItemRemoved += handleBeatmapRemoved;
|
||||
|
||||
beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets().OrderBy(_ => RNG.Next()));
|
||||
beatmapSets.AddRange(beatmaps.GetAllUsableBeatmapSets(IncludedDetails.Minimal).OrderBy(_ => RNG.Next()));
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -172,10 +172,15 @@ namespace osu.Game.Overlays
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Play the previous track or restart the current track if it's current time below <see cref="restart_cutoff_point"/>
|
||||
/// Play the previous track or restart the current track if it's current time below <see cref="restart_cutoff_point"/>.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="PreviousTrackResult"/> that indicate the decided action</returns>
|
||||
public PreviousTrackResult PreviousTrack()
|
||||
public void PreviousTrack() => Schedule(() => prev());
|
||||
|
||||
/// <summary>
|
||||
/// Play the previous track or restart the current track if it's current time below <see cref="restart_cutoff_point"/>.
|
||||
/// </summary>
|
||||
/// <returns>The <see cref="PreviousTrackResult"/> that indicate the decided action.</returns>
|
||||
private PreviousTrackResult prev()
|
||||
{
|
||||
var currentTrackPosition = current?.Track.CurrentTime;
|
||||
|
||||
@ -204,8 +209,7 @@ namespace osu.Game.Overlays
|
||||
/// <summary>
|
||||
/// Play the next random or playlist track.
|
||||
/// </summary>
|
||||
/// <returns>Whether the operation was successful.</returns>
|
||||
public bool NextTrack() => next();
|
||||
public void NextTrack() => Schedule(() => next());
|
||||
|
||||
private bool next(bool instant = false)
|
||||
{
|
||||
@ -246,7 +250,7 @@ namespace osu.Game.Overlays
|
||||
}
|
||||
else
|
||||
{
|
||||
//figure out the best direction based on order in playlist.
|
||||
// figure out the best direction based on order in playlist.
|
||||
var last = BeatmapSets.TakeWhile(b => b.ID != current.BeatmapSetInfo?.ID).Count();
|
||||
var next = beatmap.NewValue == null ? -1 : BeatmapSets.TakeWhile(b => b.ID != beatmap.NewValue.BeatmapSetInfo?.ID).Count();
|
||||
|
||||
@ -319,13 +323,13 @@ namespace osu.Game.Overlays
|
||||
return true;
|
||||
|
||||
case GlobalAction.MusicNext:
|
||||
if (NextTrack())
|
||||
if (next())
|
||||
onScreenDisplay?.Display(new MusicControllerToast("Next track"));
|
||||
|
||||
return true;
|
||||
|
||||
case GlobalAction.MusicPrev:
|
||||
switch (PreviousTrack())
|
||||
switch (prev())
|
||||
{
|
||||
case PreviousTrackResult.Restart:
|
||||
onScreenDisplay?.Display(new MusicControllerToast("Restart track"));
|
||||
|
@ -162,7 +162,7 @@ namespace osu.Game.Overlays.News
|
||||
public string TooltipText => date.ToString("dddd dd MMMM yyyy hh:mm:ss UTCz").ToUpper();
|
||||
}
|
||||
|
||||
//fake API data struct to use for now as a skeleton for data, as there is no API struct for news article info for now
|
||||
// fake API data struct to use for now as a skeleton for data, as there is no API struct for news article info for now
|
||||
public class ArticleInfo
|
||||
{
|
||||
public string Title { get; set; }
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using System;
|
||||
|
||||
namespace osu.Game.Overlays.News
|
||||
@ -12,8 +11,6 @@ namespace osu.Game.Overlays.News
|
||||
{
|
||||
private const string front_page_string = "frontpage";
|
||||
|
||||
private NewsHeaderTitle title;
|
||||
|
||||
public readonly Bindable<string> Post = new Bindable<string>(null);
|
||||
|
||||
public Action ShowFrontPage;
|
||||
@ -40,36 +37,24 @@ namespace osu.Game.Overlays.News
|
||||
{
|
||||
TabControl.AddItem(e.NewValue);
|
||||
Current.Value = e.NewValue;
|
||||
|
||||
title.IsReadingPost = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
Current.Value = front_page_string;
|
||||
title.IsReadingPost = false;
|
||||
}
|
||||
}
|
||||
|
||||
protected override Drawable CreateBackground() => new OverlayHeaderBackground(@"Headers/news");
|
||||
|
||||
protected override ScreenTitle CreateTitle() => title = new NewsHeaderTitle();
|
||||
protected override OverlayTitle CreateTitle() => new NewsHeaderTitle();
|
||||
|
||||
private class NewsHeaderTitle : ScreenTitle
|
||||
private class NewsHeaderTitle : OverlayTitle
|
||||
{
|
||||
private const string post_string = "post";
|
||||
|
||||
public bool IsReadingPost
|
||||
{
|
||||
set => Section = value ? post_string : front_page_string;
|
||||
}
|
||||
|
||||
public NewsHeaderTitle()
|
||||
{
|
||||
Title = "news";
|
||||
IsReadingPost = false;
|
||||
IconTexture = "Icons/news";
|
||||
}
|
||||
|
||||
protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/news");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -8,7 +8,6 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Overlays.News;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
@ -36,7 +35,7 @@ namespace osu.Game.Overlays
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colours.PurpleDarkAlternative
|
||||
},
|
||||
new OsuScrollContainer
|
||||
new OverlayScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new FillFlowContainer
|
||||
|
@ -39,7 +39,7 @@ namespace osu.Game.Overlays.Notifications
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
//we may have received changes before we were displayed.
|
||||
// we may have received changes before we were displayed.
|
||||
updateState();
|
||||
}
|
||||
|
||||
|
@ -261,7 +261,7 @@ namespace osu.Game.Overlays
|
||||
// todo: this can likely be replaced with WorkingBeatmap.GetBeatmapAsync()
|
||||
Task.Run(() =>
|
||||
{
|
||||
if (beatmap?.Beatmap == null) //this is not needed if a placeholder exists
|
||||
if (beatmap?.Beatmap == null) // this is not needed if a placeholder exists
|
||||
{
|
||||
title.Text = @"Nothing to play";
|
||||
artist.Text = @"Nothing to play";
|
||||
|
@ -31,7 +31,7 @@ namespace osu.Game.Overlays.OSD
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new Container //this container exists just to set a minimum width for the toast
|
||||
new Container // this container exists just to set a minimum width for the toast
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
@ -6,15 +6,15 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
public abstract class OverlayHeader : Container
|
||||
{
|
||||
public const int CONTENT_X_MARGIN = 50;
|
||||
|
||||
private readonly Box titleBackground;
|
||||
private readonly ScreenTitle title;
|
||||
|
||||
protected readonly FillFlowContainer HeaderInfo;
|
||||
|
||||
@ -56,12 +56,11 @@ namespace osu.Game.Overlays
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Horizontal = UserProfileOverlay.CONTENT_X_MARGIN,
|
||||
Vertical = 10,
|
||||
Horizontal = CONTENT_X_MARGIN,
|
||||
},
|
||||
Children = new[]
|
||||
{
|
||||
title = CreateTitle().With(title =>
|
||||
CreateTitle().With(title =>
|
||||
{
|
||||
title.Anchor = Anchor.CentreLeft;
|
||||
title.Origin = Anchor.CentreLeft;
|
||||
@ -86,7 +85,6 @@ namespace osu.Game.Overlays
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
titleBackground.Colour = colourProvider.Dark5;
|
||||
title.AccentColour = colourProvider.Highlight1;
|
||||
}
|
||||
|
||||
[NotNull]
|
||||
@ -96,11 +94,11 @@ namespace osu.Game.Overlays
|
||||
protected virtual Drawable CreateBackground() => Empty();
|
||||
|
||||
/// <summary>
|
||||
/// Creates a <see cref="Drawable"/> on the opposite side of the <see cref="ScreenTitle"/>. Used mostly to create <see cref="OverlayRulesetSelector"/>.
|
||||
/// Creates a <see cref="Drawable"/> on the opposite side of the <see cref="OverlayTitle"/>. Used mostly to create <see cref="OverlayRulesetSelector"/>.
|
||||
/// </summary>
|
||||
[NotNull]
|
||||
protected virtual Drawable CreateTitleContent() => Empty();
|
||||
|
||||
protected abstract ScreenTitle CreateTitle();
|
||||
protected abstract OverlayTitle CreateTitle();
|
||||
}
|
||||
}
|
||||
|
@ -22,7 +22,7 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(25, 0),
|
||||
Spacing = new Vector2(20, 0),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ using osu.Game.Rulesets;
|
||||
using osuTK.Graphics;
|
||||
using osuTK;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
@ -53,6 +54,8 @@ namespace osu.Game.Overlays
|
||||
Origin = Anchor.Centre,
|
||||
Anchor = Anchor.Centre,
|
||||
Text = value.Name,
|
||||
Font = OsuFont.GetFont(size: 14),
|
||||
ShadowColour = Color4.Black.Opacity(0.75f)
|
||||
}
|
||||
},
|
||||
new HoverClickSounds()
|
||||
|
149
osu.Game/Overlays/OverlayScrollContainer.cs
Normal file
149
osu.Game/Overlays/OverlayScrollContainer.cs
Normal file
@ -0,0 +1,149 @@
|
||||
// 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 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;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
/// <summary>
|
||||
/// <see cref="OsuScrollContainer"/> which provides <see cref="ScrollToTopButton"/>. Mostly used in <see cref="FullscreenOverlay"/>.
|
||||
/// </summary>
|
||||
public class OverlayScrollContainer : OsuScrollContainer
|
||||
{
|
||||
/// <summary>
|
||||
/// Scroll position at which the <see cref="ScrollToTopButton"/> will be shown.
|
||||
/// </summary>
|
||||
private const int button_scroll_position = 200;
|
||||
|
||||
protected readonly ScrollToTopButton Button;
|
||||
|
||||
public OverlayScrollContainer()
|
||||
{
|
||||
AddInternal(Button = new ScrollToTopButton
|
||||
{
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
Margin = new MarginPadding(20),
|
||||
Action = () =>
|
||||
{
|
||||
ScrollToStart();
|
||||
Button.State = Visibility.Hidden;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
{
|
||||
base.UpdateAfterChildren();
|
||||
|
||||
if (ScrollContent.DrawHeight + button_scroll_position < DrawHeight)
|
||||
{
|
||||
Button.State = Visibility.Hidden;
|
||||
return;
|
||||
}
|
||||
|
||||
Button.State = Target > button_scroll_position ? Visibility.Visible : Visibility.Hidden;
|
||||
}
|
||||
|
||||
public class ScrollToTopButton : OsuHoverContainer
|
||||
{
|
||||
private const int fade_duration = 500;
|
||||
|
||||
private Visibility state;
|
||||
|
||||
public Visibility State
|
||||
{
|
||||
get => state;
|
||||
set
|
||||
{
|
||||
if (value == state)
|
||||
return;
|
||||
|
||||
state = value;
|
||||
Enabled.Value = state == Visibility.Visible;
|
||||
this.FadeTo(state == Visibility.Visible ? 1 : 0, fade_duration, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
protected override IEnumerable<Drawable> EffectTargets => new[] { background };
|
||||
|
||||
private Color4 flashColour;
|
||||
|
||||
private readonly Container content;
|
||||
private readonly Box background;
|
||||
|
||||
public ScrollToTopButton()
|
||||
{
|
||||
Size = new Vector2(50);
|
||||
Alpha = 0;
|
||||
Add(content = new CircularContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Masking = true,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Offset = new Vector2(0f, 1f),
|
||||
Radius = 3f,
|
||||
Colour = Color4.Black.Opacity(0.25f),
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(15),
|
||||
Icon = FontAwesome.Solid.ChevronUp
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
TooltipText = "Scroll to top";
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
IdleColour = colourProvider.Background6;
|
||||
HoverColour = colourProvider.Background5;
|
||||
flashColour = colourProvider.Light1;
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
background.FlashColour(flashColour, 800, Easing.OutQuint);
|
||||
return base.OnClick(e);
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e)
|
||||
{
|
||||
content.ScaleTo(0.75f, 2000, Easing.OutQuint);
|
||||
return true;
|
||||
}
|
||||
|
||||
protected override void OnMouseUp(MouseUpEvent e)
|
||||
{
|
||||
content.ScaleTo(1, 1000, Easing.OutElastic);
|
||||
base.OnMouseUp(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
@ -35,17 +36,22 @@ namespace osu.Game.Overlays
|
||||
protected OverlayTabControl()
|
||||
{
|
||||
TabContainer.Masking = false;
|
||||
TabContainer.Spacing = new Vector2(15, 0);
|
||||
TabContainer.Spacing = new Vector2(20, 0);
|
||||
|
||||
AddInternal(bar = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 2,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.CentreLeft
|
||||
Origin = Anchor.BottomLeft
|
||||
});
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
AccentColour = colourProvider.Highlight1;
|
||||
}
|
||||
|
||||
protected override Dropdown<T> CreateDropdown() => null;
|
||||
|
||||
protected override TabItem<T> CreateTabItem(T value) => new OverlayTabItem(value);
|
||||
@ -90,7 +96,7 @@ namespace osu.Game.Overlays
|
||||
Bar = new ExpandingBar
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
ExpandedSize = 7.5f,
|
||||
ExpandedSize = 5f,
|
||||
CollapsedSize = 0
|
||||
},
|
||||
new HoverClickSounds()
|
||||
@ -119,6 +125,7 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
HoverAction();
|
||||
Text.Font = Text.Font.With(weight: FontWeight.Bold);
|
||||
Text.FadeColour(Color4.White, 120, Easing.InQuad);
|
||||
}
|
||||
|
||||
protected override void OnDeactivated()
|
||||
@ -135,11 +142,7 @@ namespace osu.Game.Overlays
|
||||
OnDeactivated();
|
||||
}
|
||||
|
||||
protected virtual void HoverAction()
|
||||
{
|
||||
Bar.Expand();
|
||||
Text.FadeColour(Color4.White, 120, Easing.InQuad);
|
||||
}
|
||||
protected virtual void HoverAction() => Bar.Expand();
|
||||
|
||||
protected virtual void UnhoverAction()
|
||||
{
|
||||
|
80
osu.Game/Overlays/OverlayTitle.cs
Normal file
80
osu.Game/Overlays/OverlayTitle.cs
Normal file
@ -0,0 +1,80 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
public abstract class OverlayTitle : CompositeDrawable
|
||||
{
|
||||
private readonly OsuSpriteText title;
|
||||
private readonly Container icon;
|
||||
|
||||
protected string Title
|
||||
{
|
||||
set => title.Text = value;
|
||||
}
|
||||
|
||||
protected string IconTexture
|
||||
{
|
||||
set => icon.Child = new OverlayTitleIcon(value);
|
||||
}
|
||||
|
||||
protected OverlayTitle()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
|
||||
InternalChild = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Spacing = new Vector2(10, 0),
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
icon = new Container
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Margin = new MarginPadding { Horizontal = 5 }, // compensates for osu-web sprites having around 5px of whitespace on each side
|
||||
Size = new Vector2(30)
|
||||
},
|
||||
title = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Font = OsuFont.GetFont(size: 20, weight: FontWeight.Regular),
|
||||
Margin = new MarginPadding { Vertical = 17.5f } // 15px padding + 2.5px line-height difference compensation
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private class OverlayTitleIcon : Sprite
|
||||
{
|
||||
private readonly string textureName;
|
||||
|
||||
public OverlayTitleIcon(string textureName)
|
||||
{
|
||||
this.textureName = textureName;
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
FillMode = FillMode.Fit;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(TextureStore textures)
|
||||
{
|
||||
Texture = textures.Get(textureName);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user