Merge remote-tracking branch 'refs/remotes/ppy/master' into spotlights_api

This commit is contained in:
Andrei Zavatski
2020-02-03 19:34:35 +03:00
382 changed files with 6646 additions and 2786 deletions

View File

@ -19,6 +19,6 @@ namespace osu.Game.Audio
public IEnumerable<string> LookupNames => new[] { sampleName };
public int Volume { get; set; } = 100;
public int Volume { get; } = 100;
}
}

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Linq.Expressions;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
@ -26,6 +27,8 @@ using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
using Decoder = osu.Game.Beatmaps.Formats.Decoder;
using ZipArchive = SharpCompress.Archives.Zip.ZipArchive;
namespace osu.Game.Beatmaps
{
@ -56,14 +59,11 @@ namespace osu.Game.Beatmaps
protected override string ImportFromStablePath => "Songs";
private readonly RulesetStore rulesets;
private readonly BeatmapStore beatmaps;
private readonly AudioManager audioManager;
private readonly GameHost host;
private readonly BeatmapUpdateQueue updateQueue;
private readonly Storage exportStorage;
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, IAPIProvider api, AudioManager audioManager, GameHost host = null,
WorkingBeatmap defaultBeatmap = null)
@ -80,6 +80,7 @@ namespace osu.Game.Beatmaps
beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b);
updateQueue = new BeatmapUpdateQueue(api);
exportStorage = storage.GetStorageForDirectory("exports");
}
protected override ArchiveDownloadRequest<BeatmapSetInfo> CreateDownloadRequest(BeatmapSetInfo set, bool minimiseDownloadSize) =>
@ -174,6 +175,50 @@ namespace osu.Game.Beatmaps
/// <param name="beatmap">The beatmap difficulty to restore.</param>
public void Restore(BeatmapInfo beatmap) => beatmaps.Restore(beatmap);
/// <summary>
/// Saves an <see cref="IBeatmap"/> file against a given <see cref="BeatmapInfo"/>.
/// </summary>
/// <param name="info">The <see cref="BeatmapInfo"/> to save the content against. The file referenced by <see cref="BeatmapInfo.Path"/> will be replaced.</param>
/// <param name="beatmapContent">The <see cref="IBeatmap"/> content to write.</param>
public void Save(BeatmapInfo info, IBeatmap beatmapContent)
{
var setInfo = QueryBeatmapSet(s => s.Beatmaps.Any(b => b.ID == info.ID));
using (var stream = new MemoryStream())
{
using (var sw = new StreamWriter(stream, Encoding.UTF8, 1024, true))
new LegacyBeatmapEncoder(beatmapContent).Encode(sw);
stream.Seek(0, SeekOrigin.Begin);
UpdateFile(setInfo, setInfo.Files.Single(f => string.Equals(f.Filename, info.Path, StringComparison.OrdinalIgnoreCase)), stream);
}
var working = workingCache.FirstOrDefault(w => w.BeatmapInfo?.ID == info.ID);
if (working != null)
workingCache.Remove(working);
}
/// <summary>
/// Exports a <see cref="BeatmapSetInfo"/> to an .osz package.
/// </summary>
/// <param name="set">The <see cref="BeatmapSetInfo"/> to export.</param>
public void Export(BeatmapSetInfo set)
{
var localSet = QueryBeatmapSet(s => s.ID == set.ID);
using (var archive = ZipArchive.Create())
{
foreach (var file in localSet.Files)
archive.AddEntry(file.Filename, Files.Storage.GetStream(file.FileInfo.StoragePath));
using (var outputStream = exportStorage.GetStream($"{set}.osz", FileAccess.Write, FileMode.Create))
archive.SaveTo(outputStream);
exportStorage.OpenInNativeExplorer();
}
}
private readonly WeakList<WorkingBeatmap> workingCache = new WeakList<WorkingBeatmap>();
/// <summary>

View File

@ -239,11 +239,11 @@ namespace osu.Game.Beatmaps.Formats
break;
case @"Source":
beatmap.BeatmapInfo.Metadata.Source = pair.Value;
metadata.Source = pair.Value;
break;
case @"Tags":
beatmap.BeatmapInfo.Metadata.Tags = pair.Value;
metadata.Tags = pair.Value;
break;
case @"BeatmapID":
@ -300,13 +300,11 @@ namespace osu.Game.Beatmaps.Formats
switch (type)
{
case LegacyEventType.Background:
string bgFilename = split[2].Trim('"');
beatmap.BeatmapInfo.Metadata.BackgroundFile = bgFilename.ToStandardisedPath();
beatmap.BeatmapInfo.Metadata.BackgroundFile = CleanFilename(split[2]);
break;
case LegacyEventType.Video:
string videoFilename = split[2].Trim('"');
beatmap.BeatmapInfo.Metadata.VideoFile = videoFilename.ToStandardisedPath();
beatmap.BeatmapInfo.Metadata.VideoFile = CleanFilename(split[2]);
break;
case LegacyEventType.Break:

View File

@ -63,7 +63,7 @@ namespace osu.Game.Beatmaps.Formats
writer.WriteLine(FormattableString.Invariant($"PreviewTime: {beatmap.Metadata.PreviewTime}"));
// Todo: Not all countdown types are supported by lazer yet
writer.WriteLine(FormattableString.Invariant($"Countdown: {(beatmap.BeatmapInfo.Countdown ? '1' : '0')}"));
writer.WriteLine(FormattableString.Invariant($"SampleSet: {toLegacySampleBank(beatmap.ControlPointInfo.SamplePoints[0].SampleBank)}"));
writer.WriteLine(FormattableString.Invariant($"SampleSet: {toLegacySampleBank(beatmap.ControlPointInfo.SamplePointAt(double.MinValue).SampleBank)}"));
writer.WriteLine(FormattableString.Invariant($"StackLeniency: {beatmap.BeatmapInfo.StackLeniency}"));
writer.WriteLine(FormattableString.Invariant($"Mode: {beatmap.BeatmapInfo.RulesetID}"));
writer.WriteLine(FormattableString.Invariant($"LetterboxInBreaks: {(beatmap.BeatmapInfo.LetterboxInBreaks ? '1' : '0')}"));

View File

@ -3,6 +3,7 @@
using System;
using System.Collections.Generic;
using osu.Framework.Extensions;
using osu.Framework.Logging;
using osu.Game.Audio;
using osu.Game.Beatmaps.ControlPoints;
@ -115,7 +116,7 @@ namespace osu.Game.Beatmaps.Formats
protected KeyValuePair<string, string> SplitKeyVal(string line, char separator = ':')
{
var split = line.Trim().Split(new[] { separator }, 2);
var split = line.Split(separator, 2);
return new KeyValuePair<string, string>
(
@ -124,6 +125,8 @@ namespace osu.Game.Beatmaps.Formats
);
}
protected string CleanFilename(string path) => path.Trim('"').ToStandardisedPath();
protected enum Section
{
None,

View File

@ -7,7 +7,6 @@ using System.Globalization;
using System.IO;
using osuTK;
using osuTK.Graphics;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Game.IO;
using osu.Game.Storyboards;
@ -65,13 +64,16 @@ namespace osu.Game.Beatmaps.Formats
private void handleEvents(string line)
{
var depth = 0;
var lineSpan = line.AsSpan();
while (line.StartsWith(" ", StringComparison.Ordinal) || line.StartsWith("_", StringComparison.Ordinal))
while (lineSpan.StartsWith(" ", StringComparison.Ordinal) || lineSpan.StartsWith("_", StringComparison.Ordinal))
{
lineSpan = lineSpan.Slice(1);
++depth;
line = line.Substring(1);
}
line = lineSpan.ToString();
decodeVariables(ref line);
string[] split = line.Split(',');
@ -89,7 +91,7 @@ namespace osu.Game.Beatmaps.Formats
{
var layer = parseLayer(split[1]);
var origin = parseOrigin(split[2]);
var path = cleanFilename(split[3]);
var path = CleanFilename(split[3]);
var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo);
var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo);
storyboardSprite = new StoryboardSprite(path, origin, new Vector2(x, y));
@ -101,7 +103,7 @@ namespace osu.Game.Beatmaps.Formats
{
var layer = parseLayer(split[1]);
var origin = parseOrigin(split[2]);
var path = cleanFilename(split[3]);
var path = CleanFilename(split[3]);
var x = float.Parse(split[4], NumberFormatInfo.InvariantInfo);
var y = float.Parse(split[5], NumberFormatInfo.InvariantInfo);
var frameCount = int.Parse(split[6]);
@ -116,7 +118,7 @@ namespace osu.Game.Beatmaps.Formats
{
var time = double.Parse(split[1], CultureInfo.InvariantCulture);
var layer = parseLayer(split[2]);
var path = cleanFilename(split[3]);
var path = CleanFilename(split[3]);
var volume = split.Length > 4 ? float.Parse(split[4], CultureInfo.InvariantCulture) : 100;
storyboard.GetLayer(layer).Add(new StoryboardSampleInfo(path, time, (int)volume));
break;
@ -331,7 +333,5 @@ namespace osu.Game.Beatmaps.Formats
break;
}
}
private string cleanFilename(string path) => path.Trim('"').ToStandardisedPath();
}
}

View File

@ -7,13 +7,11 @@ using osu.Game.Rulesets.Mods;
using System;
using System.Collections.Generic;
using osu.Game.Storyboards;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Audio;
using osu.Framework.Statistics;
using osu.Game.IO.Serialization;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.UI;
@ -76,21 +74,6 @@ namespace osu.Game.Beatmaps
return AudioManager.Tracks.GetVirtual(length);
}
/// <summary>
/// Saves the <see cref="Beatmaps.Beatmap"/>.
/// </summary>
/// <returns>The absolute path of the output file.</returns>
public string Save()
{
string directory = Path.Combine(Path.GetTempPath(), @"osu!");
Directory.CreateDirectory(directory);
var path = Path.Combine(directory, Guid.NewGuid().ToString().Replace("-", string.Empty) + ".json");
using (var sw = new StreamWriter(path))
sw.WriteLine(Beatmap.Serialize());
return path;
}
/// <summary>
/// Creates a <see cref="IBeatmapConverter"/> to convert a <see cref="IBeatmap"/> for a specified <see cref="Ruleset"/>.
/// </summary>

View File

@ -24,7 +24,7 @@ namespace osu.Game.Configuration
Set(OsuSetting.ShowConvertedBeatmaps, true);
Set(OsuSetting.DisplayStarsMinimum, 0.0, 0, 10, 0.1);
Set(OsuSetting.DisplayStarsMaximum, 10.0, 0, 10, 0.1);
Set(OsuSetting.DisplayStarsMaximum, 10.1, 0, 10.1, 0.1);
Set(OsuSetting.SongSelectGroupingMode, GroupMode.All);
Set(OsuSetting.SongSelectSortingMode, SortMode.Title);
@ -85,6 +85,7 @@ namespace osu.Game.Configuration
Set(OsuSetting.HitLighting, true);
Set(OsuSetting.ShowInterface, true);
Set(OsuSetting.ShowProgressGraph, true);
Set(OsuSetting.ShowHealthDisplayWhenCantFail, true);
Set(OsuSetting.KeyOverlay, false);
Set(OsuSetting.ScoreMeter, ScoreMeterType.HitErrorBoth);
@ -150,6 +151,7 @@ namespace osu.Game.Configuration
ScoreMeter,
FloatingComments,
ShowInterface,
ShowProgressGraph,
ShowHealthDisplayWhenCantFail,
MouseDisableButtons,
MouseDisableWheel,

View File

@ -18,5 +18,14 @@ namespace osu.Game.Configuration
[Description("Hit Error (both)")]
HitErrorBoth,
[Description("Colour (left)")]
ColourLeft,
[Description("Colour (right)")]
ColourRight,
[Description("Colour (both)")]
ColourBoth,
}
}

View File

@ -35,16 +35,11 @@ namespace osu.Game.Configuration
{
public static IEnumerable<Drawable> CreateSettingsControls(this object obj)
{
foreach (var property in obj.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance))
foreach (var (attr, property) in obj.GetSettingsSourceProperties())
{
var attr = property.GetCustomAttribute<SettingSourceAttribute>(true);
object value = property.GetValue(obj);
if (attr == null)
continue;
var prop = property.GetValue(obj);
switch (prop)
switch (value)
{
case BindableNumber<float> bNumber:
yield return new SettingsSlider<float>
@ -102,9 +97,22 @@ namespace osu.Game.Configuration
break;
default:
throw new InvalidOperationException($"{nameof(SettingSourceAttribute)} was attached to an unsupported type ({prop})");
throw new InvalidOperationException($"{nameof(SettingSourceAttribute)} was attached to an unsupported type ({value})");
}
}
}
public static IEnumerable<(SettingSourceAttribute, PropertyInfo)> GetSettingsSourceProperties(this object obj)
{
foreach (var property in obj.GetType().GetProperties(BindingFlags.GetProperty | BindingFlags.Public | BindingFlags.Instance))
{
var attr = property.GetCustomAttribute<SettingSourceAttribute>(true);
if (attr == null)
continue;
yield return (attr, property);
}
}
}
}

View File

@ -34,7 +34,7 @@ namespace osu.Game.Database
/// <typeparam name="TFileModel">The associated file join type.</typeparam>
public abstract class ArchiveModelManager<TModel, TFileModel> : ICanAcceptFiles, IModelManager<TModel>
where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete
where TFileModel : INamedFileInfo, new()
where TFileModel : class, INamedFileInfo, new()
{
private const int import_queue_request_concurrency = 1;
@ -222,9 +222,8 @@ namespace osu.Game.Database
{
model = CreateModel(archive);
if (model == null) return Task.FromResult<TModel>(null);
model.Hash = computeHash(archive);
if (model == null)
return Task.FromResult<TModel>(null);
}
catch (TaskCanceledException)
{
@ -262,18 +261,24 @@ namespace osu.Game.Database
/// <remarks>
/// In the case of no matching files, a hash will be generated from the passed archive's <see cref="ArchiveReader.Name"/>.
/// </remarks>
private string computeHash(ArchiveReader reader)
private string computeHash(TModel item, ArchiveReader reader = null)
{
// for now, concatenate all .osu files in the set to create a unique hash.
MemoryStream hashable = new MemoryStream();
foreach (string file in reader.Filenames.Where(f => HashableFileTypes.Any(f.EndsWith)))
foreach (TFileModel file in item.Files.Where(f => HashableFileTypes.Any(f.Filename.EndsWith)))
{
using (Stream s = reader.GetStream(file))
using (Stream s = Files.Store.GetStream(file.FileInfo.StoragePath))
s.CopyTo(hashable);
}
return hashable.Length > 0 ? hashable.ComputeSHA2Hash() : reader.Name.ComputeSHA2Hash();
if (hashable.Length > 0)
return hashable.ComputeSHA2Hash();
if (reader != null)
return reader.Name.ComputeSHA2Hash();
return item.Hash;
}
/// <summary>
@ -303,6 +308,7 @@ namespace osu.Game.Database
LogForModel(item, "Beginning import...");
item.Files = archive != null ? createFileInfos(archive, Files) : new List<TFileModel>();
item.Hash = computeHash(item, archive);
await Populate(item, archive, cancellationToken);
@ -358,12 +364,42 @@ namespace osu.Game.Database
return item;
}, cancellationToken, TaskCreationOptions.HideScheduler, import_scheduler).Unwrap();
public void UpdateFile(TModel model, TFileModel file, Stream contents)
{
using (var usage = ContextFactory.GetForWrite())
{
// Dereference the existing file info, since the file model will be removed.
Files.Dereference(file.FileInfo);
// Remove the file model.
usage.Context.Set<TFileModel>().Remove(file);
// Add the new file info and containing file model.
model.Files.Remove(file);
model.Files.Add(new TFileModel
{
Filename = file.Filename,
FileInfo = Files.Add(contents)
});
Update(model);
}
}
/// <summary>
/// Perform an update of the specified item.
/// TODO: Support file changes.
/// TODO: Support file additions/removals.
/// </summary>
/// <param name="item">The item to update.</param>
public void Update(TModel item) => ModelStore.Update(item);
public void Update(TModel item)
{
using (ContextFactory.GetForWrite())
{
item.Hash = computeHash(item);
ModelStore.Update(item);
}
}
/// <summary>
/// Delete an item from the manager.

View File

@ -21,7 +21,7 @@ namespace osu.Game.Database
/// <typeparam name="TFileModel">The associated file join type.</typeparam>
public abstract class DownloadableArchiveModelManager<TModel, TFileModel> : ArchiveModelManager<TModel, TFileModel>, IModelDownloader<TModel>
where TModel : class, IHasFiles<TFileModel>, IHasPrimaryKey, ISoftDelete, IEquatable<TModel>
where TFileModel : INamedFileInfo, new()
where TFileModel : class, INamedFileInfo, new()
{
public event Action<ArchiveDownloadRequest<TModel>> DownloadBegan;

View File

@ -38,6 +38,11 @@ namespace osu.Game.Graphics.Containers
/// </summary>
public int Divisor { get; set; } = 1;
/// <summary>
/// An optional minimum beat length. Any beat length below this will be multiplied by two until valid.
/// </summary>
public double MinimumBeatLength { get; set; }
/// <summary>
/// Default length of a beat in milliseconds. Used whenever there is no beatmap or track playing.
/// </summary>
@ -89,6 +94,9 @@ namespace osu.Game.Graphics.Containers
double beatLength = timingPoint.BeatLength / Divisor;
while (beatLength < MinimumBeatLength)
beatLength *= 2;
int beatIndex = (int)((currentTrackTime - timingPoint.Time) / beatLength) - (effectPoint.OmitFirstBarLine ? 1 : 0);
// The beats before the start of the first control point are off by 1, this should do the trick

View File

@ -63,7 +63,7 @@ namespace osu.Game.Graphics.Containers
}
public void AddUserLink(User user, Action<SpriteText> creationParameters = null)
=> createLink(AddText(user.Username, creationParameters), new LinkDetails(LinkAction.OpenUserProfile, user.Id.ToString()), "View Profile");
=> createLink(AddText(user.Username, creationParameters), new LinkDetails(LinkAction.OpenUserProfile, user.Id.ToString()), "view profile");
private void createLink(IEnumerable<Drawable> drawables, LinkDetails link, string tooltipText, Action action = null)
{

View File

@ -76,12 +76,12 @@ namespace osu.Game.Graphics.Containers
return base.OnMouseDown(e);
}
protected override bool OnMouseUp(MouseUpEvent e)
protected override void OnMouseUp(MouseUpEvent e)
{
if (closeOnMouseUp && !base.ReceivePositionalInputAt(e.ScreenSpaceMousePosition))
Hide();
return base.OnMouseUp(e);
base.OnMouseUp(e);
}
public virtual bool OnPressed(GlobalAction action)
@ -99,7 +99,9 @@ namespace osu.Game.Graphics.Containers
return false;
}
public bool OnReleased(GlobalAction action) => false;
public void OnReleased(GlobalAction action)
{
}
protected override void UpdateState(ValueChangedEvent<Visibility> state)
{

View File

@ -50,15 +50,15 @@ namespace osu.Game.Graphics.Containers
return base.OnMouseDown(e);
}
protected override bool OnDrag(DragEvent e)
protected override void OnDrag(DragEvent e)
{
if (rightMouseDragging)
{
scrollFromMouseEvent(e);
return true;
return;
}
return base.OnDrag(e);
base.OnDrag(e);
}
protected override bool OnDragStart(DragStartEvent e)
@ -72,15 +72,15 @@ namespace osu.Game.Graphics.Containers
return base.OnDragStart(e);
}
protected override bool OnDragEnd(DragEndEvent e)
protected override void OnDragEnd(DragEndEvent e)
{
if (rightMouseDragging)
{
rightMouseDragging = false;
return true;
return;
}
return base.OnDragEnd(e);
base.OnDragEnd(e);
}
protected override bool OnScroll(ScrollEvent e)
@ -162,13 +162,13 @@ namespace osu.Game.Graphics.Containers
return true;
}
protected override bool OnMouseUp(MouseUpEvent e)
protected override void OnMouseUp(MouseUpEvent e)
{
if (e.Button != MouseButton.Left) return false;
if (e.Button != MouseButton.Left) return;
box.FadeColour(Color4.White, 100);
return base.OnMouseUp(e);
base.OnMouseUp(e);
}
}
}

View File

@ -92,7 +92,7 @@ namespace osu.Game.Graphics.Cursor
return base.OnMouseDown(e);
}
protected override bool OnMouseUp(MouseUpEvent e)
protected override void OnMouseUp(MouseUpEvent e)
{
if (!e.IsPressed(MouseButton.Left) && !e.IsPressed(MouseButton.Right))
{
@ -107,7 +107,7 @@ namespace osu.Game.Graphics.Cursor
dragRotationState = DragRotationState.NotDragging;
}
return base.OnMouseUp(e);
base.OnMouseUp(e);
}
protected override void PopIn()

View File

@ -29,9 +29,9 @@ namespace osu.Game.Graphics
}
}
public DrawableDate(DateTimeOffset date)
public DrawableDate(DateTimeOffset date, float textSize = OsuFont.DEFAULT_FONT_SIZE, bool italic = true)
{
Font = OsuFont.GetFont(weight: FontWeight.Regular, italics: true);
Font = OsuFont.GetFont(weight: FontWeight.Regular, size: textSize, italics: italic);
Date = date;
}

View File

@ -35,6 +35,20 @@ namespace osu.Game.Graphics
Convert.ToByte(hex.Substring(2, 2), 16),
Convert.ToByte(hex.Substring(4, 2), 16),
255);
case 4:
return new Color4(
(byte)(Convert.ToByte(hex.Substring(0, 1), 16) * 17),
(byte)(Convert.ToByte(hex.Substring(1, 1), 16) * 17),
(byte)(Convert.ToByte(hex.Substring(2, 1), 16) * 17),
(byte)(Convert.ToByte(hex.Substring(3, 1), 16) * 17));
case 8:
return new Color4(
Convert.ToByte(hex.Substring(0, 2), 16),
Convert.ToByte(hex.Substring(2, 2), 16),
Convert.ToByte(hex.Substring(4, 2), 16),
Convert.ToByte(hex.Substring(6, 2), 16));
}
}

View File

@ -67,7 +67,9 @@ namespace osu.Game.Graphics
return false;
}
public bool OnReleased(GlobalAction action) => false;
public void OnReleased(GlobalAction action)
{
}
private volatile int screenShotTasks;
@ -88,7 +90,7 @@ namespace osu.Game.Graphics
{
ScheduledDelegate waitDelegate = host.DrawThread.Scheduler.AddDelayed(() =>
{
if (framesWaited++ < frames_to_wait)
if (framesWaited++ >= frames_to_wait)
// ReSharper disable once AccessToDisposedClosure
framesWaitedEvent.Set();
}, 10, true);

View File

@ -67,7 +67,9 @@ namespace osu.Game.Graphics.UserInterface
return false;
}
public bool OnReleased(GlobalAction action) => action == GlobalAction.Back;
public void OnReleased(GlobalAction action)
{
}
}
}
}

View File

@ -34,13 +34,13 @@ namespace osu.Game.Graphics.UserInterface
var tIndex = TabContainer.IndexOf(t);
var tabIndex = TabContainer.IndexOf(TabMap[index.NewValue]);
t.State = tIndex < tabIndex ? Visibility.Hidden : Visibility.Visible;
t.Chevron.FadeTo(tIndex <= tabIndex ? 0f : 1f, 500, Easing.OutQuint);
t.State = tIndex > tabIndex ? Visibility.Hidden : Visibility.Visible;
t.Chevron.FadeTo(tIndex >= tabIndex ? 0f : 1f, 500, Easing.OutQuint);
}
};
}
protected class BreadcrumbTabItem : OsuTabItem, IStateful<Visibility>
public class BreadcrumbTabItem : OsuTabItem, IStateful<Visibility>
{
protected virtual float ChevronSize => 10;

View File

@ -232,11 +232,11 @@ namespace osu.Game.Graphics.UserInterface
return base.OnMouseDown(e);
}
protected override bool OnMouseUp(MouseUpEvent e)
protected override void OnMouseUp(MouseUpEvent e)
{
if (Selected.Value)
colourContainer.ResizeWidthTo(hover_width, click_duration, Easing.In);
return base.OnMouseUp(e);
base.OnMouseUp(e);
}
protected override bool OnHover(HoverEvent e)

View File

@ -57,6 +57,6 @@ namespace osu.Game.Graphics.UserInterface
return true;
}
public string TooltipText => "View in browser";
public string TooltipText => "view in browser";
}
}

View File

@ -80,7 +80,9 @@ namespace osu.Game.Graphics.UserInterface
return false;
}
public bool OnReleased(GlobalAction action) => false;
public void OnReleased(GlobalAction action)
{
}
public override bool RequestsFocus => HoldFocus;
}

View File

@ -24,7 +24,7 @@ namespace osu.Game.Graphics.UserInterface
/// <summary>
/// Length of debounce for hover sound playback, in milliseconds. Default is 50ms.
/// </summary>
public double HoverDebounceTime { get; set; } = 50;
public double HoverDebounceTime { get; } = 50;
protected readonly HoverSampleSet SampleSet;

View File

@ -107,10 +107,10 @@ namespace osu.Game.Graphics.UserInterface
return base.OnMouseDown(e);
}
protected override bool OnMouseUp(MouseUpEvent e)
protected override void OnMouseUp(MouseUpEvent e)
{
Content.ScaleTo(1, 1000, Easing.OutElastic);
return base.OnMouseUp(e);
base.OnMouseUp(e);
}
}
}

View File

@ -129,10 +129,10 @@ namespace osu.Game.Graphics.UserInterface
return base.OnMouseDown(e);
}
protected override bool OnMouseUp(MouseUpEvent e)
protected override void OnMouseUp(MouseUpEvent e)
{
Content.ScaleTo(1, 1000, Easing.OutElastic);
return base.OnMouseUp(e);
base.OnMouseUp(e);
}
protected virtual SpriteText CreateText() => new OsuSpriteText

View File

@ -104,7 +104,7 @@ namespace osu.Game.Graphics.UserInterface
private class CapsWarning : SpriteIcon, IHasTooltip
{
public string TooltipText => @"Caps lock is active";
public string TooltipText => @"caps lock is active";
public CapsWarning()
{

View File

@ -36,6 +36,11 @@ namespace osu.Game.Graphics.UserInterface
public virtual string TooltipText { get; private set; }
/// <summary>
/// Whether to format the tooltip as a percentage or the actual value.
/// </summary>
public bool DisplayAsPercentage { get; set; }
private Color4 accentColour;
public Color4 AccentColour
@ -128,10 +133,10 @@ namespace osu.Game.Graphics.UserInterface
return base.OnMouseDown(e);
}
protected override bool OnMouseUp(MouseUpEvent e)
protected override void OnMouseUp(MouseUpEvent e)
{
Nub.Current.Value = false;
return base.OnMouseUp(e);
base.OnMouseUp(e);
}
protected override void OnUserChange(T value)
@ -169,11 +174,11 @@ namespace osu.Game.Graphics.UserInterface
else
{
double floatValue = value.ToDouble(NumberFormatInfo.InvariantInfo);
double floatMinValue = CurrentNumber.MinValue.ToDouble(NumberFormatInfo.InvariantInfo);
double floatMaxValue = CurrentNumber.MaxValue.ToDouble(NumberFormatInfo.InvariantInfo);
if (floatMaxValue == 1 && floatMinValue >= -1)
TooltipText = floatValue.ToString("P0");
if (DisplayAsPercentage)
{
TooltipText = floatValue.ToString("0%");
}
else
{
var decimalPrecision = normalise(CurrentNumber.Precision.ToDecimal(NumberFormatInfo.InvariantInfo), max_decimal_digits);

View File

@ -10,7 +10,6 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
@ -22,6 +21,22 @@ namespace osu.Game.Graphics.UserInterface
{
public class OsuTabControl<T> : TabControl<T>
{
private Color4 accentColour;
public virtual Color4 AccentColour
{
get => accentColour;
set
{
accentColour = value;
if (Dropdown is IHasAccentColour dropdown)
dropdown.AccentColour = value;
foreach (var i in TabContainer.Children.OfType<IHasAccentColour>())
i.AccentColour = value;
}
}
private readonly Box strip;
protected override Dropdown<T> CreateDropdown() => new OsuTabDropdown();
@ -63,35 +78,12 @@ namespace osu.Game.Graphics.UserInterface
AccentColour = colours.Blue;
}
private Color4 accentColour;
public Color4 AccentColour
{
get => accentColour;
set
{
accentColour = value;
if (Dropdown is IHasAccentColour dropdown)
dropdown.AccentColour = value;
foreach (var i in TabContainer.Children.OfType<IHasAccentColour>())
i.AccentColour = value;
}
}
public Color4 StripColour
{
get => strip.Colour;
set => strip.Colour = value;
}
protected override TabFillFlowContainer CreateTabFlow() => new OsuTabFillFlowContainer
{
Direction = FillDirection.Full,
RelativeSizeAxes = Axes.Both,
Depth = -1,
Masking = true
};
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
@ -283,10 +275,5 @@ namespace osu.Game.Graphics.UserInterface
}
}
}
private class OsuTabFillFlowContainer : TabFillFlowContainer
{
protected override int Compare(Drawable x, Drawable y) => CompareReverseChildID(x, y);
}
}
}

View File

@ -16,11 +16,7 @@ namespace osu.Game.Graphics.UserInterface
/// <summary>
/// How many leading zeroes the counter has.
/// </summary>
public uint LeadingZeroes
{
get;
protected set;
}
public uint LeadingZeroes { get; }
/// <summary>
/// Displays score.

View File

@ -17,7 +17,8 @@ namespace osu.Game.Graphics.UserInterface
stack.ScreenPushed += onPushed;
stack.ScreenExited += onExited;
onPushed(null, stack.CurrentScreen);
if (stack.CurrentScreen != null)
onPushed(null, stack.CurrentScreen);
Current.ValueChanged += current => current.NewValue.MakeCurrent();
}

View File

@ -40,13 +40,14 @@ namespace osu.Game.Graphics.UserInterface
public ShowMoreButton()
{
AutoSizeAxes = Axes.Both;
Height = 30;
Width = 140;
}
protected override Drawable CreateContent() => new CircularContainer
{
Masking = true,
Size = new Vector2(140, 30),
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
background = new Box

View File

@ -130,91 +130,91 @@ namespace osu.Game.IO.Legacy
}
else
{
switch (obj.GetType().Name)
switch (obj)
{
case "Boolean":
case bool boolObj:
Write((byte)ObjType.boolType);
Write((bool)obj);
Write(boolObj);
break;
case "Byte":
case byte byteObj:
Write((byte)ObjType.byteType);
Write((byte)obj);
Write(byteObj);
break;
case "UInt16":
case ushort ushortObj:
Write((byte)ObjType.uint16Type);
Write((ushort)obj);
Write(ushortObj);
break;
case "UInt32":
case uint uintObj:
Write((byte)ObjType.uint32Type);
Write((uint)obj);
Write(uintObj);
break;
case "UInt64":
case ulong ulongObj:
Write((byte)ObjType.uint64Type);
Write((ulong)obj);
Write(ulongObj);
break;
case "SByte":
case sbyte sbyteObj:
Write((byte)ObjType.sbyteType);
Write((sbyte)obj);
Write(sbyteObj);
break;
case "Int16":
case short shortObj:
Write((byte)ObjType.int16Type);
Write((short)obj);
Write(shortObj);
break;
case "Int32":
case int intObj:
Write((byte)ObjType.int32Type);
Write((int)obj);
Write(intObj);
break;
case "Int64":
case long longObj:
Write((byte)ObjType.int64Type);
Write((long)obj);
Write(longObj);
break;
case "Char":
case char charObj:
Write((byte)ObjType.charType);
base.Write((char)obj);
base.Write(charObj);
break;
case "String":
case string stringObj:
Write((byte)ObjType.stringType);
base.Write((string)obj);
base.Write(stringObj);
break;
case "Single":
case float floatObj:
Write((byte)ObjType.singleType);
Write((float)obj);
Write(floatObj);
break;
case "Double":
case double doubleObj:
Write((byte)ObjType.doubleType);
Write((double)obj);
Write(doubleObj);
break;
case "Decimal":
case decimal decimalObj:
Write((byte)ObjType.decimalType);
Write((decimal)obj);
Write(decimalObj);
break;
case "DateTime":
case DateTime dateTimeObj:
Write((byte)ObjType.dateTimeType);
Write((DateTime)obj);
Write(dateTimeObj);
break;
case "Byte[]":
case byte[] byteArray:
Write((byte)ObjType.byteArrayType);
base.Write((byte[])obj);
base.Write(byteArray);
break;
case "Char[]":
case char[] charArray:
Write((byte)ObjType.charArrayType);
base.Write((char[])obj);
base.Write(charArray);
break;
default:

View File

@ -50,7 +50,7 @@ namespace osu.Game.Input
public bool OnPressed(PlatformAction action) => updateLastInteractionTime();
public bool OnReleased(PlatformAction action) => updateLastInteractionTime();
public void OnReleased(PlatformAction action) => updateLastInteractionTime();
protected override bool Handle(UIEvent e)
{

View File

@ -0,0 +1,57 @@
// 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 Humanizer;
using Newtonsoft.Json;
using osu.Framework.Bindables;
using osu.Game.Configuration;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Online.API
{
public class APIMod : IMod
{
[JsonProperty("acronym")]
public string Acronym { get; set; }
[JsonProperty("settings")]
public Dictionary<string, object> Settings { get; set; } = new Dictionary<string, object>();
[JsonConstructor]
private APIMod()
{
}
public APIMod(Mod mod)
{
Acronym = mod.Acronym;
foreach (var (_, property) in mod.GetSettingsSourceProperties())
Settings.Add(property.Name.Underscore(), property.GetValue(mod));
}
public Mod ToMod(Ruleset ruleset)
{
Mod resultMod = ruleset.GetAllMods().FirstOrDefault(m => m.Acronym == Acronym);
if (resultMod == null)
throw new InvalidOperationException($"There is no mod in the ruleset ({ruleset.ShortName}) matching the acronym {Acronym}.");
foreach (var (_, property) in resultMod.GetSettingsSourceProperties())
{
if (!Settings.TryGetValue(property.Name.Underscore(), out object settingValue))
continue;
((IBindable)property.GetValue(resultMod)).Parse(settingValue);
}
return resultMod;
}
public bool Equals(IMod other) => Acronym == other?.Acronym;
}
}

View File

@ -16,7 +16,7 @@ namespace osu.Game.Online.API
{
protected override WebRequest CreateWebRequest() => new OsuJsonWebRequest<T>(Uri);
public T Result => ((JsonWebRequest<T>)WebRequest).ResponseObject;
public T Result => ((OsuJsonWebRequest<T>)WebRequest).ResponseObject;
protected APIRequest()
{
@ -30,16 +30,6 @@ namespace osu.Game.Online.API
/// This will be scheduled to the API's internal scheduler (run on update thread automatically).
/// </summary>
public new event APISuccessHandler<T> Success;
private class OsuJsonWebRequest<U> : JsonWebRequest<U>
{
public OsuJsonWebRequest(string uri)
: base(uri)
{
}
protected override string UserAgent => "osu!";
}
}
/// <summary>
@ -162,16 +152,6 @@ namespace osu.Game.Online.API
[JsonProperty("error")]
public string ErrorMessage { get; set; }
}
private class OsuWebRequest : WebRequest
{
public OsuWebRequest(string uri)
: base(uri)
{
}
protected override string UserAgent => "osu!";
}
}
public class APIException : InvalidOperationException

View File

@ -4,7 +4,6 @@
using System.Diagnostics;
using System.Net.Http;
using osu.Framework.Bindables;
using osu.Framework.IO.Network;
namespace osu.Game.Online.API
{
@ -166,7 +165,7 @@ namespace osu.Game.Online.API
}
}
private class AccessTokenRequest : JsonWebRequest<OAuthToken>
private class AccessTokenRequest : OsuJsonWebRequest<OAuthToken>
{
protected string GrantType;

View File

@ -0,0 +1,21 @@
// 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.IO.Network;
namespace osu.Game.Online.API
{
public class OsuJsonWebRequest<T> : JsonWebRequest<T>
{
public OsuJsonWebRequest(string uri)
: base(uri)
{
}
public OsuJsonWebRequest()
{
}
protected override string UserAgent => "osu!";
}
}

View File

@ -0,0 +1,21 @@
// 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.IO.Network;
namespace osu.Game.Online.API
{
public class OsuWebRequest : WebRequest
{
public OsuWebRequest(string uri)
: base(uri)
{
}
public OsuWebRequest()
{
}
protected override string UserAgent => "osu!";
}
}

View File

@ -2,11 +2,10 @@
// See the LICENCE file in the repository root for full licence text.
using Newtonsoft.Json;
using osu.Framework.IO.Network;
namespace osu.Game.Online.API
{
public class RegistrationRequest : WebRequest
public class RegistrationRequest : OsuWebRequest
{
internal string Username;
internal string Email;

View File

@ -1,14 +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.Game.Rulesets.Mods;
namespace osu.Game.Online.API.Requests.Responses
{
public class APIMod : IMod
{
public string Acronym { get; set; }
public bool Equals(IMod other) => Acronym == other?.Acronym;
}
}

View File

@ -6,8 +6,6 @@ using osu.Game.Users;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net;
using System.Text.RegularExpressions;
namespace osu.Game.Online.API.Requests.Responses
{
@ -70,12 +68,10 @@ namespace osu.Game.Online.API.Requests.Responses
public bool IsDeleted => DeletedAt.HasValue;
public bool HasMessage => !string.IsNullOrEmpty(MessageHtml);
public bool HasMessage => !string.IsNullOrEmpty(Message);
public bool IsVoted { get; set; }
public string GetMessage => HasMessage ? WebUtility.HtmlDecode(Regex.Replace(MessageHtml, @"<(.|\n)*?>", string.Empty)) : string.Empty;
public int DeletedChildrenCount => ChildComments.Count(c => c.IsDeleted);
}
}

View File

@ -47,17 +47,18 @@ namespace osu.Game.Online.API.Requests.Responses
[JsonProperty(@"included_comments")]
public List<Comment> IncludedComments { get; set; }
private List<long> userVotes;
[JsonProperty(@"user_votes")]
private List<long> userVotes
public List<long> UserVotes
{
set => value.ForEach(v =>
get => userVotes;
set
{
Comments.ForEach(c =>
{
if (v == c.Id)
c.IsVoted = true;
});
});
userVotes = value;
Comments.ForEach(c => c.IsVoted = value.Contains(c.Id));
}
}
private List<User> users;

View File

@ -277,7 +277,7 @@ namespace osu.Game.Online.Leaderboards
protected virtual IEnumerable<LeaderboardScoreStatistic> GetStatistics(ScoreInfo model) => new[]
{
new LeaderboardScoreStatistic(FontAwesome.Solid.Link, "Max Combo", model.MaxCombo.ToString()),
new LeaderboardScoreStatistic(FontAwesome.Solid.Crosshairs, "Accuracy", string.Format(model.Accuracy % 1 == 0 ? @"{0:P0}" : @"{0:P2}", model.Accuracy))
new LeaderboardScoreStatistic(FontAwesome.Solid.Crosshairs, "Accuracy", string.Format(model.Accuracy % 1 == 0 ? @"{0:0%}" : @"{0:0.00%}", model.Accuracy))
};
protected override bool OnHover(HoverEvent e)

View File

@ -55,10 +55,10 @@ namespace osu.Game.Online.Leaderboards
return base.OnMouseDown(e);
}
protected override bool OnMouseUp(MouseUpEvent e)
protected override void OnMouseUp(MouseUpEvent e)
{
icon.ScaleTo(1, 1000, Easing.OutElastic);
return base.OnMouseUp(e);
base.OnMouseUp(e);
}
}
}

View File

@ -5,6 +5,7 @@ using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
@ -50,7 +51,7 @@ namespace osu.Game.Online.Multiplayer
[JsonProperty("allowed_mods")]
private APIMod[] allowedMods
{
get => AllowedMods.Select(m => new APIMod { Acronym = m.Acronym }).ToArray();
get => AllowedMods.Select(m => new APIMod(m)).ToArray();
set => allowedModsBacking = value;
}
@ -59,7 +60,7 @@ namespace osu.Game.Online.Multiplayer
[JsonProperty("required_mods")]
private APIMod[] requiredMods
{
get => RequiredMods.Select(m => new APIMod { Acronym = m.Acronym }).ToArray();
get => RequiredMods.Select(m => new APIMod(m)).ToArray();
set => requiredModsBacking = value;
}
@ -72,10 +73,12 @@ namespace osu.Game.Online.Multiplayer
Beatmap = apiBeatmap == null ? beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == BeatmapID) : apiBeatmap.ToBeatmap(rulesets);
Ruleset = rulesets.GetRuleset(RulesetID);
Ruleset rulesetInstance = Ruleset.CreateInstance();
if (allowedModsBacking != null)
{
AllowedMods.Clear();
AllowedMods.AddRange(Ruleset.CreateInstance().GetAllMods().Where(mod => allowedModsBacking.Any(m => m.Acronym == mod.Acronym)));
AllowedMods.AddRange(allowedModsBacking.Select(m => m.ToMod(rulesetInstance)));
allowedModsBacking = null;
}
@ -83,7 +86,7 @@ namespace osu.Game.Online.Multiplayer
if (requiredModsBacking != null)
{
RequiredMods.Clear();
RequiredMods.AddRange(Ruleset.CreateInstance().GetAllMods().Where(mod => requiredModsBacking.Any(m => m.Acronym == mod.Acronym)));
RequiredMods.AddRange(requiredModsBacking.Select(m => m.ToMod(rulesetInstance)));
requiredModsBacking = null;
}

View File

@ -31,10 +31,10 @@ namespace osu.Game.Online.Placeholders
return base.OnMouseDown(e);
}
protected override bool OnMouseUp(MouseUpEvent e)
protected override void OnMouseUp(MouseUpEvent e)
{
this.ScaleTo(1, 1000, Easing.OutElastic);
return base.OnMouseUp(e);
base.OnMouseUp(e);
}
protected override bool OnClick(ClickEvent e)

View File

@ -327,10 +327,10 @@ namespace osu.Game
return;
}
performFromMainMenu(() =>
PerformFromScreen(screen =>
{
// we might already be at song select, so a check is required before performing the load to solo.
if (menuScreen.IsCurrentScreen())
if (screen is MainMenu)
menuScreen.LoadToSolo();
// we might even already be at the song
@ -344,7 +344,7 @@ namespace osu.Game
Ruleset.Value = first.Ruleset;
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(first);
}, $"load {beatmap}", bypassScreenAllowChecks: true, targetScreen: typeof(PlaySongSelect));
}, validScreens: new[] { typeof(PlaySongSelect) });
}
/// <summary>
@ -381,12 +381,12 @@ namespace osu.Game
return;
}
performFromMainMenu(() =>
PerformFromScreen(screen =>
{
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap);
menuScreen.Push(new ReplayPlayerLoader(databasedScore));
}, $"watch {databasedScoreInfo}", bypassScreenAllowChecks: true);
screen.Push(new ReplayPlayerLoader(databasedScore));
}, validScreens: new[] { typeof(PlaySongSelect) });
}
protected virtual Loader CreateLoader() => new Loader();
@ -441,53 +441,42 @@ namespace osu.Game
private ScheduledDelegate performFromMainMenuTask;
/// <summary>
/// Perform an action only after returning to the main menu.
/// Perform an action only after returning to a specific screen as indicated by <paramref name="validScreens"/>.
/// Eagerly tries to exit the current screen until it succeeds.
/// </summary>
/// <param name="action">The action to perform once we are in the correct state.</param>
/// <param name="taskName">The task name to display in a notification (if we can't immediately reach the main menu state).</param>
/// <param name="targetScreen">An optional target screen type. If this screen is already current we can immediately perform the action without returning to the menu.</param>
/// <param name="bypassScreenAllowChecks">Whether checking <see cref="IOsuScreen.AllowExternalScreenChange"/> should be bypassed.</param>
private void performFromMainMenu(Action action, string taskName, Type targetScreen = null, bool bypassScreenAllowChecks = false)
/// <param name="validScreens">An optional collection of valid screen types. If any of these screens are already current we can perform the action immediately, else the first valid parent will be made current before performing the action. <see cref="MainMenu"/> is used if not specified.</param>
protected void PerformFromScreen(Action<IScreen> action, IEnumerable<Type> validScreens = null)
{
performFromMainMenuTask?.Cancel();
// if the current screen does not allow screen changing, give the user an option to try again later.
if (!bypassScreenAllowChecks && (ScreenStack.CurrentScreen as IOsuScreen)?.AllowExternalScreenChange == false)
{
notifications.Post(new SimpleNotification
{
Text = $"Click here to {taskName}",
Activated = () =>
{
performFromMainMenu(action, taskName, targetScreen, true);
return true;
}
});
return;
}
validScreens ??= Enumerable.Empty<Type>();
validScreens = validScreens.Append(typeof(MainMenu));
CloseAllOverlays(false);
// we may already be at the target screen type.
if (targetScreen != null && ScreenStack.CurrentScreen?.GetType() == targetScreen)
if (validScreens.Contains(ScreenStack.CurrentScreen?.GetType()) && !Beatmap.Disabled)
{
action();
action(ScreenStack.CurrentScreen);
return;
}
// all conditions have been met to continue with the action.
if (menuScreen?.IsCurrentScreen() == true && !Beatmap.Disabled)
// find closest valid target
IScreen screen = ScreenStack.CurrentScreen;
while (screen != null)
{
action();
return;
if (validScreens.Contains(screen.GetType()))
{
screen.MakeCurrent();
break;
}
screen = screen.GetParentScreen();
}
// menuScreen may not be initialised yet (null check required).
menuScreen?.MakeCurrent();
performFromMainMenuTask = Schedule(() => performFromMainMenu(action, taskName));
performFromMainMenuTask = Schedule(() => PerformFromScreen(action, validScreens));
}
/// <summary>
@ -881,7 +870,9 @@ namespace osu.Game
#endregion
public bool OnReleased(GlobalAction action) => false;
public void OnReleased(GlobalAction action)
{
}
private Container overlayContent;

View File

@ -330,7 +330,7 @@ namespace osu.Game
private class OsuUserInputManager : UserInputManager
{
protected override MouseButtonEventManager CreateButtonManagerFor(MouseButton button)
protected override MouseButtonEventManager CreateButtonEventManagerFor(MouseButton button)
{
switch (button)
{
@ -338,7 +338,7 @@ namespace osu.Game
return new RightMouseManager(button);
}
return base.CreateButtonManagerFor(button);
return base.CreateButtonEventManagerFor(button);
}
private class RightMouseManager : MouseButtonEventManager

View File

@ -24,7 +24,7 @@ namespace osu.Game.Overlays.BeatmapSet.Buttons
{
private readonly bool noVideo;
public string TooltipText => button.Enabled.Value ? "Download this beatmap" : "Login to download";
public string TooltipText => button.Enabled.Value ? "download this beatmap" : "login to download";
private readonly IBindable<User> localUser = new Bindable<User>();

View File

@ -42,7 +42,7 @@ namespace osu.Game.Overlays.BeatmapSet
int playCount = beatmap?.OnlineInfo?.PlayCount ?? 0;
var rate = playCount != 0 ? (float)passCount / playCount : 0;
successPercent.Text = rate.ToString("P0");
successPercent.Text = rate.ToString("0%");
successRate.Length = rate;
percentContainer.ResizeWidthTo(successRate.Length, 250, Easing.InOutCubic);

View File

@ -34,6 +34,7 @@ namespace osu.Game.Overlays
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
public BeatmapSetOverlay()
: base(OverlayColourScheme.Blue)
{
OsuScrollContainer scroll;
Info info;

View File

@ -7,11 +7,9 @@ using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays
{
public abstract class BreadcrumbControlOverlayHeader : OverlayHeader
public abstract class BreadcrumbControlOverlayHeader : TabControlOverlayHeader<string>
{
protected OverlayHeaderBreadcrumbControl BreadcrumbControl;
protected override TabControl<string> CreateTabControl() => BreadcrumbControl = new OverlayHeaderBreadcrumbControl();
protected override OsuTabControl<string> CreateTabControl() => new OverlayHeaderBreadcrumbControl();
public class OverlayHeaderBreadcrumbControl : BreadcrumbControl<string>
{

View File

@ -2,14 +2,11 @@
// 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.Bindables;
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.UserInterface;
using osu.Game.Online.API.Requests.Responses;
@ -27,8 +24,8 @@ namespace osu.Game.Overlays.Changelog
public ChangelogHeader()
{
BreadcrumbControl.AddItem(listing_string);
BreadcrumbControl.Current.ValueChanged += e =>
TabControl.AddItem(listing_string);
TabControl.Current.ValueChanged += e =>
{
if (e.NewValue == listing_string)
ListingSelected?.Invoke();
@ -38,44 +35,36 @@ namespace osu.Game.Overlays.Changelog
Streams.Current.ValueChanged += e =>
{
if (e.NewValue?.LatestBuild != null && e.NewValue != Current.Value?.UpdateStream)
if (e.NewValue?.LatestBuild != null && !e.NewValue.Equals(Current.Value?.UpdateStream))
Current.Value = e.NewValue.LatestBuild;
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
BreadcrumbControl.AccentColour = colours.Violet;
TitleBackgroundColour = colours.GreyVioletDarker;
ControlBackgroundColour = colours.GreyVioletDark;
}
private ChangelogHeaderTitle title;
private void showBuild(ValueChangedEvent<APIChangelogBuild> e)
{
if (e.OldValue != null)
BreadcrumbControl.RemoveItem(e.OldValue.ToString());
TabControl.RemoveItem(e.OldValue.ToString());
if (e.NewValue != null)
{
BreadcrumbControl.AddItem(e.NewValue.ToString());
BreadcrumbControl.Current.Value = e.NewValue.ToString();
TabControl.AddItem(e.NewValue.ToString());
TabControl.Current.Value = e.NewValue.ToString();
Streams.Current.Value = Streams.Items.FirstOrDefault(s => s.Name == e.NewValue.UpdateStream.Name);
updateCurrentStream();
title.Version = e.NewValue.UpdateStream.DisplayName;
}
else
{
BreadcrumbControl.Current.Value = listing_string;
TabControl.Current.Value = listing_string;
Streams.Current.Value = null;
title.Version = null;
}
}
protected override Drawable CreateBackground() => new HeaderBackground();
protected override Drawable CreateBackground() => new OverlayHeaderBackground(@"Headers/changelog");
protected override Drawable CreateContent() => new Container
{
@ -89,19 +78,18 @@ namespace osu.Game.Overlays.Changelog
protected override ScreenTitle CreateTitle() => title = new ChangelogHeaderTitle();
public class HeaderBackground : Sprite
public void Populate(List<APIUpdateStream> streams)
{
public HeaderBackground()
{
RelativeSizeAxes = Axes.Both;
FillMode = FillMode.Fill;
}
Streams.Populate(streams);
updateCurrentStream();
}
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
Texture = textures.Get(@"Headers/changelog");
}
private void updateCurrentStream()
{
if (Current.Value == null)
return;
Streams.Current.Value = Streams.Items.FirstOrDefault(s => s.Name == Current.Value.UpdateStream.Name);
}
private class ChangelogHeaderTitle : ScreenTitle
@ -117,12 +105,6 @@ namespace osu.Game.Overlays.Changelog
Version = null;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
AccentColour = colours.Violet;
}
protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/changelog");
}
}

View File

@ -29,8 +29,6 @@ namespace osu.Game.Overlays.Changelog
public void Populate(List<APIUpdateStream> streams)
{
Current.Value = null;
foreach (APIUpdateStream updateStream in streams)
AddItem(updateStream);
}

View File

@ -26,7 +26,7 @@ namespace osu.Game.Overlays
{
public readonly Bindable<APIChangelogBuild> Current = new Bindable<APIChangelogBuild>();
private ChangelogHeader header;
protected ChangelogHeader Header;
private Container<ChangelogContent> content;
@ -34,16 +34,16 @@ namespace osu.Game.Overlays
private List<APIChangelogBuild> builds;
private List<APIUpdateStream> streams;
protected List<APIUpdateStream> Streams;
public ChangelogOverlay()
: base(OverlayColourScheme.Purple)
{
}
[BackgroundDependencyLoader]
private void load(AudioManager audio, OsuColour colour)
{
Waves.FirstWaveColour = colour.GreyVioletLight;
Waves.SecondWaveColour = colour.GreyViolet;
Waves.ThirdWaveColour = colour.GreyVioletDark;
Waves.FourthWaveColour = colour.GreyVioletDarker;
Children = new Drawable[]
{
new Box
@ -62,7 +62,7 @@ namespace osu.Game.Overlays
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
header = new ChangelogHeader
Header = new ChangelogHeader
{
ListingSelected = ShowListing,
},
@ -78,7 +78,7 @@ namespace osu.Game.Overlays
sampleBack = audio.Samples.Get(@"UI/generic-select-soft");
header.Current.BindTo(Current);
Header.Current.BindTo(Current);
Current.BindValueChanged(e =>
{
@ -117,7 +117,7 @@ namespace osu.Game.Overlays
performAfterFetch(() =>
{
var build = builds.Find(b => b.Version == version && b.UpdateStream.Name == updateStream)
?? streams.Find(s => s.Name == updateStream)?.LatestBuild;
?? Streams.Find(s => s.Name == updateStream)?.LatestBuild;
if (build != null)
ShowBuild(build);
@ -179,9 +179,9 @@ namespace osu.Game.Overlays
res.Streams.ForEach(s => s.LatestBuild.UpdateStream = res.Streams.Find(s2 => s2.Id == s.LatestBuild.UpdateStream.Id));
builds = res.Builds;
streams = res.Streams;
Streams = res.Streams;
header.Streams.Populate(res.Streams);
Header.Populate(res.Streams);
tcs.SetResult(true);
});

View File

@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Chat.Selection
private const float text_size = 15;
private const float transition_duration = 100;
private readonly Channel channel;
public readonly Channel Channel;
private readonly Bindable<bool> joinedBind = new Bindable<bool>();
private readonly OsuSpriteText name;
@ -36,7 +36,7 @@ namespace osu.Game.Overlays.Chat.Selection
private Color4 topicColour;
private Color4 hoverColour;
public IEnumerable<string> FilterTerms => new[] { channel.Name, channel.Topic };
public IEnumerable<string> FilterTerms => new[] { Channel.Name, Channel.Topic ?? string.Empty };
public bool MatchingFilter
{
@ -50,7 +50,7 @@ namespace osu.Game.Overlays.Chat.Selection
public ChannelListItem(Channel channel)
{
this.channel = channel;
Channel = channel;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
@ -148,7 +148,7 @@ namespace osu.Game.Overlays.Chat.Selection
hoverColour = colours.Yellow;
joinedBind.ValueChanged += joined => updateColour(joined.NewValue);
joinedBind.BindTo(channel.Joined);
joinedBind.BindTo(Channel.Joined);
joinedBind.TriggerChange();
FinishTransforms(true);
@ -156,7 +156,7 @@ namespace osu.Game.Overlays.Chat.Selection
protected override bool OnHover(HoverEvent e)
{
if (!channel.Joined.Value)
if (!Channel.Joined.Value)
name.FadeColour(hoverColour, 50, Easing.OutQuint);
return base.OnHover(e);
@ -164,7 +164,7 @@ namespace osu.Game.Overlays.Chat.Selection
protected override void OnHoverLost(HoverLostEvent e)
{
if (!channel.Joined.Value)
if (!Channel.Joined.Value)
name.FadeColour(Color4.White, transition_duration);
}

View File

@ -9,6 +9,7 @@ using osuTK;
using System;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
namespace osu.Game.Overlays.Chat.Tabs
{
@ -81,7 +82,10 @@ namespace osu.Game.Overlays.Chat.Tabs
RemoveItem(channel);
if (Current.Value == channel)
Current.Value = Items.FirstOrDefault();
{
// Prefer non-selector channels first
Current.Value = Items.FirstOrDefault(c => !(c is ChannelSelectorTabItem.ChannelSelectorTabChannel)) ?? Items.FirstOrDefault();
}
}
protected override void SelectTab(TabItem<Channel> tab)
@ -110,5 +114,18 @@ namespace osu.Game.Overlays.Chat.Tabs
OnRequestLeave?.Invoke(tab.Value);
}
protected override TabFillFlowContainer CreateTabFlow() => new ChannelTabFillFlowContainer
{
Direction = FillDirection.Full,
RelativeSizeAxes = Axes.Both,
Depth = -1,
Masking = true
};
private class ChannelTabFillFlowContainer : TabFillFlowContainer
{
protected override int Compare(Drawable x, Drawable y) => CompareReverseChildID(x, y);
}
}
}

View File

@ -141,16 +141,13 @@ namespace osu.Game.Overlays.Chat.Tabs
updateState();
}
protected override bool OnMouseUp(MouseUpEvent e)
protected override void OnMouseUp(MouseUpEvent e)
{
switch (e.Button)
{
case MouseButton.Middle:
CloseButton.Click();
return true;
default:
return false;
break;
}
}

View File

@ -34,10 +34,10 @@ namespace osu.Game.Overlays.Chat.Tabs
return base.OnMouseDown(e);
}
protected override bool OnMouseUp(MouseUpEvent e)
protected override void OnMouseUp(MouseUpEvent e)
{
icon.ScaleTo(0.75f, 1000, Easing.OutElastic);
return base.OnMouseUp(e);
base.OnMouseUp(e);
}
protected override bool OnHover(HoverEvent e)

View File

@ -299,7 +299,7 @@ namespace osu.Game.Overlays
return true;
}
protected override bool OnDrag(DragEvent e)
protected override void OnDrag(DragEvent e)
{
if (isDragging)
{
@ -311,20 +311,20 @@ namespace osu.Game.Overlays
ChatHeight.Value = targetChatHeight;
}
return true;
}
protected override bool OnDragEnd(DragEndEvent e)
protected override void OnDragEnd(DragEndEvent e)
{
isDragging = false;
return base.OnDragEnd(e);
base.OnDragEnd(e);
}
private void selectTab(int index)
{
var channel = ChannelTabControl.Items.Skip(index).FirstOrDefault();
if (channel != null && !(channel is ChannelSelectorTabItem.ChannelSelectorTabChannel))
var channel = ChannelTabControl.Items
.Where(tab => !(tab is ChannelSelectorTabItem.ChannelSelectorTabChannel))
.ElementAtOrDefault(index);
if (channel != null)
ChannelTabControl.Current.Value = channel;
}

View File

@ -8,7 +8,6 @@ using osu.Game.Online.API.Requests;
using osu.Framework.Graphics;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Online.API.Requests.Responses;
using System.Threading;
using System.Linq;
@ -18,8 +17,8 @@ namespace osu.Game.Overlays.Comments
{
public class CommentsContainer : CompositeDrawable
{
private readonly CommentableType type;
private readonly long id;
private CommentableType type;
private long? id;
public readonly Bindable<CommentsSortCriteria> Sort = new Bindable<CommentsSortCriteria>();
public readonly BindableBool ShowDeleted = new BindableBool();
@ -27,30 +26,26 @@ namespace osu.Game.Overlays.Comments
[Resolved]
private IAPIProvider api { get; set; }
[Resolved]
private OsuColour colours { get; set; }
private GetCommentsRequest request;
private CancellationTokenSource loadCancellation;
private int currentPage;
private readonly Box background;
private readonly FillFlowContainer content;
private readonly DeletedChildrenPlaceholder deletedChildrenPlaceholder;
private readonly CommentsShowMoreButton moreButton;
private FillFlowContainer content;
private DeletedCommentsCounter deletedCommentsCounter;
private CommentsShowMoreButton moreButton;
private TotalCommentsCounter commentCounter;
public CommentsContainer(CommentableType type, long id)
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
this.type = type;
this.id = id;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
AddRangeInternal(new Drawable[]
{
background = new Box
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background5
},
new FillFlowContainer
{
@ -59,6 +54,7 @@ namespace osu.Game.Overlays.Comments
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
commentCounter = new TotalCommentsCounter(),
new CommentsHeader
{
Sort = { BindTarget = Sort },
@ -72,6 +68,7 @@ namespace osu.Game.Overlays.Comments
},
new Container
{
Name = @"Footer",
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
@ -79,7 +76,7 @@ namespace osu.Game.Overlays.Comments
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(0.2f)
Colour = colourProvider.Background4
},
new FillFlowContainer
{
@ -88,7 +85,7 @@ namespace osu.Game.Overlays.Comments
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
deletedChildrenPlaceholder = new DeletedChildrenPlaceholder
deletedCommentsCounter = new DeletedCommentsCounter
{
ShowDeleted = { BindTarget = ShowDeleted }
},
@ -101,7 +98,8 @@ namespace osu.Game.Overlays.Comments
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Margin = new MarginPadding(5),
Action = getComments
Action = getComments,
IsLoading = true,
}
}
}
@ -113,19 +111,29 @@ namespace osu.Game.Overlays.Comments
});
}
[BackgroundDependencyLoader]
private void load()
{
background.Colour = colours.Gray2;
}
protected override void LoadComplete()
{
Sort.BindValueChanged(onSortChanged, true);
Sort.BindValueChanged(_ => refetchComments(), true);
base.LoadComplete();
}
private void onSortChanged(ValueChangedEvent<CommentsSortCriteria> sort)
/// <param name="type">The type of resource to get comments for.</param>
/// <param name="id">The id of the resource to get comments for.</param>
public void ShowComments(CommentableType type, long id)
{
this.type = type;
this.id = id;
if (!IsLoaded)
return;
// only reset when changing ID/type. other refetch ops are generally just changing sort order.
commentCounter.Current.Value = 0;
refetchComments();
}
private void refetchComments()
{
clearComments();
getComments();
@ -133,9 +141,12 @@ namespace osu.Game.Overlays.Comments
private void getComments()
{
if (!id.HasValue)
return;
request?.Cancel();
loadCancellation?.Cancel();
request = new GetCommentsRequest(type, id, Sort.Value, currentPage++);
request = new GetCommentsRequest(type, id.Value, Sort.Value, currentPage++);
request.Success += onSuccess;
api.Queue(request);
}
@ -143,7 +154,8 @@ namespace osu.Game.Overlays.Comments
private void clearComments()
{
currentPage = 1;
deletedChildrenPlaceholder.DeletedCount.Value = 0;
deletedCommentsCounter.Count.Value = 0;
moreButton.Show();
moreButton.IsLoading = true;
content.Clear();
}
@ -152,29 +164,14 @@ namespace osu.Game.Overlays.Comments
{
loadCancellation = new CancellationTokenSource();
FillFlowContainer page = new FillFlowContainer
LoadComponentAsync(new CommentsPage(response)
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
};
foreach (var c in response.Comments)
{
if (c.IsTopLevel)
{
page.Add(new DrawableComment(c)
{
ShowDeleted = { BindTarget = ShowDeleted }
});
}
}
LoadComponentAsync(page, loaded =>
ShowDeleted = { BindTarget = ShowDeleted }
}, loaded =>
{
content.Add(loaded);
deletedChildrenPlaceholder.DeletedCount.Value += response.Comments.Count(c => c.IsDeleted && c.IsTopLevel);
deletedCommentsCounter.Count.Value += response.Comments.Count(c => c.IsDeleted && c.IsTopLevel);
if (response.HasMore)
{
@ -184,8 +181,12 @@ namespace osu.Game.Overlays.Comments
moreButton.Current.Value = response.TopLevelCount - loadedTopLevelComments;
moreButton.IsLoading = false;
}
else
{
moreButton.Hide();
}
moreButton.FadeTo(response.HasMore ? 1 : 0);
commentCounter.Current.Value = response.Total;
}, loadCancellation.Token);
}

View File

@ -76,9 +76,9 @@ namespace osu.Game.Overlays.Comments
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
private void load(OverlayColourProvider colourProvider)
{
background.Colour = colours.Gray3;
background.Colour = colourProvider.Background4;
}
private class ShowDeletedButton : HeaderButton

View File

@ -0,0 +1,92 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics;
using osu.Framework.Bindables;
using osu.Game.Online.API.Requests.Responses;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics.Sprites;
using System.Linq;
namespace osu.Game.Overlays.Comments
{
public class CommentsPage : CompositeDrawable
{
public readonly BindableBool ShowDeleted = new BindableBool();
private readonly CommentBundle commentBundle;
public CommentsPage(CommentBundle commentBundle)
{
this.commentBundle = commentBundle;
}
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
FillFlowContainer flow;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
AddRangeInternal(new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background5
},
flow = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
}
});
if (!commentBundle.Comments.Any())
{
flow.Add(new NoCommentsPlaceholder());
return;
}
foreach (var c in commentBundle.Comments)
{
if (c.IsTopLevel)
{
flow.Add(new DrawableComment(c)
{
ShowDeleted = { BindTarget = ShowDeleted }
});
}
}
}
private class NoCommentsPlaceholder : CompositeDrawable
{
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
Height = 80;
RelativeSizeAxes = Axes.X;
AddRangeInternal(new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background4
},
new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Margin = new MarginPadding { Left = 50 },
Text = @"No comments yet."
}
});
}
}
}
}

View File

@ -1,8 +1,8 @@
// 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.Game.Graphics;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Comments
@ -11,11 +11,14 @@ namespace osu.Game.Overlays.Comments
{
public readonly BindableInt Current = new BindableInt();
public CommentsShowMoreButton()
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
IdleColour = OsuColour.Gray(0.3f);
HoverColour = OsuColour.Gray(0.4f);
ChevronIconColour = OsuColour.Gray(0.5f);
Height = 20;
IdleColour = colourProvider.Background2;
HoverColour = colourProvider.Background1;
ChevronIconColour = colourProvider.Foreground1;
}
protected override void LoadComplete()

View File

@ -12,51 +12,56 @@ using osu.Game.Graphics.Sprites;
namespace osu.Game.Overlays.Comments
{
public class DeletedChildrenPlaceholder : FillFlowContainer
public class DeletedCommentsCounter : CompositeDrawable
{
public readonly BindableBool ShowDeleted = new BindableBool();
public readonly BindableInt DeletedCount = new BindableInt();
public readonly BindableInt Count = new BindableInt();
private readonly SpriteText countText;
public DeletedChildrenPlaceholder()
public DeletedCommentsCounter()
{
AutoSizeAxes = Axes.Both;
Direction = FillDirection.Horizontal;
Spacing = new Vector2(3, 0);
Margin = new MarginPadding { Vertical = 10, Left = 80 };
Children = new Drawable[]
InternalChild = new FillFlowContainer
{
new SpriteIcon
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(3, 0),
Children = new Drawable[]
{
Icon = FontAwesome.Solid.Trash,
Size = new Vector2(14),
},
countText = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true),
new SpriteIcon
{
Icon = FontAwesome.Solid.Trash,
Size = new Vector2(14),
},
countText = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true),
}
}
};
}
protected override void LoadComplete()
{
DeletedCount.BindValueChanged(_ => updateDisplay(), true);
ShowDeleted.BindValueChanged(_ => updateDisplay(), true);
base.LoadComplete();
Count.BindValueChanged(_ => updateDisplay(), true);
ShowDeleted.BindValueChanged(_ => updateDisplay(), true);
}
private void updateDisplay()
{
if (DeletedCount.Value != 0)
if (!ShowDeleted.Value && Count.Value != 0)
{
countText.Text = @"deleted comment".ToQuantity(DeletedCount.Value);
this.FadeTo(ShowDeleted.Value ? 0 : 1);
countText.Text = @"deleted comment".ToQuantity(Count.Value);
Show();
}
else
{
Hide();
}
}
}
}

View File

@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Comments
{
LinkFlowContainer username;
FillFlowContainer childCommentsContainer;
DeletedChildrenPlaceholder deletedChildrenPlaceholder;
DeletedCommentsCounter deletedCommentsCounter;
FillFlowContainer info;
LinkFlowContainer message;
GridContainer content;
@ -184,7 +184,7 @@ namespace osu.Game.Overlays.Comments
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical
},
deletedChildrenPlaceholder = new DeletedChildrenPlaceholder
deletedCommentsCounter = new DeletedCommentsCounter
{
ShowDeleted = { BindTarget = ShowDeleted }
}
@ -193,7 +193,7 @@ namespace osu.Game.Overlays.Comments
}
};
deletedChildrenPlaceholder.DeletedCount.Value = comment.DeletedChildrenCount;
deletedCommentsCounter.Count.Value = comment.DeletedChildrenCount;
if (comment.UserId.HasValue)
username.AddUserLink(comment.User);
@ -213,7 +213,7 @@ namespace osu.Game.Overlays.Comments
if (comment.HasMessage)
{
var formattedSource = MessageFormatter.FormatText(comment.GetMessage);
var formattedSource = MessageFormatter.FormatText(comment.Message);
message.AddLinks(formattedSource.Text, formattedSource.Links);
}
@ -343,7 +343,7 @@ namespace osu.Game.Overlays.Comments
if (parentComment == null)
return string.Empty;
return parentComment.HasMessage ? parentComment.GetMessage : parentComment.IsDeleted ? @"deleted" : string.Empty;
return parentComment.HasMessage ? parentComment.Message : parentComment.IsDeleted ? @"deleted" : string.Empty;
}
}
}

View File

@ -5,7 +5,6 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
@ -45,9 +44,9 @@ namespace osu.Game.Overlays.Comments
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
private void load(OverlayColourProvider colourProvider)
{
background.Colour = colours.Gray4;
background.Colour = colourProvider.Background3;
}
protected override bool OnHover(HoverEvent e)

View File

@ -56,7 +56,7 @@ namespace osu.Game.Overlays.Comments
public readonly BindableBool Active = new BindableBool();
[Resolved]
private OsuColour colours { get; set; }
private OverlayColourProvider colourProvider { get; set; }
private readonly SpriteText text;
@ -78,7 +78,7 @@ namespace osu.Game.Overlays.Comments
updateBackgroundState();
text.Font = text.Font.With(weight: active.NewValue ? FontWeight.Bold : FontWeight.Medium);
text.Colour = active.NewValue ? colours.BlueLighter : Color4.White;
text.Colour = active.NewValue ? colourProvider.Light1 : Color4.White;
}, true);
}

View File

@ -5,7 +5,6 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Framework.Graphics.Sprites;
using osuTK;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
@ -17,9 +16,10 @@ namespace osu.Game.Overlays.Comments
{
public readonly BindableInt Current = new BindableInt();
private readonly SpriteText counter;
private OsuSpriteText counter;
public TotalCommentsCounter()
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
RelativeSizeAxes = Axes.X;
Height = 50;
@ -38,6 +38,7 @@ namespace osu.Game.Overlays.Comments
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Font = OsuFont.GetFont(size: 20, italics: true),
Colour = colourProvider.Light1,
Text = @"Comments"
},
new CircularContainer
@ -51,14 +52,15 @@ namespace osu.Game.Overlays.Comments
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(0.05f)
Colour = colourProvider.Background6
},
counter = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Margin = new MarginPadding { Horizontal = 10, Vertical = 5 },
Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold)
Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold),
Colour = colourProvider.Foreground1
}
},
}
@ -66,12 +68,6 @@ namespace osu.Game.Overlays.Comments
});
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
counter.Colour = colours.BlueLighter;
}
protected override void LoadComplete()
{
Current.BindValueChanged(value => counter.Text = value.NewValue.ToString("N0"), true);

View File

@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Direct
if (BeatmapSet.Value.OnlineInfo.Availability?.DownloadDisabled ?? false)
{
button.Enabled.Value = false;
button.TooltipText = "This beatmap is currently not available for download.";
button.TooltipText = "this beatmap is currently not available for download.";
return;
}

View File

@ -84,14 +84,8 @@ namespace osu.Game.Overlays
}
public DirectOverlay()
: base(OverlayColourScheme.Blue)
{
// osu!direct colours are not part of the standard palette
Waves.FirstWaveColour = OsuColour.FromHex(@"19b0e2");
Waves.SecondWaveColour = OsuColour.FromHex(@"2280a2");
Waves.ThirdWaveColour = OsuColour.FromHex(@"005774");
Waves.FourthWaveColour = OsuColour.FromHex(@"003a4e");
ScrollFlow.Children = new Drawable[]
{
resultCountsContainer = new FillFlowContainer

View File

@ -6,7 +6,6 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Effects;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Online.API;
using osuTK.Graphics;
@ -18,12 +17,12 @@ namespace osu.Game.Overlays
[Resolved]
protected IAPIProvider API { get; private set; }
protected FullscreenOverlay()
[Cached]
protected readonly OverlayColourProvider ColourProvider;
protected FullscreenOverlay(OverlayColourScheme colourScheme)
{
Waves.FirstWaveColour = OsuColour.Gray(0.4f);
Waves.SecondWaveColour = OsuColour.Gray(0.3f);
Waves.ThirdWaveColour = OsuColour.Gray(0.2f);
Waves.FourthWaveColour = OsuColour.Gray(0.1f);
ColourProvider = new OverlayColourProvider(colourScheme);
RelativeSizeAxes = Axes.Both;
RelativePositionAxes = Axes.Both;
@ -41,6 +40,15 @@ namespace osu.Game.Overlays
};
}
[BackgroundDependencyLoader]
private void load()
{
Waves.FirstWaveColour = ColourProvider.Light4;
Waves.SecondWaveColour = ColourProvider.Light3;
Waves.ThirdWaveColour = ColourProvider.Dark4;
Waves.FourthWaveColour = ColourProvider.Dark3;
}
public override void Show()
{
if (State.Value == Visibility.Visible)

View File

@ -177,17 +177,19 @@ namespace osu.Game.Overlays.KeyBinding
return true;
}
protected override bool OnMouseUp(MouseUpEvent e)
protected override void OnMouseUp(MouseUpEvent e)
{
// don't do anything until the last button is released.
if (!HasFocus || e.HasAnyButtonPressed)
return base.OnMouseUp(e);
{
base.OnMouseUp(e);
return;
}
if (bindTarget.IsHovered)
finalise();
else
updateBindTarget();
return true;
}
protected override bool OnScroll(ScrollEvent e)
@ -216,12 +218,15 @@ namespace osu.Game.Overlays.KeyBinding
return true;
}
protected override bool OnKeyUp(KeyUpEvent e)
protected override void OnKeyUp(KeyUpEvent e)
{
if (!HasFocus) return base.OnKeyUp(e);
if (!HasFocus)
{
base.OnKeyUp(e);
return;
}
finalise();
return true;
}
protected override bool OnJoystickPress(JoystickPressEvent e)
@ -235,13 +240,15 @@ namespace osu.Game.Overlays.KeyBinding
return true;
}
protected override bool OnJoystickRelease(JoystickReleaseEvent e)
protected override void OnJoystickRelease(JoystickReleaseEvent e)
{
if (!HasFocus)
return base.OnJoystickRelease(e);
{
base.OnJoystickRelease(e);
return;
}
finalise();
return true;
}
private void clear()
@ -313,14 +320,6 @@ namespace osu.Game.Overlays.KeyBinding
Size = new Vector2(80, 20);
}
protected override bool OnMouseUp(MouseUpEvent e)
{
base.OnMouseUp(e);
// without this, the mouse up triggers a finalise (and deselection) of the current binding target.
return true;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{

View File

@ -80,7 +80,7 @@ namespace osu.Game.Overlays.Mods
foregroundIcon.RotateTo(rotate_angle * direction, mod_switch_duration, mod_switch_easing);
backgroundIcon.RotateTo(-rotate_angle * direction, mod_switch_duration, mod_switch_easing);
backgroundIcon.Icon = modAfter.Icon;
backgroundIcon.Mod = modAfter;
using (BeginDelayedSequence(mod_switch_duration, true))
{
@ -158,7 +158,7 @@ namespace osu.Game.Overlays.Mods
return base.OnMouseDown(e);
}
protected override bool OnMouseUp(MouseUpEvent e)
protected override void OnMouseUp(MouseUpEvent e)
{
scaleContainer.ScaleTo(1, 500, Easing.OutElastic);
@ -172,8 +172,6 @@ namespace osu.Game.Overlays.Mods
break;
}
}
return true;
}
protected override bool OnClick(ClickEvent e)
@ -218,8 +216,8 @@ namespace osu.Game.Overlays.Mods
private void displayMod(Mod mod)
{
if (backgroundIcon != null)
backgroundIcon.Icon = foregroundIcon.Icon;
foregroundIcon.Icon = mod.Icon;
backgroundIcon.Mod = foregroundIcon.Mod;
foregroundIcon.Mod = mod;
text.Text = mod.Name;
Colour = mod.HasImplementation ? Color4.White : Color4.Gray;
}

View File

@ -47,7 +47,7 @@ namespace osu.Game.Overlays.Mods
protected readonly Container ModSettingsContainer;
protected readonly Bindable<IReadOnlyList<Mod>> SelectedMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
public readonly Bindable<IReadOnlyList<Mod>> SelectedMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
private Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> availableMods;
@ -321,14 +321,13 @@ namespace osu.Game.Overlays.Mods
}
[BackgroundDependencyLoader(true)]
private void load(OsuColour colours, AudioManager audio, Bindable<IReadOnlyList<Mod>> selectedMods, OsuGameBase osu)
private void load(OsuColour colours, AudioManager audio, OsuGameBase osu)
{
LowMultiplierColour = colours.Red;
HighMultiplierColour = colours.Green;
UnrankedLabel.Colour = colours.Blue;
availableMods = osu.AvailableMods.GetBoundCopy();
SelectedMods.BindTo(selectedMods);
sampleOn = audio.Samples.Get(@"UI/check-on");
sampleOff = audio.Samples.Get(@"UI/check-off");
@ -473,7 +472,10 @@ namespace osu.Game.Overlays.Mods
if (selectedMod != null)
{
if (State.Value == Visibility.Visible) sampleOn?.Play();
DeselectTypes(selectedMod.IncompatibleMods, true);
if (selectedMod.RequiresConfiguration) ModSettingsContainer.Alpha = 1;
}
else
{

View File

@ -43,10 +43,10 @@ namespace osu.Game.Overlays.Music
return base.OnMouseDown(e);
}
protected override bool OnMouseUp(MouseUpEvent e)
protected override void OnMouseUp(MouseUpEvent e)
{
IsDraggable = false;
return base.OnMouseUp(e);
base.OnMouseUp(e);
}
private bool selected;

View File

@ -136,29 +136,29 @@ namespace osu.Game.Overlays.Music
return draggedItem != null || base.OnDragStart(e);
}
protected override bool OnDrag(DragEvent e)
protected override void OnDrag(DragEvent e)
{
nativeDragPosition = e.ScreenSpaceMousePosition;
if (draggedItem == null)
return base.OnDrag(e);
return true;
if (draggedItem == null)
base.OnDrag(e);
}
protected override bool OnDragEnd(DragEndEvent e)
protected override void OnDragEnd(DragEndEvent e)
{
nativeDragPosition = e.ScreenSpaceMousePosition;
if (draggedItem == null)
return base.OnDragEnd(e);
{
base.OnDragEnd(e);
return;
}
if (dragDestination != null)
musicController.ChangeBeatmapSetPosition(draggedItem.BeatmapSetInfo, dragDestination.Value);
draggedItem = null;
dragDestination = null;
return true;
}
protected override void Update()

View File

@ -196,7 +196,7 @@ namespace osu.Game.Overlays
if (!instant)
queuedDirection = TrackChangeDirection.Next;
var playable = BeatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).Skip(1).FirstOrDefault() ?? BeatmapSets.FirstOrDefault();
var playable = BeatmapSets.SkipWhile(i => i.ID != current.BeatmapSetInfo.ID).ElementAtOrDefault(1) ?? BeatmapSets.FirstOrDefault();
if (playable != null)
{
@ -326,7 +326,9 @@ namespace osu.Game.Overlays
return false;
}
public bool OnReleased(GlobalAction action) => false;
public void OnReleased(GlobalAction action)
{
}
public class MusicControllerToast : Toast
{

View File

@ -1,12 +1,8 @@
// 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.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using System;
@ -24,9 +20,9 @@ namespace osu.Game.Overlays.News
public NewsHeader()
{
BreadcrumbControl.AddItem(front_page_string);
TabControl.AddItem(front_page_string);
BreadcrumbControl.Current.ValueChanged += e =>
TabControl.Current.ValueChanged += e =>
{
if (e.NewValue == front_page_string)
ShowFrontPage?.Invoke();
@ -35,52 +31,29 @@ namespace osu.Game.Overlays.News
Current.ValueChanged += showPost;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
BreadcrumbControl.AccentColour = colours.Violet;
TitleBackgroundColour = colours.GreyVioletDarker;
ControlBackgroundColour = colours.GreyVioletDark;
}
private void showPost(ValueChangedEvent<string> e)
{
if (e.OldValue != null)
BreadcrumbControl.RemoveItem(e.OldValue);
TabControl.RemoveItem(e.OldValue);
if (e.NewValue != null)
{
BreadcrumbControl.AddItem(e.NewValue);
BreadcrumbControl.Current.Value = e.NewValue;
TabControl.AddItem(e.NewValue);
TabControl.Current.Value = e.NewValue;
title.IsReadingPost = true;
}
else
{
BreadcrumbControl.Current.Value = front_page_string;
TabControl.Current.Value = front_page_string;
title.IsReadingPost = false;
}
}
protected override Drawable CreateBackground() => new NewsHeaderBackground();
protected override Drawable CreateBackground() => new OverlayHeaderBackground(@"Headers/news");
protected override ScreenTitle CreateTitle() => title = new NewsHeaderTitle();
private class NewsHeaderBackground : Sprite
{
public NewsHeaderBackground()
{
RelativeSizeAxes = Axes.Both;
FillMode = FillMode.Fill;
}
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
Texture = textures.Get(@"Headers/news");
}
}
private class NewsHeaderTitle : ScreenTitle
{
private const string post_string = "post";
@ -97,12 +70,6 @@ namespace osu.Game.Overlays.News
}
protected override Drawable CreateIcon() => new ScreenTitleTextureIcon(@"Icons/news");
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
AccentColour = colours.Violet;
}
}
}
}

View File

@ -21,6 +21,11 @@ namespace osu.Game.Overlays
public readonly Bindable<string> Current = new Bindable<string>(null);
public NewsOverlay()
: base(OverlayColourScheme.Purple)
{
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{

View File

@ -385,7 +385,7 @@ namespace osu.Game.Overlays
return true;
}
protected override bool OnDrag(DragEvent e)
protected override void OnDrag(DragEvent e)
{
Vector2 change = e.MousePosition - e.MouseDownPosition;
@ -393,13 +393,12 @@ namespace osu.Game.Overlays
change *= change.Length <= 0 ? 0 : MathF.Pow(change.Length, 0.7f) / change.Length;
this.MoveTo(change);
return true;
}
protected override bool OnDragEnd(DragEndEvent e)
protected override void OnDragEnd(DragEndEvent e)
{
this.MoveTo(Vector2.Zero, 800, Easing.OutElastic);
return base.OnDragEnd(e);
base.OnDragEnd(e);
}
}

View 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 System;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays
{
public class OverlayColourProvider
{
private readonly OverlayColourScheme colourScheme;
public OverlayColourProvider(OverlayColourScheme colourScheme)
{
this.colourScheme = colourScheme;
}
public Color4 Highlight1 => getColour(1, 0.7f);
public Color4 Content1 => getColour(0.4f, 1);
public Color4 Content2 => getColour(0.4f, 0.9f);
public Color4 Light1 => getColour(0.4f, 0.8f);
public Color4 Light2 => getColour(0.4f, 0.75f);
public Color4 Light3 => getColour(0.4f, 0.7f);
public Color4 Light4 => getColour(0.4f, 0.5f);
public Color4 Dark1 => getColour(0.2f, 0.35f);
public Color4 Dark2 => getColour(0.2f, 0.3f);
public Color4 Dark3 => getColour(0.2f, 0.25f);
public Color4 Dark4 => getColour(0.2f, 0.2f);
public Color4 Dark5 => getColour(0.2f, 0.15f);
public Color4 Dark6 => getColour(0.2f, 0.1f);
public Color4 Foreground1 => getColour(0.1f, 0.6f);
public Color4 Background1 => getColour(0.1f, 0.4f);
public Color4 Background2 => getColour(0.1f, 0.3f);
public Color4 Background3 => getColour(0.1f, 0.25f);
public Color4 Background4 => getColour(0.1f, 0.2f);
public Color4 Background5 => getColour(0.1f, 0.15f);
public Color4 Background6 => getColour(0.1f, 0.1f);
private Color4 getColour(float saturation, float lightness) => Color4.FromHsl(new Vector4(getBaseHue(colourScheme), saturation, lightness, 1));
// See https://github.com/ppy/osu-web/blob/4218c288292d7c810b619075471eaea8bbb8f9d8/app/helpers.php#L1463
private static float getBaseHue(OverlayColourScheme colourScheme)
{
switch (colourScheme)
{
default:
throw new ArgumentException($@"{colourScheme} colour scheme does not provide a hue value in {nameof(getBaseHue)}.");
case OverlayColourScheme.Red:
return 0;
case OverlayColourScheme.Pink:
return 333 / 360f;
case OverlayColourScheme.Orange:
return 46 / 360f;
case OverlayColourScheme.Green:
return 115 / 360f;
case OverlayColourScheme.Purple:
return 255 / 360f;
case OverlayColourScheme.Blue:
return 200 / 360f;
}
}
}
public enum OverlayColourScheme
{
Red,
Pink,
Orange,
Green,
Purple,
Blue
}
}

View File

@ -2,10 +2,10 @@
// See the LICENCE file in the repository root for full licence text.
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface;
using osuTK.Graphics;
@ -14,23 +14,9 @@ namespace osu.Game.Overlays
public abstract class OverlayHeader : Container
{
private readonly Box titleBackground;
private readonly Box controlBackground;
private readonly Container background;
private readonly ScreenTitle title;
protected Color4 TitleBackgroundColour
{
set => titleBackground.Colour = value;
}
protected Color4 ControlBackgroundColour
{
set => controlBackground.Colour = value;
}
protected float BackgroundHeight
{
set => background.Height = value;
}
protected readonly FillFlowContainer HeaderInfo;
protected OverlayHeader()
{
@ -44,47 +30,51 @@ namespace osu.Game.Overlays
Direction = FillDirection.Vertical,
Children = new[]
{
background = new Container
{
RelativeSizeAxes = Axes.X,
Height = 80,
Masking = true,
Child = CreateBackground()
},
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
titleBackground = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Gray,
},
CreateTitle().With(title =>
{
title.Margin = new MarginPadding
{
Vertical = 10,
Left = UserProfileOverlay.CONTENT_X_MARGIN
};
})
}
},
new Container
HeaderInfo = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Depth = -float.MaxValue,
Children = new Drawable[]
Children = new[]
{
controlBackground = new Box
CreateBackground(),
new Container
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Gray,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
titleBackground = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Gray,
},
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding
{
Horizontal = UserProfileOverlay.CONTENT_X_MARGIN,
Vertical = 10,
},
Children = new[]
{
title = CreateTitle().With(title =>
{
title.Anchor = Anchor.CentreLeft;
title.Origin = Anchor.CentreLeft;
}),
CreateTitleContent().With(content =>
{
content.Anchor = Anchor.CentreRight;
content.Origin = Anchor.CentreRight;
})
}
}
}
},
CreateTabControl().With(control => control.Margin = new MarginPadding { Left = UserProfileOverlay.CONTENT_X_MARGIN })
}
},
CreateContent()
@ -92,13 +82,25 @@ namespace osu.Game.Overlays
});
}
protected abstract Drawable CreateBackground();
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
titleBackground.Colour = colourProvider.Dark5;
title.AccentColour = colourProvider.Highlight1;
}
[NotNull]
protected virtual Drawable CreateContent() => new Container();
protected virtual Drawable CreateContent() => Empty();
[NotNull]
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"/>.
/// </summary>
[NotNull]
protected virtual Drawable CreateTitleContent() => Empty();
protected abstract ScreenTitle CreateTitle();
protected abstract TabControl<string> CreateTabControl();
}
}

View File

@ -0,0 +1,43 @@
// 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;
namespace osu.Game.Overlays
{
public class OverlayHeaderBackground : CompositeDrawable
{
public OverlayHeaderBackground(string textureName)
{
Height = 80;
RelativeSizeAxes = Axes.X;
Masking = true;
InternalChild = new Background(textureName);
}
private class Background : Sprite
{
private readonly string textureName;
public Background(string textureName)
{
this.textureName = textureName;
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
RelativeSizeAxes = Axes.Both;
FillMode = FillMode.Fill;
}
[BackgroundDependencyLoader]
private void load(TextureStore textures)
{
Texture = textures.Get(textureName);
}
}
}
}

View File

@ -0,0 +1,28 @@
// 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.Game.Rulesets;
using osuTK;
namespace osu.Game.Overlays
{
public class OverlayRulesetSelector : RulesetSelector
{
public OverlayRulesetSelector()
{
AutoSizeAxes = Axes.Both;
}
protected override TabItem<RulesetInfo> CreateTabItem(RulesetInfo value) => new OverlayRulesetTabItem(value);
protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(25, 0),
};
}
}

View File

@ -0,0 +1,93 @@
// 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;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets;
using osuTK.Graphics;
using osuTK;
using osu.Framework.Allocation;
namespace osu.Game.Overlays
{
public class OverlayRulesetTabItem : TabItem<RulesetInfo>
{
private Color4 accentColour;
protected virtual Color4 AccentColour
{
get => accentColour;
set
{
accentColour = value;
text.FadeColour(value, 120, Easing.OutQuint);
}
}
protected override Container<Drawable> Content { get; }
[Resolved]
private OverlayColourProvider colourProvider { get; set; }
private readonly OsuSpriteText text;
public OverlayRulesetTabItem(RulesetInfo value)
: base(value)
{
AutoSizeAxes = Axes.Both;
AddRangeInternal(new Drawable[]
{
Content = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(3, 0),
Child = text = new OsuSpriteText
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Text = value.Name,
}
},
new HoverClickSounds()
});
Enabled.Value = true;
}
[BackgroundDependencyLoader]
private void load()
{
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()
{
text.Font = text.Font.With(weight: Active.Value ? FontWeight.Bold : FontWeight.Medium);
AccentColour = IsHovered || Active.Value ? Color4.White : colourProvider.Highlight1;
}
}
}

View File

@ -13,41 +13,25 @@ using osuTK.Graphics;
namespace osu.Game.Overlays
{
public abstract class OverlayTabControl<T> : TabControl<T>
public abstract class OverlayTabControl<T> : OsuTabControl<T>
{
private readonly Box bar;
private Color4 accentColour = Color4.White;
public Color4 AccentColour
{
get => accentColour;
set
{
if (accentColour == value)
return;
accentColour = value;
bar.Colour = value;
foreach (TabItem<T> tabItem in TabContainer)
{
((OverlayTabItem)tabItem).AccentColour = value;
}
}
}
public new MarginPadding Padding
{
get => TabContainer.Padding;
set => TabContainer.Padding = value;
}
protected float BarHeight
{
set => bar.Height = value;
}
public override Color4 AccentColour
{
get => base.AccentColour;
set
{
base.AccentColour = value;
bar.Colour = value;
}
}
protected OverlayTabControl()
{
TabContainer.Masking = false;
@ -66,7 +50,7 @@ namespace osu.Game.Overlays
protected override TabItem<T> CreateTabItem(T value) => new OverlayTabItem(value);
protected class OverlayTabItem : TabItem<T>
protected class OverlayTabItem : TabItem<T>, IHasAccentColour
{
protected readonly ExpandingBar Bar;
protected readonly OsuSpriteText Text;

View File

@ -1,4 +1,4 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// 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;
@ -33,16 +33,16 @@ namespace osu.Game.Overlays.Profile.Header
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
private void load(OverlayColourProvider colourProvider)
{
iconColour = colours.GreySeafoamLighter;
iconColour = colourProvider.Foreground1;
InternalChildren = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colours.GreySeafoamDark,
Colour = colourProvider.Background4
},
new FillFlowContainer
{
@ -82,7 +82,7 @@ namespace osu.Game.Overlays.Profile.Header
else
{
topLinkContainer.AddText("Joined ");
topLinkContainer.AddText(new DrawableDate(user.JoinDate), embolden);
topLinkContainer.AddText(new DrawableDate(user.JoinDate, italic: false), embolden);
}
addSpacer(topLinkContainer);
@ -95,7 +95,7 @@ namespace osu.Game.Overlays.Profile.Header
else if (user.LastVisit.HasValue)
{
topLinkContainer.AddText("Last seen ");
topLinkContainer.AddText(new DrawableDate(user.LastVisit.Value), embolden);
topLinkContainer.AddText(new DrawableDate(user.LastVisit.Value, italic: false), embolden);
addSpacer(topLinkContainer);
}
@ -111,34 +111,42 @@ namespace osu.Game.Overlays.Profile.Header
topLinkContainer.AddText("Contributed ");
topLinkContainer.AddLink($@"{user.PostCount:#,##0} forum posts", $"https://osu.ppy.sh/users/{user.Id}/posts", creationParameters: embolden);
string websiteWithoutProtcol = user.Website;
string websiteWithoutProtocol = user.Website;
if (!string.IsNullOrEmpty(websiteWithoutProtcol))
if (!string.IsNullOrEmpty(websiteWithoutProtocol))
{
if (Uri.TryCreate(websiteWithoutProtcol, UriKind.Absolute, out var uri))
if (Uri.TryCreate(websiteWithoutProtocol, UriKind.Absolute, out var uri))
{
websiteWithoutProtcol = uri.Host + uri.PathAndQuery + uri.Fragment;
websiteWithoutProtcol = websiteWithoutProtcol.TrimEnd('/');
websiteWithoutProtocol = uri.Host + uri.PathAndQuery + uri.Fragment;
websiteWithoutProtocol = websiteWithoutProtocol.TrimEnd('/');
}
}
tryAddInfo(FontAwesome.Solid.MapMarker, user.Location);
tryAddInfo(OsuIcon.Heart, user.Interests);
tryAddInfo(FontAwesome.Solid.Suitcase, user.Occupation);
bottomLinkContainer.NewLine();
bool anyInfoAdded = false;
anyInfoAdded |= tryAddInfo(FontAwesome.Solid.MapMarker, user.Location);
anyInfoAdded |= tryAddInfo(OsuIcon.Heart, user.Interests);
anyInfoAdded |= tryAddInfo(FontAwesome.Solid.Suitcase, user.Occupation);
if (anyInfoAdded)
bottomLinkContainer.NewLine();
if (!string.IsNullOrEmpty(user.Twitter))
tryAddInfo(FontAwesome.Brands.Twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}");
tryAddInfo(FontAwesome.Brands.Discord, user.Discord);
tryAddInfo(FontAwesome.Brands.Skype, user.Skype, @"skype:" + user.Skype + @"?chat");
tryAddInfo(FontAwesome.Brands.Lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}");
tryAddInfo(FontAwesome.Solid.Link, websiteWithoutProtcol, user.Website);
anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Twitter, "@" + user.Twitter, $@"https://twitter.com/{user.Twitter}");
anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Discord, user.Discord);
anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Skype, user.Skype, @"skype:" + user.Skype + @"?chat");
anyInfoAdded |= tryAddInfo(FontAwesome.Brands.Lastfm, user.Lastfm, $@"https://last.fm/users/{user.Lastfm}");
anyInfoAdded |= tryAddInfo(FontAwesome.Solid.Link, websiteWithoutProtocol, user.Website);
// If no information was added to the bottomLinkContainer, hide it to avoid unwanted padding
bottomLinkContainer.Alpha = anyInfoAdded ? 1 : 0;
}
private void addSpacer(OsuTextFlowContainer textFlow) => textFlow.AddArbitraryDrawable(new Container { Width = 15 });
private void tryAddInfo(IconUsage icon, string content, string link = null)
private bool tryAddInfo(IconUsage icon, string content, string link = null)
{
if (string.IsNullOrEmpty(content)) return;
if (string.IsNullOrEmpty(content)) return false;
// newlines could be contained in API returned user content.
content = content.Replace("\n", " ");
@ -155,6 +163,7 @@ namespace osu.Game.Overlays.Profile.Header
bottomLinkContainer.AddText(" " + content, embolden);
addSpacer(bottomLinkContainer);
return true;
}
private void embolden(SpriteText text) => text.Font = text.Font.With(weight: FontWeight.Bold);

View File

@ -7,7 +7,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Textures;
using osu.Game.Graphics;
using osu.Game.Overlays.Profile.Header.Components;
using osu.Game.Users;
using osuTK;
@ -28,7 +27,7 @@ namespace osu.Game.Overlays.Profile.Header
}
[BackgroundDependencyLoader]
private void load(OsuColour colours, TextureStore textures)
private void load(OverlayColourProvider colourProvider, TextureStore textures)
{
Container<Drawable> hiddenDetailContainer;
Container<Drawable> expandedDetailContainer;
@ -38,7 +37,7 @@ namespace osu.Game.Overlays.Profile.Header
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colours.GreySeafoam
Colour = colourProvider.Background4
},
new FillFlowContainer
{
@ -119,12 +118,12 @@ namespace osu.Game.Overlays.Profile.Header
hiddenDetailGlobal = new OverlinedInfoContainer
{
Title = "Global Ranking",
LineColour = colours.Yellow
LineColour = colourProvider.Highlight1
},
hiddenDetailCountry = new OverlinedInfoContainer
{
Title = "Country Ranking",
LineColour = colours.Yellow
LineColour = colourProvider.Highlight1
},
}
}

View File

@ -6,7 +6,6 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osuTK;
namespace osu.Game.Overlays.Profile.Header.Components
@ -25,10 +24,10 @@ namespace osu.Game.Overlays.Profile.Header.Components
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
private void load(OverlayColourProvider colourProvider)
{
IdleColour = colours.GreySeafoamLight;
HoverColour = colours.GreySeafoamLight.Darken(0.2f);
IdleColour = colourProvider.Background2;
HoverColour = colourProvider.Background2.Lighten(0.2f);
Child = icon = new SpriteIcon
{

View File

@ -24,7 +24,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
public LevelBadge()
{
TooltipText = "Level";
TooltipText = "level";
}
[BackgroundDependencyLoader]

View File

@ -25,11 +25,11 @@ namespace osu.Game.Overlays.Profile.Header.Components
public LevelProgressBar()
{
TooltipText = "Progress to next level";
TooltipText = "progress to next level";
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
private void load(OverlayColourProvider colourProvider)
{
InternalChildren = new Drawable[]
{
@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Profile.Header.Components
RelativeSizeAxes = Axes.Both,
BackgroundColour = Color4.Black,
Direction = BarDirection.LeftToRight,
AccentColour = colours.Yellow
AccentColour = colourProvider.Highlight1
}
},
levelProgressText = new OsuSpriteText

View File

@ -43,7 +43,8 @@ namespace osu.Game.Overlays.Profile.Header.Components
line = new Circle
{
RelativeSizeAxes = Axes.X,
Height = 4,
Height = 2,
Margin = new MarginPadding { Bottom = 2 }
},
title = new OsuSpriteText
{

View File

@ -6,7 +6,6 @@ using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Game.Graphics;
using osu.Game.Users;
namespace osu.Game.Overlays.Profile.Header.Components
@ -27,12 +26,12 @@ namespace osu.Game.Overlays.Profile.Header.Components
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
private void load(OverlayColourProvider colourProvider)
{
InternalChild = info = new OverlinedInfoContainer
{
Title = "Total Play Time",
LineColour = colours.Yellow,
LineColour = colourProvider.Highlight1,
};
User.BindValueChanged(updateTime, true);

View File

@ -2,12 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osuTK.Graphics;
namespace osu.Game.Overlays.Profile.Header.Components
{
@ -24,9 +23,6 @@ namespace osu.Game.Overlays.Profile.Header.Components
{
AutoSizeAxes = Axes.X;
IdleColour = Color4.Black;
HoverColour = OsuColour.Gray(0.1f);
base.Content.Add(new CircularContainer
{
Masking = true,
@ -47,5 +43,12 @@ namespace osu.Game.Overlays.Profile.Header.Components
}
});
}
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
IdleColour = colourProvider.Background6;
HoverColour = colourProvider.Background5;
}
}
}

View File

@ -1,63 +1,29 @@
// 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.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics;
using osu.Game.Rulesets;
using osu.Game.Users;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.Profile.Header.Components
{
public class ProfileRulesetSelector : RulesetSelector
public class ProfileRulesetSelector : OverlayRulesetSelector
{
private Color4 accentColour = Color4.White;
public readonly Bindable<User> User = new Bindable<User>();
public ProfileRulesetSelector()
{
TabContainer.Masking = false;
TabContainer.Spacing = new Vector2(10, 0);
AutoSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
accentColour = colours.Seafoam;
foreach (TabItem<RulesetInfo> tabItem in TabContainer)
((ProfileRulesetTabItem)tabItem).AccentColour = accentColour;
}
protected override void LoadComplete()
{
base.LoadComplete();
User.BindValueChanged(u => SetDefaultRuleset(Rulesets.GetRuleset(u.NewValue?.PlayMode ?? "osu")), true);
}
public void SetDefaultRuleset(RulesetInfo ruleset)
{
foreach (TabItem<RulesetInfo> tabItem in TabContainer)
foreach (var tabItem in TabContainer)
((ProfileRulesetTabItem)tabItem).IsDefault = ((ProfileRulesetTabItem)tabItem).Value.ID == ruleset.ID;
}
protected override TabItem<RulesetInfo> CreateTabItem(RulesetInfo value) => new ProfileRulesetTabItem(value)
{
AccentColour = accentColour
};
protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer
{
Direction = FillDirection.Horizontal,
AutoSizeAxes = Axes.Both,
};
protected override TabItem<RulesetInfo> CreateTabItem(RulesetInfo value) => new ProfileRulesetTabItem(value);
}
}

View File

@ -2,40 +2,15 @@
// 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.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Rulesets;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Overlays.Profile.Header.Components
{
public class ProfileRulesetTabItem : TabItem<RulesetInfo>, IHasAccentColour
public class ProfileRulesetTabItem : OverlayRulesetTabItem
{
private readonly OsuSpriteText text;
private readonly SpriteIcon icon;
private Color4 accentColour;
public Color4 AccentColour
{
get => accentColour;
set
{
if (accentColour == value)
return;
accentColour = value;
updateState();
}
}
private bool isDefault;
public bool IsDefault
@ -52,74 +27,30 @@ namespace osu.Game.Overlays.Profile.Header.Components
}
}
protected override Color4 AccentColour
{
get => base.AccentColour;
set
{
base.AccentColour = value;
icon.FadeColour(value, 120, Easing.OutQuint);
}
}
private readonly SpriteIcon icon;
public ProfileRulesetTabItem(RulesetInfo value)
: base(value)
{
AutoSizeAxes = Axes.Both;
Children = new Drawable[]
Add(icon = new SpriteIcon
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Origin = Anchor.BottomLeft,
Anchor = Anchor.BottomLeft,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(3, 0),
Children = new Drawable[]
{
text = new OsuSpriteText
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Text = value.Name,
},
icon = new SpriteIcon
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Alpha = 0,
AlwaysPresent = true,
Icon = FontAwesome.Solid.Star,
Size = new Vector2(12),
},
}
},
new HoverClickSounds()
};
}
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()
{
text.Font = text.Font.With(weight: Active.Value ? FontWeight.Bold : FontWeight.Medium);
if (IsHovered || Active.Value)
{
text.FadeColour(Color4.White, 120, Easing.InQuad);
icon.FadeColour(Color4.White, 120, Easing.InQuad);
}
else
{
text.FadeColour(AccentColour, 120, Easing.InQuad);
icon.FadeColour(AccentColour, 120, Easing.InQuad);
}
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Alpha = 0,
AlwaysPresent = true,
Icon = FontAwesome.Solid.Star,
Size = new Vector2(12),
});
}
}
}

Some files were not shown because too many files have changed in this diff Show More