1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-13 14:12:56 +08:00

Merge branch 'master' into registration-loc

This commit is contained in:
Dean Herbert 2023-01-18 13:41:53 +09:00 committed by GitHub
commit 39ac7615ec
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 630 additions and 218 deletions

View File

@ -31,10 +31,8 @@ namespace osu.Game.Rulesets.Osu.Tests
[Test] [Test]
public void TestMaximumDistanceTrackingWithoutMovement( public void TestMaximumDistanceTrackingWithoutMovement(
[Values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)] [Values(0, 5, 10)] float circleSize,
float circleSize, [Values(0, 5, 10)] double velocity)
[Values(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)]
double velocity)
{ {
const double time_slider_start = 1000; const double time_slider_start = 1000;

View File

@ -1,8 +1,6 @@
// 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
@ -11,7 +9,10 @@ using osu.Game.Overlays;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Testing; using osu.Framework.Testing;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
@ -27,15 +28,20 @@ namespace osu.Game.Tests.Visual.Online
private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; private DummyAPIAccess dummyAPI => (DummyAPIAccess)API;
private CommentsContainer commentsContainer; private CommentsContainer commentsContainer = null!;
private TextBox editorTextBox = null!;
[SetUp] [SetUp]
public void SetUp() => Schedule(() => public void SetUp() => Schedule(() =>
{
Child = new BasicScrollContainer Child = new BasicScrollContainer
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Child = commentsContainer = new CommentsContainer() Child = commentsContainer = new CommentsContainer()
}); };
editorTextBox = commentsContainer.ChildrenOfType<TextBox>().First();
});
[Test] [Test]
public void TestIdleState() public void TestIdleState()
@ -126,6 +132,44 @@ namespace osu.Game.Tests.Visual.Online
commentsContainer.ChildrenOfType<DrawableComment>().Count(d => d.Comment.Pinned == withPinned) == 1); commentsContainer.ChildrenOfType<DrawableComment>().Count(d => d.Comment.Pinned == withPinned) == 1);
} }
[Test]
public void TestPost()
{
setUpCommentsResponse(new CommentBundle { Comments = new List<Comment>() });
AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
AddAssert("no comments placeholder shown", () => commentsContainer.ChildrenOfType<CommentsContainer.NoCommentsPlaceholder>().Any());
setUpPostResponse();
AddStep("enter text", () => editorTextBox.Current.Value = "comm");
AddStep("submit", () => commentsContainer.ChildrenOfType<RoundedButton>().First().TriggerClick());
AddUntilStep("comment sent", () =>
{
string writtenText = editorTextBox.Current.Value;
var comment = commentsContainer.ChildrenOfType<DrawableComment>().LastOrDefault();
return comment != null && comment.ChildrenOfType<SpriteText>().Any(y => y.Text == writtenText);
});
AddAssert("no comments placeholder removed", () => !commentsContainer.ChildrenOfType<CommentsContainer.NoCommentsPlaceholder>().Any());
}
[Test]
public void TestPostWithExistingComments()
{
setUpCommentsResponse(getExampleComments());
AddStep("show comments", () => commentsContainer.ShowComments(CommentableType.Beatmapset, 123));
setUpPostResponse();
AddStep("enter text", () => editorTextBox.Current.Value = "comm");
AddStep("submit", () => commentsContainer.ChildrenOfType<CommentEditor>().Single().ChildrenOfType<RoundedButton>().First().TriggerClick());
AddUntilStep("comment sent", () =>
{
string writtenText = editorTextBox.Current.Value;
var comment = commentsContainer.ChildrenOfType<DrawableComment>().LastOrDefault();
return comment != null && comment.ChildrenOfType<SpriteText>().Any(y => y.Text == writtenText);
});
}
private void setUpCommentsResponse(CommentBundle commentBundle) private void setUpCommentsResponse(CommentBundle commentBundle)
=> AddStep("set up response", () => => AddStep("set up response", () =>
{ {
@ -139,7 +183,33 @@ namespace osu.Game.Tests.Visual.Online
}; };
}); });
private CommentBundle getExampleComments(bool withPinned = false) private void setUpPostResponse()
=> AddStep("set up response", () =>
{
dummyAPI.HandleRequest = request =>
{
if (!(request is CommentPostRequest req))
return false;
req.TriggerSuccess(new CommentBundle
{
Comments = new List<Comment>
{
new Comment
{
Id = 98,
Message = req.Message,
LegacyName = "FirstUser",
CreatedAt = DateTimeOffset.Now,
VotesCount = 98,
}
}
});
return true;
};
});
private static CommentBundle getExampleComments(bool withPinned = false)
{ {
var bundle = new CommentBundle var bundle = new CommentBundle
{ {

View File

@ -113,7 +113,7 @@ namespace osu.Game.Tournament.Screens.Setup
new LabelledDropdown<RulesetInfo> new LabelledDropdown<RulesetInfo>
{ {
Label = "Ruleset", Label = "Ruleset",
Description = "Decides what stats are displayed and which ranks are retrieved for players.", Description = "Decides what stats are displayed and which ranks are retrieved for players. This requires a restart to reload data for an existing bracket.",
Items = rulesets.AvailableRulesets, Items = rulesets.AvailableRulesets,
Current = LadderInfo.Ruleset, Current = LadderInfo.Ruleset,
}, },

View File

@ -16,6 +16,7 @@ using osu.Framework.IO.Stores;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Platform; using osu.Framework.Platform;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Online;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.API.Requests.Responses;
using osu.Game.Tournament.IO; using osu.Game.Tournament.IO;
@ -44,6 +45,14 @@ namespace osu.Game.Tournament
return dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); return dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
} }
public override EndpointConfiguration CreateEndpoints()
{
if (UseDevelopmentServer)
return base.CreateEndpoints();
return new ProductionEndpointConfiguration();
}
private TournamentSpriteText initialisationText; private TournamentSpriteText initialisationText;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -156,9 +165,21 @@ namespace osu.Game.Tournament
addedInfo |= addSeedingBeatmaps(); addedInfo |= addSeedingBeatmaps();
if (addedInfo) if (addedInfo)
SaveChanges(); saveChanges();
ladder.CurrentMatch.Value = ladder.Matches.FirstOrDefault(p => p.Current.Value); ladder.CurrentMatch.Value = ladder.Matches.FirstOrDefault(p => p.Current.Value);
ladder.Ruleset.BindValueChanged(r =>
{
// Refetch player rank data on next startup as the ruleset has changed.
foreach (var team in ladder.Teams)
{
foreach (var player in team.Players)
player.Rank = null;
}
SaveChanges();
});
} }
catch (Exception e) catch (Exception e)
{ {
@ -306,6 +327,11 @@ namespace osu.Game.Tournament
return; return;
} }
saveChanges();
}
private void saveChanges()
{
foreach (var r in ladder.Rounds) foreach (var r in ladder.Rounds)
r.Matches = ladder.Matches.Where(p => p.Round.Value == r).Select(p => p.ID).ToList(); r.Matches = ladder.Matches.Where(p => p.Round.Value == r).Select(p => p.ID).ToList();

View File

@ -19,6 +19,11 @@ namespace osu.Game.Localisation
/// </summary> /// </summary>
public static LocalisableString HeaderDescription => new TranslatableString(getKey(@"header_description"), @"join the real-time discussion"); public static LocalisableString HeaderDescription => new TranslatableString(getKey(@"header_description"), @"join the real-time discussion");
/// <summary>
/// "Mention"
/// </summary>
public static LocalisableString MentionUser => new TranslatableString(getKey(@"mention_user"), @"Mention");
private static string getKey(string key) => $"{prefix}:{key}"; private static string getKey(string key) => $"{prefix}:{key}";
} }
} }

View File

@ -0,0 +1,41 @@
// 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.Net.Http;
using osu.Framework.IO.Network;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
{
public class CommentPostRequest : APIRequest<CommentBundle>
{
public readonly CommentableType Commentable;
public readonly long CommentableId;
public readonly string Message;
public readonly long? ParentCommentId;
public CommentPostRequest(CommentableType commentable, long commentableId, string message, long? parentCommentId = null)
{
Commentable = commentable;
CommentableId = commentableId;
Message = message;
ParentCommentId = parentCommentId;
}
protected override WebRequest CreateWebRequest()
{
var req = base.CreateWebRequest();
req.Method = HttpMethod.Post;
req.AddParameter(@"comment[commentable_type]", Commentable.ToString().ToLowerInvariant());
req.AddParameter(@"comment[commentable_id]", $"{CommentableId}");
req.AddParameter(@"comment[message]", Message);
if (ParentCommentId.HasValue)
req.AddParameter(@"comment[parent_id]", $"{ParentCommentId}");
return req;
}
protected override string Target => "comments";
}
}

View File

@ -25,6 +25,7 @@ namespace osu.Game.Online.Chat
/// </summary> /// </summary>
public partial class StandAloneChatDisplay : CompositeDrawable public partial class StandAloneChatDisplay : CompositeDrawable
{ {
[Cached]
public readonly Bindable<Channel> Channel = new Bindable<Channel>(); public readonly Bindable<Channel> Channel = new Bindable<Channel>();
protected readonly ChatTextBox TextBox; protected readonly ChatTextBox TextBox;

View File

@ -0,0 +1,19 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
namespace osu.Game.Online
{
public class ExperimentalEndpointConfiguration : EndpointConfiguration
{
public ExperimentalEndpointConfiguration()
{
WebsiteRootUrl = @"https://osu.ppy.sh";
APIEndpointUrl = @"https://lazer.ppy.sh";
APIClientSecret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk";
APIClientID = "5";
SpectatorEndpointUrl = "https://spectator.ppy.sh/spectator";
MultiplayerEndpointUrl = "https://spectator.ppy.sh/multiplayer";
MetadataEndpointUrl = "https://spectator.ppy.sh/metadata";
}
}
}

View File

@ -1,16 +1,13 @@
// 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
namespace osu.Game.Online namespace osu.Game.Online
{ {
public class ProductionEndpointConfiguration : EndpointConfiguration public class ProductionEndpointConfiguration : EndpointConfiguration
{ {
public ProductionEndpointConfiguration() public ProductionEndpointConfiguration()
{ {
WebsiteRootUrl = @"https://osu.ppy.sh"; WebsiteRootUrl = APIEndpointUrl = @"https://osu.ppy.sh";
APIEndpointUrl = @"https://lazer.ppy.sh";
APIClientSecret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk"; APIClientSecret = @"FGc9GAtyHzeQDshWP5Ah7dega8hJACAJpQtw6OXk";
APIClientID = "5"; APIClientID = "5";
SpectatorEndpointUrl = "https://spectator.ppy.sh/spectator"; SpectatorEndpointUrl = "https://spectator.ppy.sh/spectator";

View File

@ -98,8 +98,8 @@ namespace osu.Game
public virtual bool UseDevelopmentServer => DebugUtils.IsDebugBuild; public virtual bool UseDevelopmentServer => DebugUtils.IsDebugBuild;
internal EndpointConfiguration CreateEndpoints() => public virtual EndpointConfiguration CreateEndpoints() =>
UseDevelopmentServer ? new DevelopmentEndpointConfiguration() : new ProductionEndpointConfiguration(); UseDevelopmentServer ? new DevelopmentEndpointConfiguration() : new ExperimentalEndpointConfiguration();
public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version(); public virtual Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName().Version ?? new Version();

View File

@ -4,6 +4,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -24,6 +25,7 @@ using osu.Game.Online.Chat;
using osu.Game.Resources.Localisation.Web; using osu.Game.Resources.Localisation.Web;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using ChatStrings = osu.Game.Localisation.ChatStrings;
namespace osu.Game.Overlays.Chat namespace osu.Game.Overlays.Chat
{ {
@ -65,6 +67,9 @@ namespace osu.Game.Overlays.Chat
[Resolved(canBeNull: true)] [Resolved(canBeNull: true)]
private UserProfileOverlay? profileOverlay { get; set; } private UserProfileOverlay? profileOverlay { get; set; }
[Resolved]
private Bindable<Channel?>? currentChannel { get; set; }
private readonly APIUser user; private readonly APIUser user;
private readonly OsuSpriteText drawableText; private readonly OsuSpriteText drawableText;
@ -156,6 +161,14 @@ namespace osu.Game.Overlays.Chat
if (!user.Equals(api.LocalUser.Value)) if (!user.Equals(api.LocalUser.Value))
items.Add(new OsuMenuItem(UsersStrings.CardSendMessage, MenuItemType.Standard, openUserChannel)); items.Add(new OsuMenuItem(UsersStrings.CardSendMessage, MenuItemType.Standard, openUserChannel));
if (currentChannel?.Value != null)
{
items.Add(new OsuMenuItem(ChatStrings.MentionUser, MenuItemType.Standard, () =>
{
currentChannel.Value.TextBoxMessage.Value += $"@{user.Username} ";
}));
}
return items.ToArray(); return items.ToArray();
} }
} }

View File

@ -3,6 +3,7 @@
#nullable disable #nullable disable
using System;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Online.API; using osu.Game.Online.API;
@ -17,16 +18,22 @@ using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Threading; using osu.Framework.Threading;
using System.Collections.Generic; using System.Collections.Generic;
using JetBrains.Annotations; using JetBrains.Annotations;
using osu.Framework.Localisation;
using osu.Framework.Logging;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Resources.Localisation.Web; using osu.Game.Resources.Localisation.Web;
using APIUser = osu.Game.Online.API.Requests.Responses.APIUser; using osu.Game.Users.Drawables;
using osuTK;
namespace osu.Game.Overlays.Comments namespace osu.Game.Overlays.Comments
{ {
[Cached]
public partial class CommentsContainer : CompositeDrawable public partial class CommentsContainer : CompositeDrawable
{ {
private readonly Bindable<CommentableType> type = new Bindable<CommentableType>(); private readonly Bindable<CommentableType> type = new Bindable<CommentableType>();
private readonly BindableLong id = new BindableLong(); private readonly BindableLong id = new BindableLong();
public IBindable<CommentableType> Type => type;
public IBindable<long> Id => id;
public readonly Bindable<CommentsSortCriteria> Sort = new Bindable<CommentsSortCriteria>(); public readonly Bindable<CommentsSortCriteria> Sort = new Bindable<CommentsSortCriteria>();
public readonly BindableBool ShowDeleted = new BindableBool(); public readonly BindableBool ShowDeleted = new BindableBool();
@ -46,12 +53,14 @@ namespace osu.Game.Overlays.Comments
private DeletedCommentsCounter deletedCommentsCounter; private DeletedCommentsCounter deletedCommentsCounter;
private CommentsShowMoreButton moreButton; private CommentsShowMoreButton moreButton;
private TotalCommentsCounter commentCounter; private TotalCommentsCounter commentCounter;
private UpdateableAvatar avatar;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider) private void load(OverlayColourProvider colourProvider)
{ {
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y; AutoSizeAxes = Axes.Y;
AddRangeInternal(new Drawable[] AddRangeInternal(new Drawable[]
{ {
new Box new Box
@ -86,6 +95,32 @@ namespace osu.Game.Overlays.Comments
}, },
}, },
}, },
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Horizontal = 50, Vertical = 20 },
Children = new Drawable[]
{
avatar = new UpdateableAvatar(api.LocalUser.Value)
{
Size = new Vector2(50),
CornerExponent = 2,
CornerRadius = 25,
Masking = true,
},
new Container
{
Padding = new MarginPadding { Left = 60 },
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Child = new NewCommentEditor
{
OnPost = prependPostedComments
}
}
}
},
new CommentsHeader new CommentsHeader
{ {
Sort = { BindTarget = Sort }, Sort = { BindTarget = Sort },
@ -151,6 +186,7 @@ namespace osu.Game.Overlays.Comments
protected override void LoadComplete() protected override void LoadComplete()
{ {
User.BindValueChanged(_ => refetchComments()); User.BindValueChanged(_ => refetchComments());
User.BindValueChanged(e => avatar.User = e.NewValue);
Sort.BindValueChanged(_ => refetchComments(), true); Sort.BindValueChanged(_ => refetchComments(), true);
base.LoadComplete(); base.LoadComplete();
} }
@ -245,7 +281,6 @@ namespace osu.Game.Overlays.Comments
{ {
pinnedContent.AddRange(loaded.Where(d => d.Comment.Pinned)); pinnedContent.AddRange(loaded.Where(d => d.Comment.Pinned));
content.AddRange(loaded.Where(d => !d.Comment.Pinned)); content.AddRange(loaded.Where(d => !d.Comment.Pinned));
deletedCommentsCounter.Count.Value += topLevelComments.Select(d => d.Comment).Count(c => c.IsDeleted && c.IsTopLevel); deletedCommentsCounter.Count.Value += topLevelComments.Select(d => d.Comment).Count(c => c.IsDeleted && c.IsTopLevel);
if (bundle.HasMore) if (bundle.HasMore)
@ -288,6 +323,34 @@ namespace osu.Game.Overlays.Comments
} }
} }
private void prependPostedComments(CommentBundle bundle)
{
var topLevelComments = new List<DrawableComment>();
foreach (var comment in bundle.Comments)
{
// Exclude possible duplicated comments.
if (CommentDictionary.ContainsKey(comment.Id))
continue;
topLevelComments.Add(getDrawableComment(comment));
}
if (topLevelComments.Any())
{
LoadComponentsAsync(topLevelComments, loaded =>
{
if (content.Count > 0 && content[0] is NoCommentsPlaceholder placeholder)
content.Remove(placeholder, true);
foreach (var comment in loaded)
{
content.Insert((int)-Clock.CurrentTime, comment);
}
}, (loadCancellation = new CancellationTokenSource()).Token);
}
}
private DrawableComment getDrawableComment(Comment comment) private DrawableComment getDrawableComment(Comment comment)
{ {
if (CommentDictionary.TryGetValue(comment.Id, out var existing)) if (CommentDictionary.TryGetValue(comment.Id, out var existing))
@ -317,7 +380,7 @@ namespace osu.Game.Overlays.Comments
base.Dispose(isDisposing); base.Dispose(isDisposing);
} }
private partial class NoCommentsPlaceholder : CompositeDrawable internal partial class NoCommentsPlaceholder : CompositeDrawable
{ {
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
@ -336,5 +399,41 @@ namespace osu.Game.Overlays.Comments
}); });
} }
} }
private partial class NewCommentEditor : CommentEditor
{
[Resolved]
private CommentsContainer commentsContainer { get; set; }
[Resolved]
private IAPIProvider api { get; set; }
public Action<CommentBundle> OnPost;
//TODO should match web, left empty due to no multiline support
protected override LocalisableString FooterText => default;
protected override LocalisableString CommitButtonText => CommonStrings.ButtonsPost;
protected override LocalisableString TextBoxPlaceholder => CommentsStrings.PlaceholderNew;
protected override void OnCommit(string text)
{
ShowLoadingSpinner = true;
CommentPostRequest req = new CommentPostRequest(commentsContainer.Type.Value, commentsContainer.Id.Value, text);
req.Failure += e => Schedule(() =>
{
ShowLoadingSpinner = false;
Logger.Error(e, "Posting comment failed.");
});
req.Success += cb => Schedule(() =>
{
ShowLoadingSpinner = false;
Current.Value = string.Empty;
OnPost?.Invoke(cb);
});
api.Queue(req);
}
}
} }
} }

View File

@ -57,6 +57,11 @@ namespace osu.Game.Overlays.Comments
/// </summary> /// </summary>
public bool WasDeleted { get; protected set; } public bool WasDeleted { get; protected set; }
/// <summary>
/// Tracks this comment's level of nesting. 0 means that this comment has no parents.
/// </summary>
public int Level { get; private set; }
private FillFlowContainer childCommentsVisibilityContainer = null!; private FillFlowContainer childCommentsVisibilityContainer = null!;
private FillFlowContainer childCommentsContainer = null!; private FillFlowContainer childCommentsContainer = null!;
private LoadRepliesButton loadRepliesButton = null!; private LoadRepliesButton loadRepliesButton = null!;
@ -70,7 +75,7 @@ namespace osu.Game.Overlays.Comments
private GridContainer content = null!; private GridContainer content = null!;
private VotePill votePill = null!; private VotePill votePill = null!;
[Resolved(canBeNull: true)] [Resolved]
private IDialogOverlay? dialogOverlay { get; set; } private IDialogOverlay? dialogOverlay { get; set; }
[Resolved] [Resolved]
@ -79,7 +84,7 @@ namespace osu.Game.Overlays.Comments
[Resolved] [Resolved]
private GameHost host { get; set; } = null!; private GameHost host { get; set; } = null!;
[Resolved(canBeNull: true)] [Resolved]
private OnScreenDisplay? onScreenDisplay { get; set; } private OnScreenDisplay? onScreenDisplay { get; set; }
public DrawableComment(Comment comment) public DrawableComment(Comment comment)
@ -88,12 +93,16 @@ namespace osu.Game.Overlays.Comments
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider) private void load(OverlayColourProvider colourProvider, DrawableComment? parentComment)
{ {
LinkFlowContainer username; LinkFlowContainer username;
FillFlowContainer info; FillFlowContainer info;
CommentMarkdownContainer message; CommentMarkdownContainer message;
Level = parentComment?.Level + 1 ?? 0;
float childrenPadding = Level < 6 ? 20 : 5;
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y; AutoSizeAxes = Axes.Y;
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
@ -248,7 +257,7 @@ namespace osu.Game.Overlays.Comments
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
Padding = new MarginPadding { Left = 20 }, Padding = new MarginPadding { Left = childrenPadding },
Children = new Drawable[] Children = new Drawable[]
{ {
childCommentsContainer = new FillFlowContainer childCommentsContainer = new FillFlowContainer

View File

@ -0,0 +1,110 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Overlays.Profile.Header.Components
{
public partial class ExtendedDetails : CompositeDrawable
{
public Bindable<UserProfileData?> User { get; } = new Bindable<UserProfileData?>();
private SpriteText rankedScore = null!;
private SpriteText hitAccuracy = null!;
private SpriteText playCount = null!;
private SpriteText totalScore = null!;
private SpriteText totalHits = null!;
private SpriteText maximumCombo = null!;
private SpriteText replaysWatched = null!;
[BackgroundDependencyLoader]
private void load()
{
var font = OsuFont.Default.With(size: 12);
const float vertical_spacing = 4;
AutoSizeAxes = Axes.Both;
// this should really be a grid, but trying to avoid one to avoid the performance hit.
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(20, 0),
Children = new[]
{
new FillFlowContainer
{
Name = @"Labels",
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, vertical_spacing),
Children = new Drawable[]
{
new OsuSpriteText { Font = font, Text = UsersStrings.ShowStatsRankedScore },
new OsuSpriteText { Font = font, Text = UsersStrings.ShowStatsHitAccuracy },
new OsuSpriteText { Font = font, Text = UsersStrings.ShowStatsPlayCount },
new OsuSpriteText { Font = font, Text = UsersStrings.ShowStatsTotalScore },
new OsuSpriteText { Font = font, Text = UsersStrings.ShowStatsTotalHits },
new OsuSpriteText { Font = font, Text = UsersStrings.ShowStatsMaximumCombo },
new OsuSpriteText { Font = font, Text = UsersStrings.ShowStatsReplaysWatchedByOthers },
}
},
new FillFlowContainer
{
Name = @"Values",
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, vertical_spacing),
Children = new Drawable[]
{
rankedScore = new OsuSpriteText { Font = font },
hitAccuracy = new OsuSpriteText { Font = font },
playCount = new OsuSpriteText { Font = font },
totalScore = new OsuSpriteText { Font = font },
totalHits = new OsuSpriteText { Font = font },
maximumCombo = new OsuSpriteText { Font = font },
replaysWatched = new OsuSpriteText { Font = font },
}
},
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
User.BindValueChanged(user => updateStatistics(user.NewValue?.User.Statistics), true);
}
private void updateStatistics(UserStatistics? statistics)
{
if (statistics == null)
{
Alpha = 0;
return;
}
Alpha = 1;
rankedScore.Text = statistics.RankedScore.ToLocalisableString(@"N0");
hitAccuracy.Text = statistics.DisplayAccuracy;
playCount.Text = statistics.PlayCount.ToLocalisableString(@"N0");
totalScore.Text = statistics.TotalScore.ToLocalisableString(@"N0");
totalHits.Text = statistics.TotalHits.ToLocalisableString(@"N0");
maximumCombo.Text = statistics.MaxCombo.ToLocalisableString(@"N0");
replaysWatched.Text = statistics.ReplaysWatched.ToLocalisableString(@"N0");
}
}
}

View File

@ -0,0 +1,186 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Leaderboards;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Scoring;
using osuTK;
namespace osu.Game.Overlays.Profile.Header.Components
{
public partial class MainDetails : CompositeDrawable
{
private readonly Dictionary<ScoreRank, ScoreRankInfo> scoreRankInfos = new Dictionary<ScoreRank, ScoreRankInfo>();
private ProfileValueDisplay medalInfo = null!;
private ProfileValueDisplay ppInfo = null!;
private ProfileValueDisplay detailGlobalRank = null!;
private ProfileValueDisplay detailCountryRank = null!;
private RankGraph rankGraph = null!;
public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>();
[BackgroundDependencyLoader]
private void load()
{
AutoSizeAxes = Axes.Y;
InternalChild = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
AutoSizeDuration = 200,
AutoSizeEasing = Easing.OutQuint,
Masking = true,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 15),
Children = new Drawable[]
{
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(20),
Children = new Drawable[]
{
detailGlobalRank = new ProfileValueDisplay(true)
{
Title = UsersStrings.ShowRankGlobalSimple,
},
detailCountryRank = new ProfileValueDisplay(true)
{
Title = UsersStrings.ShowRankCountrySimple,
},
}
},
new Container
{
RelativeSizeAxes = Axes.X,
Height = 60,
Children = new Drawable[]
{
rankGraph = new RankGraph
{
RelativeSizeAxes = Axes.Both,
},
}
},
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
medalInfo = new ProfileValueDisplay
{
Title = UsersStrings.ShowStatsMedals,
},
ppInfo = new ProfileValueDisplay
{
Title = "pp",
},
new TotalPlayTime
{
User = { BindTarget = User }
},
}
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5),
Children = new[]
{
scoreRankInfos[ScoreRank.XH] = new ScoreRankInfo(ScoreRank.XH),
scoreRankInfos[ScoreRank.X] = new ScoreRankInfo(ScoreRank.X),
scoreRankInfos[ScoreRank.SH] = new ScoreRankInfo(ScoreRank.SH),
scoreRankInfos[ScoreRank.S] = new ScoreRankInfo(ScoreRank.S),
scoreRankInfos[ScoreRank.A] = new ScoreRankInfo(ScoreRank.A),
}
}
}
},
}
};
}
protected override void LoadComplete()
{
base.LoadComplete();
User.BindValueChanged(e => updateDisplay(e.NewValue), true);
}
private void updateDisplay(UserProfileData? data)
{
var user = data?.User;
medalInfo.Content = user?.Achievements?.Length.ToString() ?? "0";
ppInfo.Content = user?.Statistics?.PP?.ToLocalisableString("#,##0") ?? (LocalisableString)"0";
foreach (var scoreRankInfo in scoreRankInfos)
scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0;
detailGlobalRank.Content = user?.Statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
detailCountryRank.Content = user?.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
rankGraph.Statistics.Value = user?.Statistics;
}
private partial class ScoreRankInfo : CompositeDrawable
{
private readonly OsuSpriteText rankCount;
public int RankCount
{
set => rankCount.Text = value.ToLocalisableString("#,##0");
}
public ScoreRankInfo(ScoreRank rank)
{
AutoSizeAxes = Axes.Both;
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Y,
Width = 44,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new DrawableRank(rank)
{
RelativeSizeAxes = Axes.X,
Height = 22,
},
rankCount = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold),
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
}
}
};
}
}
}
}

View File

@ -1,33 +1,17 @@
// 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. // See the LICENCE file in the repository root for full licence text.
using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Leaderboards;
using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Overlays.Profile.Header.Components;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Scoring;
using osuTK;
namespace osu.Game.Overlays.Profile.Header namespace osu.Game.Overlays.Profile.Header
{ {
public partial class DetailHeaderContainer : CompositeDrawable public partial class DetailHeaderContainer : CompositeDrawable
{ {
private readonly Dictionary<ScoreRank, ScoreRankInfo> scoreRankInfos = new Dictionary<ScoreRank, ScoreRankInfo>();
private ProfileValueDisplay medalInfo = null!;
private ProfileValueDisplay ppInfo = null!;
private ProfileValueDisplay detailGlobalRank = null!;
private ProfileValueDisplay detailCountryRank = null!;
private RankGraph rankGraph = null!;
public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>(); public readonly Bindable<UserProfileData?> User = new Bindable<UserProfileData?>();
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -35,8 +19,6 @@ namespace osu.Game.Overlays.Profile.Header
{ {
AutoSizeAxes = Axes.Y; AutoSizeAxes = Axes.Y;
User.ValueChanged += e => updateDisplay(e.NewValue);
InternalChildren = new Drawable[] InternalChildren = new Drawable[]
{ {
new Box new Box
@ -44,149 +26,52 @@ namespace osu.Game.Overlays.Profile.Header
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Background5, Colour = colourProvider.Background5,
}, },
new FillFlowContainer new Container
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
AutoSizeDuration = 200,
AutoSizeEasing = Easing.OutQuint,
Masking = true,
Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 }, Padding = new MarginPadding { Horizontal = UserProfileOverlay.CONTENT_X_MARGIN, Vertical = 10 },
Direction = FillDirection.Vertical, Child = new GridContainer
Spacing = new Vector2(0, 15),
Children = new Drawable[]
{ {
new FillFlowContainer RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
RowDimensions = new[]
{ {
RelativeSizeAxes = Axes.X, new Dimension(GridSizeMode.AutoSize),
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(20),
Children = new Drawable[]
{
detailGlobalRank = new ProfileValueDisplay(true)
{
Title = UsersStrings.ShowRankGlobalSimple,
},
detailCountryRank = new ProfileValueDisplay(true)
{
Title = UsersStrings.ShowRankCountrySimple,
},
}
}, },
new Container ColumnDimensions = new[]
{ {
RelativeSizeAxes = Axes.X, new Dimension(),
Height = 60, new Dimension(GridSizeMode.AutoSize),
Children = new Drawable[] new Dimension(GridSizeMode.AutoSize),
{
rankGraph = new RankGraph
{
RelativeSizeAxes = Axes.Both,
},
}
}, },
new Container Content = new[]
{ {
RelativeSizeAxes = Axes.X, new Drawable[]
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{ {
new FillFlowContainer new MainDetails
{
RelativeSizeAxes = Axes.X,
User = { BindTarget = User }
},
new Box
{
RelativeSizeAxes = Axes.Y,
Width = 2,
Colour = colourProvider.Background6,
Margin = new MarginPadding { Horizontal = 15 }
},
new ExtendedDetails
{ {
AutoSizeAxes = Axes.Both,
Anchor = Anchor.CentreLeft, Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft, Origin = Anchor.CentreLeft,
Direction = FillDirection.Horizontal, User = { BindTarget = User }
Spacing = new Vector2(10, 0),
Children = new Drawable[]
{
medalInfo = new ProfileValueDisplay
{
Title = UsersStrings.ShowStatsMedals,
},
ppInfo = new ProfileValueDisplay
{
Title = "pp",
},
new TotalPlayTime
{
User = { BindTarget = User }
},
}
},
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5),
Children = new[]
{
scoreRankInfos[ScoreRank.XH] = new ScoreRankInfo(ScoreRank.XH),
scoreRankInfos[ScoreRank.X] = new ScoreRankInfo(ScoreRank.X),
scoreRankInfos[ScoreRank.SH] = new ScoreRankInfo(ScoreRank.SH),
scoreRankInfos[ScoreRank.S] = new ScoreRankInfo(ScoreRank.S),
scoreRankInfos[ScoreRank.A] = new ScoreRankInfo(ScoreRank.A),
}
} }
} }
},
}
},
};
}
private void updateDisplay(UserProfileData? data)
{
var user = data?.User;
medalInfo.Content = user?.Achievements?.Length.ToString() ?? "0";
ppInfo.Content = user?.Statistics?.PP?.ToLocalisableString("#,##0") ?? (LocalisableString)"0";
foreach (var scoreRankInfo in scoreRankInfos)
scoreRankInfo.Value.RankCount = user?.Statistics?.GradesCount[scoreRankInfo.Key] ?? 0;
detailGlobalRank.Content = user?.Statistics?.GlobalRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
detailCountryRank.Content = user?.Statistics?.CountryRank?.ToLocalisableString("\\##,##0") ?? (LocalisableString)"-";
rankGraph.Statistics.Value = user?.Statistics;
}
private partial class ScoreRankInfo : CompositeDrawable
{
private readonly OsuSpriteText rankCount;
public int RankCount
{
set => rankCount.Text = value.ToLocalisableString("#,##0");
}
public ScoreRankInfo(ScoreRank rank)
{
AutoSizeAxes = Axes.Both;
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Y,
Width = 44,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new DrawableRank(rank)
{
RelativeSizeAxes = Axes.X,
Height = 22,
},
rankCount = new OsuSpriteText
{
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold),
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
} }
} }
}; }
} };
} }
} }
} }

View File

@ -5,18 +5,15 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Extensions.LocalisationExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Cursor;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Overlays.Profile.Header.Components; using osu.Game.Overlays.Profile.Header.Components;
using osu.Game.Resources.Localisation.Web;
using osu.Game.Users.Drawables; using osu.Game.Users.Drawables;
using osuTK; using osuTK;
@ -38,7 +35,6 @@ namespace osu.Game.Overlays.Profile.Header
private OsuSpriteText titleText = null!; private OsuSpriteText titleText = null!;
private UpdateableFlag userFlag = null!; private UpdateableFlag userFlag = null!;
private OsuSpriteText userCountryText = null!; private OsuSpriteText userCountryText = null!;
private FillFlowContainer userStats = null!;
private GroupBadgeFlow groupBadgeFlow = null!; private GroupBadgeFlow groupBadgeFlow = null!;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -164,16 +160,6 @@ namespace osu.Game.Overlays.Profile.Header
} }
} }
}, },
userStats = new FillFlowContainer
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
AutoSizeAxes = Axes.Y,
Width = 300,
Margin = new MarginPadding { Right = UserProfileOverlay.CONTENT_X_MARGIN },
Padding = new MarginPadding { Vertical = 15 },
Spacing = new Vector2(0, 2)
}
}; };
User.BindValueChanged(user => updateUser(user.NewValue)); User.BindValueChanged(user => updateUser(user.NewValue));
@ -192,43 +178,6 @@ namespace osu.Game.Overlays.Profile.Header
titleText.Text = user?.Title ?? string.Empty; titleText.Text = user?.Title ?? string.Empty;
titleText.Colour = Color4Extensions.FromHex(user?.Colour ?? "fff"); titleText.Colour = Color4Extensions.FromHex(user?.Colour ?? "fff");
groupBadgeFlow.User.Value = user; groupBadgeFlow.User.Value = user;
userStats.Clear();
if (user?.Statistics != null)
{
userStats.Add(new UserStatsLine(UsersStrings.ShowStatsRankedScore, user.Statistics.RankedScore.ToLocalisableString("#,##0")));
userStats.Add(new UserStatsLine(UsersStrings.ShowStatsHitAccuracy, user.Statistics.DisplayAccuracy));
userStats.Add(new UserStatsLine(UsersStrings.ShowStatsPlayCount, user.Statistics.PlayCount.ToLocalisableString("#,##0")));
userStats.Add(new UserStatsLine(UsersStrings.ShowStatsTotalScore, user.Statistics.TotalScore.ToLocalisableString("#,##0")));
userStats.Add(new UserStatsLine(UsersStrings.ShowStatsTotalHits, user.Statistics.TotalHits.ToLocalisableString("#,##0")));
userStats.Add(new UserStatsLine(UsersStrings.ShowStatsMaximumCombo, user.Statistics.MaxCombo.ToLocalisableString("#,##0")));
userStats.Add(new UserStatsLine(UsersStrings.ShowStatsReplaysWatchedByOthers, user.Statistics.ReplaysWatched.ToLocalisableString("#,##0")));
}
}
private partial class UserStatsLine : Container
{
public UserStatsLine(LocalisableString left, LocalisableString right)
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Children = new Drawable[]
{
new OsuSpriteText
{
Font = OsuFont.GetFont(size: 15),
Text = left,
},
new OsuSpriteText
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Font = OsuFont.GetFont(size: 15, weight: FontWeight.Bold),
Text = right,
},
};
}
} }
} }
} }

View File

@ -596,6 +596,9 @@ namespace osu.Game.Screens.Select
public void FlushPendingFilterOperations() public void FlushPendingFilterOperations()
{ {
if (!IsLoaded)
return;
if (PendingFilter?.Completed == false) if (PendingFilter?.Completed == false)
{ {
applyActiveCriteria(false); applyActiveCriteria(false);

View File

@ -863,6 +863,7 @@ namespace osu.Game.Screens.Select
// if we have a pending filter operation, we want to run it now. // if we have a pending filter operation, we want to run it now.
// it could change selection (ie. if the ruleset has been changed). // it could change selection (ie. if the ruleset has been changed).
Carousel.FlushPendingFilterOperations(); Carousel.FlushPendingFilterOperations();
return true; return true;
} }