1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 11:35:35 +08:00

Merge branch 'master' into fix-incorrect-beatmap-set-info-equality

This commit is contained in:
Dan Balasescu 2019-10-17 19:51:03 +09:00 committed by GitHub
commit bd57c0b58e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1065 additions and 61 deletions

View File

@ -285,6 +285,12 @@ namespace osu.Game.Tests.Visual.Background
});
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
rulesets?.Dispose();
}
private class DummySongSelect : PlaySongSelect
{
protected override BackgroundScreen CreateBackground()

View File

@ -0,0 +1,58 @@
// 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 NUnit.Framework;
using osu.Game.Online.API.Requests;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics;
using osu.Game.Overlays.Comments;
namespace osu.Game.Tests.Visual.Online
{
[TestFixture]
public class TestSceneCommentsContainer : OsuTestScene
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(CommentsContainer),
typeof(CommentsHeader),
typeof(DrawableComment),
typeof(HeaderButton),
typeof(SortTabControl),
typeof(ShowChildrenButton),
typeof(DeletedChildrenPlaceholder)
};
protected override bool UseOnlineAPI => true;
public TestSceneCommentsContainer()
{
BasicScrollContainer scrollFlow;
Add(scrollFlow = new BasicScrollContainer
{
RelativeSizeAxes = Axes.Both,
});
AddStep("Big Black comments", () =>
{
scrollFlow.Clear();
scrollFlow.Add(new CommentsContainer(CommentableType.Beatmapset, 41823));
});
AddStep("Airman comments", () =>
{
scrollFlow.Clear();
scrollFlow.Add(new CommentsContainer(CommentableType.Beatmapset, 24313));
});
AddStep("lazer build comments", () =>
{
scrollFlow.Clear();
scrollFlow.Add(new CommentsContainer(CommentableType.Build, 4772));
});
}
}
}

View File

@ -1,10 +1,12 @@
// 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.Overlays.Profile.Sections;
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Framework.Allocation;
using osu.Game.Graphics;
namespace osu.Game.Tests.Visual.Online
{
@ -17,11 +19,11 @@ namespace osu.Game.Tests.Visual.Online
public TestSceneShowMoreButton()
{
ShowMoreButton button = null;
TestButton button = null;
int fireCount = 0;
Add(button = new ShowMoreButton
Add(button = new TestButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@ -51,5 +53,16 @@ namespace osu.Game.Tests.Visual.Online
AddAssert("action fired twice", () => fireCount == 2);
AddAssert("is in loading state", () => button.IsLoading);
}
private class TestButton : ShowMoreButton
{
[BackgroundDependencyLoader]
private void load(OsuColour colors)
{
IdleColour = colors.YellowDark;
HoverColour = colors.Yellow;
ChevronIconColour = colors.Red;
}
}
}
}

View File

@ -349,5 +349,11 @@ namespace osu.Game.Tests.Visual.SongSelect
DateAdded = DateTimeOffset.UtcNow,
};
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
rulesets?.Dispose();
}
}
}

View File

@ -1,30 +1,36 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osuTK;
using osuTK.Graphics;
using System.Collections.Generic;
namespace osu.Game.Overlays.Profile.Sections
namespace osu.Game.Graphics.UserInterface
{
public class ShowMoreButton : OsuHoverContainer
{
private const float fade_duration = 200;
private readonly Box background;
private readonly LoadingAnimation loading;
private readonly FillFlowContainer content;
private Color4 chevronIconColour;
protected override IEnumerable<Drawable> EffectTargets => new[] { background };
protected Color4 ChevronIconColour
{
get => chevronIconColour;
set => chevronIconColour = leftChevron.Colour = rightChevron.Colour = value;
}
public string Text
{
get => text.Text;
set => text.Text = value;
}
private bool isLoading;
@ -33,26 +39,32 @@ namespace osu.Game.Overlays.Profile.Sections
get => isLoading;
set
{
if (isLoading == value)
return;
isLoading = value;
Enabled.Value = !isLoading;
if (value)
{
loading.FadeIn(fade_duration, Easing.OutQuint);
loading.Show();
content.FadeOut(fade_duration, Easing.OutQuint);
}
else
{
loading.FadeOut(fade_duration, Easing.OutQuint);
loading.Hide();
content.FadeIn(fade_duration, Easing.OutQuint);
}
}
}
private readonly Box background;
private readonly LoadingAnimation loading;
private readonly FillFlowContainer content;
private readonly ChevronIcon leftChevron;
private readonly ChevronIcon rightChevron;
private readonly SpriteText text;
protected override IEnumerable<Drawable> EffectTargets => new[] { background };
public ShowMoreButton()
{
AutoSizeAxes = Axes.Both;
@ -77,15 +89,15 @@ namespace osu.Game.Overlays.Profile.Sections
Spacing = new Vector2(7),
Children = new Drawable[]
{
new ChevronIcon(),
new OsuSpriteText
leftChevron = new ChevronIcon(),
text = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold),
Text = "show more".ToUpper(),
},
new ChevronIcon(),
rightChevron = new ChevronIcon(),
}
},
loading = new LoadingAnimation
@ -99,13 +111,6 @@ namespace osu.Game.Overlays.Profile.Sections
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colors)
{
IdleColour = colors.GreySeafoamDark;
HoverColour = colors.GreySeafoam;
}
protected override bool OnClick(ClickEvent e)
{
if (!Enabled.Value)
@ -133,12 +138,6 @@ namespace osu.Game.Overlays.Profile.Sections
Size = new Vector2(icon_size);
Icon = FontAwesome.Solid.ChevronDown;
}
[BackgroundDependencyLoader]
private void load(OsuColour colors)
{
Colour = colors.Yellow;
}
}
}
}

View File

@ -0,0 +1,47 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.IO.Network;
using Humanizer;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays.Comments;
namespace osu.Game.Online.API.Requests
{
public class GetCommentsRequest : APIRequest<CommentBundle>
{
private readonly long id;
private readonly int page;
private readonly CommentableType type;
private readonly CommentsSortCriteria sort;
public GetCommentsRequest(CommentableType type, long id, CommentsSortCriteria sort = CommentsSortCriteria.New, int page = 1)
{
this.type = type;
this.sort = sort;
this.id = id;
this.page = page;
}
protected override WebRequest CreateWebRequest()
{
var req = base.CreateWebRequest();
req.AddParameter("commentable_type", type.ToString().Underscore().ToLowerInvariant());
req.AddParameter("commentable_id", id.ToString());
req.AddParameter("sort", sort.ToString().ToLowerInvariant());
req.AddParameter("page", page.ToString());
return req;
}
protected override string Target => "comments";
}
public enum CommentableType
{
Build,
Beatmapset,
NewsPost
}
}

View File

@ -0,0 +1,79 @@
// 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 Newtonsoft.Json;
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
{
public class Comment
{
[JsonProperty(@"id")]
public long Id { get; set; }
[JsonProperty(@"parent_id")]
public long? ParentId { get; set; }
public readonly List<Comment> ChildComments = new List<Comment>();
public Comment ParentComment { get; set; }
[JsonProperty(@"user_id")]
public long? UserId { get; set; }
public User User { get; set; }
[JsonProperty(@"message")]
public string Message { get; set; }
[JsonProperty(@"message_html")]
public string MessageHtml { get; set; }
[JsonProperty(@"replies_count")]
public int RepliesCount { get; set; }
[JsonProperty(@"votes_count")]
public int VotesCount { get; set; }
[JsonProperty(@"commenatble_type")]
public string CommentableType { get; set; }
[JsonProperty(@"commentable_id")]
public int CommentableId { get; set; }
[JsonProperty(@"legacy_name")]
public string LegacyName { get; set; }
[JsonProperty(@"created_at")]
public DateTimeOffset CreatedAt { get; set; }
[JsonProperty(@"updated_at")]
public DateTimeOffset? UpdatedAt { get; set; }
[JsonProperty(@"deleted_at")]
public DateTimeOffset? DeletedAt { get; set; }
[JsonProperty(@"edited_at")]
public DateTimeOffset? EditedAt { get; set; }
[JsonProperty(@"edited_by_id")]
public long? EditedById { get; set; }
public User EditedUser { get; set; }
public bool IsTopLevel => !ParentId.HasValue;
public bool IsDeleted => DeletedAt.HasValue;
public bool HasMessage => !string.IsNullOrEmpty(MessageHtml);
public string GetMessage => HasMessage ? WebUtility.HtmlDecode(Regex.Replace(MessageHtml, @"<(.|\n)*?>", string.Empty)) : string.Empty;
public int DeletedChildrenCount => ChildComments.Count(c => c.IsDeleted);
}
}

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 Newtonsoft.Json;
using osu.Game.Users;
using System.Collections.Generic;
namespace osu.Game.Online.API.Requests.Responses
{
public class CommentBundle
{
private List<Comment> comments;
[JsonProperty(@"comments")]
public List<Comment> Comments
{
get => comments;
set
{
comments = value;
comments.ForEach(child =>
{
if (child.ParentId != null)
{
comments.ForEach(parent =>
{
if (parent.Id == child.ParentId)
{
parent.ChildComments.Add(child);
child.ParentComment = parent;
}
});
}
});
}
}
[JsonProperty(@"has_more")]
public bool HasMore { get; set; }
[JsonProperty(@"has_more_id")]
public long? HasMoreId { get; set; }
[JsonProperty(@"user_follow")]
public bool UserFollow { get; set; }
[JsonProperty(@"included_comments")]
public List<Comment> IncludedComments { get; set; }
private List<User> users;
[JsonProperty(@"users")]
public List<User> Users
{
get => users;
set
{
users = value;
value.ForEach(u =>
{
Comments.ForEach(c =>
{
if (c.UserId == u.Id)
c.User = u;
if (c.EditedById == u.Id)
c.EditedUser = u;
});
});
}
}
[JsonProperty(@"total")]
public int Total { get; set; }
[JsonProperty(@"top_level_count")]
public int TopLevelCount { get; set; }
}
}

View File

@ -298,6 +298,12 @@ namespace osu.Game
public string[] HandledExtensions => fileImporters.SelectMany(i => i.HandledExtensions).ToArray();
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
RulesetStore?.Dispose();
}
private class OsuUserInputManager : UserInputManager
{
protected override MouseButtonEventManager CreateButtonManagerFor(MouseButton button)

View File

@ -0,0 +1,197 @@
// 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.Game.Online.API;
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;
using osu.Framework.Extensions.IEnumerableExtensions;
namespace osu.Game.Overlays.Comments
{
public class CommentsContainer : CompositeDrawable
{
private readonly CommentableType type;
private readonly long id;
public readonly Bindable<CommentsSortCriteria> Sort = new Bindable<CommentsSortCriteria>();
public readonly BindableBool ShowDeleted = new BindableBool();
[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;
public CommentsContainer(CommentableType type, long id)
{
this.type = type;
this.id = id;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
AddRangeInternal(new Drawable[]
{
background = new Box
{
RelativeSizeAxes = Axes.Both,
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new CommentsHeader
{
Sort = { BindTarget = Sort },
ShowDeleted = { BindTarget = ShowDeleted }
},
content = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
},
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(0.2f)
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
deletedChildrenPlaceholder = new DeletedChildrenPlaceholder
{
ShowDeleted = { BindTarget = ShowDeleted }
},
new Container
{
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Child = moreButton = new CommentsShowMoreButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Margin = new MarginPadding(5),
Action = getComments
}
}
}
}
}
}
}
}
});
}
[BackgroundDependencyLoader]
private void load()
{
background.Colour = colours.Gray2;
}
protected override void LoadComplete()
{
Sort.BindValueChanged(onSortChanged, true);
base.LoadComplete();
}
private void onSortChanged(ValueChangedEvent<CommentsSortCriteria> sort)
{
clearComments();
getComments();
}
private void getComments()
{
request?.Cancel();
loadCancellation?.Cancel();
request = new GetCommentsRequest(type, id, Sort.Value, currentPage++);
request.Success += onSuccess;
api.Queue(request);
}
private void clearComments()
{
currentPage = 1;
deletedChildrenPlaceholder.DeletedCount.Value = 0;
moreButton.IsLoading = true;
content.Clear();
}
private void onSuccess(CommentBundle response)
{
loadCancellation = new CancellationTokenSource();
FillFlowContainer page = new FillFlowContainer
{
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 =>
{
content.Add(loaded);
deletedChildrenPlaceholder.DeletedCount.Value += response.Comments.Count(c => c.IsDeleted && c.IsTopLevel);
if (response.HasMore)
{
int loadedTopLevelComments = 0;
content.Children.OfType<FillFlowContainer>().ForEach(p => loadedTopLevelComments += p.Children.OfType<DrawableComment>().Count());
moreButton.Current.Value = response.TopLevelCount - loadedTopLevelComments;
moreButton.IsLoading = false;
}
moreButton.FadeTo(response.HasMore ? 1 : 0);
}, loadCancellation.Token);
}
protected override void Dispose(bool isDisposing)
{
request?.Cancel();
loadCancellation?.Cancel();
base.Dispose(isDisposing);
}
}
}

View File

@ -0,0 +1,32 @@
// 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.Bindables;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Comments
{
public class CommentsShowMoreButton : ShowMoreButton
{
public readonly BindableInt Current = new BindableInt();
public CommentsShowMoreButton()
{
IdleColour = OsuColour.Gray(0.3f);
HoverColour = OsuColour.Gray(0.4f);
ChevronIconColour = OsuColour.Gray(0.5f);
}
protected override void LoadComplete()
{
Current.BindValueChanged(onCurrentChanged, true);
base.LoadComplete();
}
private void onCurrentChanged(ValueChangedEvent<int> count)
{
Text = $@"Show More ({count.NewValue})".ToUpper();
}
}
}

View File

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

View File

@ -0,0 +1,363 @@
// 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.Containers;
using osu.Framework.Graphics;
using osu.Game.Graphics;
using osu.Framework.Graphics.Sprites;
using osuTK;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Users.Drawables;
using osu.Game.Graphics.Containers;
using osu.Game.Utils;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Shapes;
using System.Linq;
using osu.Game.Online.Chat;
namespace osu.Game.Overlays.Comments
{
public class DrawableComment : CompositeDrawable
{
private const int avatar_size = 40;
private const int margin = 10;
public readonly BindableBool ShowDeleted = new BindableBool();
private readonly BindableBool childrenExpanded = new BindableBool(true);
private readonly FillFlowContainer childCommentsVisibilityContainer;
private readonly Comment comment;
public DrawableComment(Comment comment)
{
LinkFlowContainer username;
FillFlowContainer childCommentsContainer;
DeletedChildrenPlaceholder deletedChildrenPlaceholder;
FillFlowContainer info;
LinkFlowContainer message;
GridContainer content;
VotePill votePill;
this.comment = comment;
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChild = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding(margin),
Child = content = new GridContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
ColumnDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize),
new Dimension(),
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.AutoSize)
},
Content = new[]
{
new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Margin = new MarginPadding { Horizontal = margin },
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5, 0),
Children = new Drawable[]
{
votePill = new VotePill(comment.VotesCount)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AlwaysPresent = true,
},
new UpdateableAvatar(comment.User)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(avatar_size),
Masking = true,
CornerRadius = avatar_size / 2f,
},
}
},
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(0, 3),
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(7, 0),
Children = new Drawable[]
{
username = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true))
{
AutoSizeAxes = Axes.Both,
},
new ParentUsername(comment),
new SpriteText
{
Alpha = comment.IsDeleted ? 1 : 0,
Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true),
Text = @"deleted",
}
}
},
message = new LinkFlowContainer(s => s.Font = OsuFont.GetFont(size: 14))
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Right = 40 }
},
info = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(10, 0),
Colour = OsuColour.Gray(0.7f),
Children = new Drawable[]
{
new SpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Font = OsuFont.GetFont(size: 12),
Text = HumanizerUtils.Humanize(comment.CreatedAt)
},
new RepliesButton(comment.RepliesCount)
{
Expanded = { BindTarget = childrenExpanded }
},
}
}
}
}
}
}
}
},
childCommentsVisibilityContainer = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
childCommentsContainer = new FillFlowContainer
{
Padding = new MarginPadding { Left = 20 },
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical
},
deletedChildrenPlaceholder = new DeletedChildrenPlaceholder
{
ShowDeleted = { BindTarget = ShowDeleted }
}
}
}
}
};
deletedChildrenPlaceholder.DeletedCount.Value = comment.DeletedChildrenCount;
if (comment.UserId.HasValue)
username.AddUserLink(comment.User);
else
username.AddText(comment.LegacyName);
if (comment.EditedAt.HasValue)
{
info.Add(new SpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Font = OsuFont.GetFont(size: 12),
Text = $@"edited {HumanizerUtils.Humanize(comment.EditedAt.Value)} by {comment.EditedUser.Username}"
});
}
if (comment.HasMessage)
{
var formattedSource = MessageFormatter.FormatText(comment.GetMessage);
message.AddLinks(formattedSource.Text, formattedSource.Links);
}
if (comment.IsDeleted)
{
content.FadeColour(OsuColour.Gray(0.5f));
votePill.Hide();
}
if (comment.IsTopLevel)
{
AddInternal(new Container
{
RelativeSizeAxes = Axes.X,
Height = 1.5f,
Anchor = Anchor.BottomCentre,
Origin = Anchor.BottomCentre,
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(0.1f)
}
});
if (comment.ChildComments.Any())
{
AddInternal(new ChevronButton(comment)
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Margin = new MarginPadding { Right = 30, Top = margin },
Expanded = { BindTarget = childrenExpanded }
});
}
}
comment.ChildComments.ForEach(c => childCommentsContainer.Add(new DrawableComment(c)
{
ShowDeleted = { BindTarget = ShowDeleted }
}));
}
protected override void LoadComplete()
{
ShowDeleted.BindValueChanged(show =>
{
if (comment.IsDeleted)
this.FadeTo(show.NewValue ? 1 : 0);
}, true);
childrenExpanded.BindValueChanged(expanded => childCommentsVisibilityContainer.FadeTo(expanded.NewValue ? 1 : 0), true);
base.LoadComplete();
}
private class ChevronButton : ShowChildrenButton
{
private readonly SpriteIcon icon;
public ChevronButton(Comment comment)
{
Alpha = comment.IsTopLevel && comment.ChildComments.Any() ? 1 : 0;
Child = icon = new SpriteIcon
{
Size = new Vector2(12),
Colour = OsuColour.Gray(0.7f)
};
}
protected override void OnExpandedChanged(ValueChangedEvent<bool> expanded)
{
icon.Icon = expanded.NewValue ? FontAwesome.Solid.ChevronUp : FontAwesome.Solid.ChevronDown;
}
}
private class RepliesButton : ShowChildrenButton
{
private readonly SpriteText text;
private readonly int count;
public RepliesButton(int count)
{
this.count = count;
Alpha = count == 0 ? 0 : 1;
Child = text = new SpriteText
{
Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold),
};
}
protected override void OnExpandedChanged(ValueChangedEvent<bool> expanded)
{
text.Text = $@"{(expanded.NewValue ? "[+]" : "[-]")} replies ({count})";
}
}
private class ParentUsername : FillFlowContainer, IHasTooltip
{
public string TooltipText => getParentMessage();
private readonly Comment parentComment;
public ParentUsername(Comment comment)
{
parentComment = comment.ParentComment;
AutoSizeAxes = Axes.Both;
Direction = FillDirection.Horizontal;
Spacing = new Vector2(3, 0);
Alpha = comment.ParentId == null ? 0 : 1;
Children = new Drawable[]
{
new SpriteIcon
{
Icon = FontAwesome.Solid.Reply,
Size = new Vector2(14),
},
new SpriteText
{
Font = OsuFont.GetFont(size: 14, weight: FontWeight.Bold, italics: true),
Text = parentComment?.User?.Username ?? parentComment?.LegacyName
}
};
}
private string getParentMessage()
{
if (parentComment == null)
return string.Empty;
return parentComment.HasMessage ? parentComment.GetMessage : parentComment.IsDeleted ? @"deleted" : string.Empty;
}
}
private class VotePill : CircularContainer
{
public VotePill(int count)
{
AutoSizeAxes = Axes.X;
Height = 20;
Masking = true;
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(0.05f)
},
new SpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Margin = new MarginPadding { Horizontal = margin },
Font = OsuFont.GetFont(size: 14),
Text = $"+{count}"
}
};
}
}
}
}

View File

@ -0,0 +1,34 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Graphics;
using osu.Game.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Framework.Bindables;
namespace osu.Game.Overlays.Comments
{
public abstract class ShowChildrenButton : OsuHoverContainer
{
public readonly BindableBool Expanded = new BindableBool(true);
protected ShowChildrenButton()
{
AutoSizeAxes = Axes.Both;
}
protected override void LoadComplete()
{
Expanded.BindValueChanged(OnExpandedChanged, true);
base.LoadComplete();
}
protected abstract void OnExpandedChanged(ValueChangedEvent<bool> expanded);
protected override bool OnClick(ClickEvent e)
{
Expanded.Value = !Expanded.Value;
return true;
}
}
}

View File

@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Profile.Sections
{
public abstract class PaginatedContainer<TModel> : FillFlowContainer
{
private readonly ShowMoreButton moreButton;
private readonly ProfileShowMoreButton moreButton;
private readonly OsuSpriteText missingText;
private APIRequest<List<TModel>> retrievalRequest;
private CancellationTokenSource loadCancellation;
@ -56,7 +56,7 @@ namespace osu.Game.Overlays.Profile.Sections
RelativeSizeAxes = Axes.X,
Spacing = new Vector2(0, 2),
},
moreButton = new ShowMoreButton
moreButton = new ProfileShowMoreButton
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,

View File

@ -0,0 +1,20 @@
// 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.Game.Graphics;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Overlays.Profile.Sections
{
public class ProfileShowMoreButton : ShowMoreButton
{
[BackgroundDependencyLoader]
private void load(OsuColour colors)
{
IdleColour = colors.GreySeafoamDark;
HoverColour = colors.GreySeafoam;
ChevronIconColour = colors.Yellow;
}
}
}

View File

@ -11,28 +11,22 @@ using osu.Game.Database;
namespace osu.Game.Rulesets
{
/// <summary>
/// Todo: All of this needs to be moved to a RulesetStore.
/// </summary>
public class RulesetStore : DatabaseBackedStore
public class RulesetStore : DatabaseBackedStore, IDisposable
{
private static readonly Dictionary<Assembly, Type> loaded_assemblies = new Dictionary<Assembly, Type>();
private const string ruleset_library_prefix = "osu.Game.Rulesets";
static RulesetStore()
{
AppDomain.CurrentDomain.AssemblyResolve += currentDomain_AssemblyResolve;
// On android in release configuration assemblies are loaded from the apk directly into memory.
// We cannot read assemblies from cwd, so should check loaded assemblies instead.
loadFromAppDomain();
loadFromDisk();
}
private readonly Dictionary<Assembly, Type> loadedAssemblies = new Dictionary<Assembly, Type>();
public RulesetStore(IDatabaseContextFactory factory)
: base(factory)
{
// On android in release configuration assemblies are loaded from the apk directly into memory.
// We cannot read assemblies from cwd, so should check loaded assemblies instead.
loadFromAppDomain();
loadFromDisk();
addMissingRulesets();
AppDomain.CurrentDomain.AssemblyResolve += resolveRulesetAssembly;
}
/// <summary>
@ -54,9 +48,7 @@ namespace osu.Game.Rulesets
/// </summary>
public IEnumerable<RulesetInfo> AvailableRulesets { get; private set; }
private static Assembly currentDomain_AssemblyResolve(object sender, ResolveEventArgs args) => loaded_assemblies.Keys.FirstOrDefault(a => a.FullName == args.Name);
private const string ruleset_library_prefix = "osu.Game.Rulesets";
private Assembly resolveRulesetAssembly(object sender, ResolveEventArgs args) => loadedAssemblies.Keys.FirstOrDefault(a => a.FullName == args.Name);
private void addMissingRulesets()
{
@ -64,7 +56,7 @@ namespace osu.Game.Rulesets
{
var context = usage.Context;
var instances = loaded_assemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r, (RulesetInfo)null)).ToList();
var instances = loadedAssemblies.Values.Select(r => (Ruleset)Activator.CreateInstance(r, (RulesetInfo)null)).ToList();
//add all legacy modes in correct order
foreach (var r in instances.Where(r => r.LegacyID != null).OrderBy(r => r.LegacyID))
@ -113,7 +105,7 @@ namespace osu.Game.Rulesets
}
}
private static void loadFromAppDomain()
private void loadFromAppDomain()
{
foreach (var ruleset in AppDomain.CurrentDomain.GetAssemblies())
{
@ -126,7 +118,7 @@ namespace osu.Game.Rulesets
}
}
private static void loadFromDisk()
private void loadFromDisk()
{
try
{
@ -141,11 +133,11 @@ namespace osu.Game.Rulesets
}
}
private static void loadRulesetFromFile(string file)
private void loadRulesetFromFile(string file)
{
var filename = Path.GetFileNameWithoutExtension(file);
if (loaded_assemblies.Values.Any(t => t.Namespace == filename))
if (loadedAssemblies.Values.Any(t => t.Namespace == filename))
return;
try
@ -158,19 +150,30 @@ namespace osu.Game.Rulesets
}
}
private static void addRuleset(Assembly assembly)
private void addRuleset(Assembly assembly)
{
if (loaded_assemblies.ContainsKey(assembly))
if (loadedAssemblies.ContainsKey(assembly))
return;
try
{
loaded_assemblies[assembly] = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset)));
loadedAssemblies[assembly] = assembly.GetTypes().First(t => t.IsPublic && t.IsSubclassOf(typeof(Ruleset)));
}
catch (Exception e)
{
Logger.Error(e, $"Failed to add ruleset {assembly}");
}
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
AppDomain.CurrentDomain.AssemblyResolve -= resolveRulesetAssembly;
}
}
}