diff --git a/osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs b/osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs new file mode 100644 index 0000000000..0446cadac9 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneNewsCard.cs @@ -0,0 +1,52 @@ +// Copyright (c) ppy Pty Ltd . 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.Overlays.News; +using osu.Game.Online.API.Requests.Responses; +using osu.Framework.Allocation; +using osu.Game.Overlays; +using osuTK; +using System; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneNewsCard : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider overlayColour = new OverlayColourProvider(OverlayColourScheme.Purple); + + public TestSceneNewsCard() + { + Add(new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Direction = FillDirection.Vertical, + Width = 500, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(0, 20), + Children = new[] + { + new NewsCard(new APINewsPost + { + Title = "This post has an image which starts with \"/\" and has many authors!", + Preview = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.", + Author = "someone, someone1, someone2, someone3, someone4", + FirstImage = "/help/wiki/shared/news/banners/monthly-beatmapping-contest.png", + PublishedAt = DateTimeOffset.Now + }), + new NewsCard(new APINewsPost + { + Title = "This post has a full-url image! (HTML entity: &)", + Preview = "boom (HTML entity: &)", + Author = "user (HTML entity: &)", + FirstImage = "https://assets.ppy.sh/artists/88/header.jpg", + PublishedAt = DateTimeOffset.Now + }) + } + }); + } + } +} diff --git a/osu.Game/Graphics/DateTooltip.cs b/osu.Game/Graphics/DateTooltip.cs new file mode 100644 index 0000000000..67fcab43f7 --- /dev/null +++ b/osu.Game/Graphics/DateTooltip.cs @@ -0,0 +1,78 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Graphics +{ + public class DateTooltip : VisibilityContainer, ITooltip + { + private readonly OsuSpriteText dateText, timeText; + private readonly Box background; + + public DateTooltip() + { + AutoSizeAxes = Axes.Both; + Masking = true; + CornerRadius = 5; + + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding(10), + Children = new Drawable[] + { + dateText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + }, + timeText = new OsuSpriteText + { + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + } + } + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.GreySeafoamDarker; + timeText.Colour = colours.BlueLighter; + } + + protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); + protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); + + public bool SetContent(object content) + { + if (!(content is DateTimeOffset date)) + return false; + + dateText.Text = $"{date:d MMMM yyyy} "; + timeText.Text = $"{date:HH:mm:ss \"UTC\"z}"; + return true; + } + + public void Move(Vector2 pos) => Position = pos; + } +} diff --git a/osu.Game/Graphics/DrawableDate.cs b/osu.Game/Graphics/DrawableDate.cs index 8b6df4a834..259d9c8d6e 100644 --- a/osu.Game/Graphics/DrawableDate.cs +++ b/osu.Game/Graphics/DrawableDate.cs @@ -4,12 +4,9 @@ using System; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Sprites; using osu.Game.Utils; -using osuTK; namespace osu.Game.Graphics { @@ -81,69 +78,5 @@ namespace osu.Game.Graphics public ITooltip GetCustomTooltip() => new DateTooltip(); public object TooltipContent => Date; - - private class DateTooltip : VisibilityContainer, ITooltip - { - private readonly OsuSpriteText dateText, timeText; - private readonly Box background; - - public DateTooltip() - { - AutoSizeAxes = Axes.Both; - Masking = true; - CornerRadius = 5; - - Children = new Drawable[] - { - background = new Box - { - RelativeSizeAxes = Axes.Both - }, - new FillFlowContainer - { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - Padding = new MarginPadding(10), - Children = new Drawable[] - { - dateText = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - }, - timeText = new OsuSpriteText - { - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Regular), - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - } - } - }, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - background.Colour = colours.GreySeafoamDarker; - timeText.Colour = colours.BlueLighter; - } - - protected override void PopIn() => this.FadeIn(200, Easing.OutQuint); - protected override void PopOut() => this.FadeOut(200, Easing.OutQuint); - - public bool SetContent(object content) - { - if (!(content is DateTimeOffset date)) - return false; - - dateText.Text = $"{date:d MMMM yyyy} "; - timeText.Text = $"{date:HH:mm:ss \"UTC\"z}"; - return true; - } - - public void Move(Vector2 pos) => Position = pos; - } } } diff --git a/osu.Game/Online/API/Requests/Responses/APINewsPost.cs b/osu.Game/Online/API/Requests/Responses/APINewsPost.cs new file mode 100644 index 0000000000..ced08f0bf2 --- /dev/null +++ b/osu.Game/Online/API/Requests/Responses/APINewsPost.cs @@ -0,0 +1,57 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using Newtonsoft.Json; +using System; +using System.Net; + +namespace osu.Game.Online.API.Requests.Responses +{ + public class APINewsPost + { + [JsonProperty("id")] + public long Id { get; set; } + + private string author; + + [JsonProperty("author")] + public string Author + { + get => author; + set => author = WebUtility.HtmlDecode(value); + } + + [JsonProperty("edit_url")] + public string EditUrl { get; set; } + + [JsonProperty("first_image")] + public string FirstImage { get; set; } + + [JsonProperty("published_at")] + public DateTimeOffset PublishedAt { get; set; } + + [JsonProperty("updated_at")] + public DateTimeOffset UpdatedAt { get; set; } + + [JsonProperty("slug")] + public string Slug { get; set; } + + private string title; + + [JsonProperty("title")] + public string Title + { + get => title; + set => title = WebUtility.HtmlDecode(value); + } + + private string preview; + + [JsonProperty("preview")] + public string Preview + { + get => preview; + set => preview = WebUtility.HtmlDecode(value); + } + } +} diff --git a/osu.Game/Overlays/News/NewsCard.cs b/osu.Game/Overlays/News/NewsCard.cs new file mode 100644 index 0000000000..9c478a7c1d --- /dev/null +++ b/osu.Game/Overlays/News/NewsCard.cs @@ -0,0 +1,198 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Textures; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API.Requests.Responses; + +namespace osu.Game.Overlays.News +{ + public class NewsCard : CompositeDrawable + { + [Resolved] + private OverlayColourProvider colourProvider { get; set; } + + private readonly APINewsPost post; + + private Box background; + private TextFlowContainer main; + + public NewsCard(APINewsPost post) + { + this.post = post; + } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Masking = true; + CornerRadius = 6; + + NewsBackground bg; + + InternalChildren = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background4 + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.X, + Height = 160, + Masking = true, + CornerRadius = 6, + Children = new Drawable[] + { + new DelayedLoadWrapper(bg = new NewsBackground(post.FirstImage) + { + RelativeSizeAxes = Axes.Both, + FillMode = FillMode.Fill, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Alpha = 0 + }) + { + RelativeSizeAxes = Axes.Both + }, + new DateContainer(post.PublishedAt) + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Margin = new MarginPadding + { + Top = 10, + Right = 15 + } + } + } + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding + { + Horizontal = 15, + Vertical = 10 + }, + Child = main = new TextFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y + } + } + } + }, + new HoverClickSounds() + }; + + bg.OnLoadComplete += d => d.FadeIn(250, Easing.In); + + main.AddParagraph(post.Title, t => t.Font = OsuFont.GetFont(size: 20, weight: FontWeight.SemiBold)); + main.AddParagraph(post.Preview, t => t.Font = OsuFont.GetFont(size: 12)); // Should use sans-serif font + main.AddParagraph("by ", t => t.Font = OsuFont.GetFont(size: 12)); + main.AddText(post.Author, t => t.Font = OsuFont.GetFont(size: 12, weight: FontWeight.SemiBold)); + } + + protected override bool OnHover(HoverEvent e) + { + background.FadeColour(colourProvider.Background3, 200, Easing.OutQuint); + return true; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + background.FadeColour(colourProvider.Background4, 200, Easing.OutQuint); + base.OnHoverLost(e); + } + + [LongRunningLoad] + private class NewsBackground : Sprite + { + private readonly string sourceUrl; + + public NewsBackground(string sourceUrl) + { + this.sourceUrl = sourceUrl; + } + + [BackgroundDependencyLoader] + private void load(LargeTextureStore store) + { + Texture = store.Get(createUrl(sourceUrl)); + } + + private string createUrl(string source) + { + if (string.IsNullOrEmpty(source)) + return "Headers/news"; + + if (source.StartsWith('/')) + return "https://osu.ppy.sh" + source; + + return source; + } + } + + private class DateContainer : CircularContainer, IHasCustomTooltip + { + public ITooltip GetCustomTooltip() => new DateTooltip(); + + public object TooltipContent => date; + + private readonly DateTimeOffset date; + + public DateContainer(DateTimeOffset date) + { + this.date = date; + } + + [BackgroundDependencyLoader] + private void load(OverlayColourProvider colourProvider) + { + AutoSizeAxes = Axes.Both; + Masking = true; + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = colourProvider.Background6.Opacity(0.5f) + }, + new OsuSpriteText + { + Text = date.ToString("d MMM yyyy").ToUpper(), + Font = OsuFont.GetFont(size: 10, weight: FontWeight.SemiBold), + Margin = new MarginPadding + { + Horizontal = 20, + Vertical = 5 + } + } + }; + } + } + } +}