diff --git a/osu.Game.Tests/Visual/TestCaseChatDisplay.cs b/osu.Game.Tests/Visual/TestCaseChatDisplay.cs index 6df59350e7..048106da26 100644 --- a/osu.Game.Tests/Visual/TestCaseChatDisplay.cs +++ b/osu.Game.Tests/Visual/TestCaseChatDisplay.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System.ComponentModel; -using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; using osu.Game.Overlays; @@ -11,28 +10,12 @@ namespace osu.Game.Tests.Visual [Description("Testing chat api and overlay")] public class TestCaseChatDisplay : OsuTestCase { - private readonly BeatmapSetOverlay beatmapSetOverlay; - private readonly ChatOverlay chat; - - private DependencyContainer dependencies; - - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); - public TestCaseChatDisplay() { - Add(chat = new ChatOverlay + Add(new ChatOverlay { State = Visibility.Visible }); - - Add(beatmapSetOverlay = new BeatmapSetOverlay()); - } - - [BackgroundDependencyLoader] - private void load() - { - dependencies.Cache(chat); - dependencies.Cache(beatmapSetOverlay); } } } diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index 7984a67c36..ef36242f1f 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -14,6 +14,7 @@ using osu.Game.Overlays.Chat; using osu.Game.Users; using System; using System.Linq; +using osu.Game.Graphics.Sprites; namespace osu.Game.Tests.Visual { @@ -57,7 +58,7 @@ namespace osu.Game.Tests.Visual AddAssert($"msg #{textContainer.Count} is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic(newLine.ContentFlow)); AddAssert($"msg #{textContainer.Count} shows link(s)", isShowingLinks); - bool isItalic(ChatFlowContainer c) => c.Cast().All(sprite => sprite.Font == @"Exo2.0-MediumItalic"); + bool isItalic(LinkFlowContainer c) => c.Cast().All(sprite => sprite.Font == @"Exo2.0-MediumItalic"); bool isShowingLinks() { @@ -68,11 +69,11 @@ namespace osu.Game.Tests.Visual textColour = OsuColour.FromHex(newLine.Message.Sender.Colour); return newLine.ContentFlow - .Cast() - .All(sprite => sprite.HandleInput && !sprite.TextColour.Equals(textColour) - || !sprite.HandleInput && sprite.TextColour.Equals(textColour) + .Cast() + .All(sprite => sprite.HandleInput && !sprite.Colour.Equals(textColour) + || !sprite.HandleInput && sprite.Colour.Equals(textColour) // if someone with a background uses /me with a link, the usual link colour is overridden - || isAction && hasBackground && sprite.HandleInput && !sprite.TextColour.Equals((ColourInfo)Color4.White)); + || isAction && hasBackground && sprite.HandleInput && !sprite.Colour.Equals((ColourInfo)Color4.White)); } } diff --git a/osu.Game/Graphics/Containers/ChatFlowContainer.cs b/osu.Game/Graphics/Containers/ChatFlowContainer.cs deleted file mode 100644 index bf7d152a3b..0000000000 --- a/osu.Game/Graphics/Containers/ChatFlowContainer.cs +++ /dev/null @@ -1,61 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics.Colour; -using osu.Game.Online.Chat; -using System; - -namespace osu.Game.Graphics.Containers -{ - public class ChatFlowContainer : OsuTextFlowContainer - { - private readonly Action defaultCreationParameters; - private ColourInfo urlColour; - - public ChatFlowContainer(Action defaultCreationParameters = null) - { - this.defaultCreationParameters = defaultCreationParameters; - } - - public override bool HandleInput => true; - - public void AddLink(string text, string url, LinkAction linkType, string linkArgument) - { - var chatSprite = new ChatLink - { - Text = text, - Url = url, - TextColour = urlColour, - LinkAction = linkType, - LinkArgument = linkArgument, - }; - - defaultCreationParameters?.Invoke(chatSprite); - - AddInternal(chatSprite); - } - - public void AddText(string text, Action creationParameters = null) - { - foreach (var word in SplitWords(text)) - { - if (string.IsNullOrEmpty(word)) - continue; - - var chatSprite = new ChatLink { Text = word }; - - defaultCreationParameters?.Invoke(chatSprite); - creationParameters?.Invoke(chatSprite); - - AddInternal(chatSprite); - } - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - urlColour = colours.Blue; - } - } -} diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs new file mode 100644 index 0000000000..4485630e12 --- /dev/null +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -0,0 +1,75 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Online.Chat; +using System; +using System.Diagnostics; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics.Sprites; +using osu.Game.Overlays; + +namespace osu.Game.Graphics.Containers +{ + public class LinkFlowContainer : OsuTextFlowContainer + { + public LinkFlowContainer(Action defaultCreationParameters = null) + : base(defaultCreationParameters) + { + } + + public override bool HandleInput => true; + + private BeatmapSetOverlay beatmapSetOverlay; + private ChatOverlay chat; + private OsuGame game; + + [BackgroundDependencyLoader(true)] + private void load(BeatmapSetOverlay beatmapSetOverlay, ChatOverlay chat, OsuGame game) + { + this.beatmapSetOverlay = beatmapSetOverlay; + this.chat = chat; + // this will be null in tests + this.game = game; + } + + public void AddLink(string text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null) + { + AddInternal(new DrawableLinkCompiler(AddText(text).ToList()) + { + TooltipText = tooltipText ?? (url != text ? url : string.Empty), + Action = () => + { + switch (linkType) + { + case LinkAction.OpenBeatmap: + // todo: implement this when overlay.ShowBeatmap(id) exists + break; + case LinkAction.OpenBeatmapSet: + if (int.TryParse(linkArgument, out int setId)) + beatmapSetOverlay.ShowBeatmapSet(setId); + break; + case LinkAction.OpenChannel: + chat.OpenChannel(chat.AvailableChannels.Find(c => c.Name == linkArgument)); + break; + case LinkAction.OpenEditorTimestamp: + game?.LoadEditorTimestamp(); + break; + case LinkAction.JoinMultiplayerMatch: + if (int.TryParse(linkArgument, out int matchId)) + game?.JoinMultiplayerMatch(matchId); + break; + case LinkAction.Spectate: + // todo: implement this when spectating exists + break; + case LinkAction.External: + Process.Start(url); + break; + default: + throw new NotImplementedException($"This {nameof(LinkAction)} ({linkType.ToString()}) is missing an associated action."); + } + } + }); + } + } +} diff --git a/osu.Game/Graphics/Containers/OsuHoverContainer.cs b/osu.Game/Graphics/Containers/OsuHoverContainer.cs index 502ac6592f..fd1742871b 100644 --- a/osu.Game/Graphics/Containers/OsuHoverContainer.cs +++ b/osu.Game/Graphics/Containers/OsuHoverContainer.cs @@ -1,8 +1,10 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Collections.Generic; using OpenTK.Graphics; using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Graphics; using osu.Framework.Input; @@ -10,26 +12,34 @@ namespace osu.Game.Graphics.Containers { public class OsuHoverContainer : OsuClickableContainer { - private Color4 hoverColour; - private Color4 unhoverColour; + protected Color4 HoverColour; + + protected Color4 IdleColour = Color4.White; + + protected virtual IEnumerable EffectTargets => new[] { Content }; protected override bool OnHover(InputState state) { - this.FadeColour(hoverColour, 500, Easing.OutQuint); + EffectTargets.ForEach(d => d.FadeColour(HoverColour, 500, Easing.OutQuint)); return base.OnHover(state); } protected override void OnHoverLost(InputState state) { - this.FadeColour(unhoverColour, 500, Easing.OutQuint); + EffectTargets.ForEach(d => d.FadeColour(IdleColour, 500, Easing.OutQuint)); base.OnHoverLost(state); } [BackgroundDependencyLoader] private void load(OsuColour colours) { - hoverColour = colours.Yellow; - unhoverColour = Colour; + HoverColour = colours.Yellow; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + EffectTargets.ForEach(d => d.FadeColour(IdleColour)); } } } diff --git a/osu.Game/Graphics/Sprites/OsuSpriteLink.cs b/osu.Game/Graphics/Sprites/OsuSpriteLink.cs deleted file mode 100644 index f663f50624..0000000000 --- a/osu.Game/Graphics/Sprites/OsuSpriteLink.cs +++ /dev/null @@ -1,52 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; -using System.Collections.Generic; -using System.Diagnostics; -using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.Containers; - -namespace osu.Game.Graphics.Sprites -{ - public class OsuSpriteLink : OsuSpriteText - { - public override bool HandleInput => !string.IsNullOrEmpty(Url); - - protected override IEnumerable FlowingChildren => Children; - - protected override Container Content => content; - - private readonly OsuHoverContainer content; - - public OsuSpriteLink() - { - AddInternal(content = new OsuHoverContainer - { - AutoSizeAxes = Axes.Both, - Action = OnLinkClicked, - }); - } - - private string url; - - public string Url - { - get => url; - set - { - if (!string.IsNullOrEmpty(value)) - url = value; - } - } - - public ColourInfo TextColour - { - get => Content.Colour; - set => Content.Colour = value; - } - - protected virtual void OnLinkClicked() => Process.Start(Url); - } -} diff --git a/osu.Game/Online/Chat/ChatLink.cs b/osu.Game/Online/Chat/ChatLink.cs deleted file mode 100644 index e51f7d0828..0000000000 --- a/osu.Game/Online/Chat/ChatLink.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) 2007-2018 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Allocation; -using osu.Framework.Graphics.Cursor; -using osu.Game.Graphics.Sprites; -using osu.Game.Overlays; -using System; - -namespace osu.Game.Online.Chat -{ - public class ChatLink : OsuSpriteLink, IHasTooltip - { - private BeatmapSetOverlay beatmapSetOverlay; - private ChatOverlay chat; - private OsuGame game; - - /// - /// The type of action executed on clicking this link. - /// - public LinkAction LinkAction { get; set; } - - /// - /// The argument necessary for the action specified by to execute. - /// Usually a part of the URL. - /// - public string LinkArgument { get; set; } - - protected override void OnLinkClicked() - { - switch (LinkAction) - { - case LinkAction.OpenBeatmap: - // todo: implement this when overlay.ShowBeatmap(id) exists - break; - case LinkAction.OpenBeatmapSet: - if (int.TryParse(LinkArgument, out int setId)) - beatmapSetOverlay.ShowBeatmapSet(setId); - break; - case LinkAction.OpenChannel: - chat.OpenChannel(chat.AvailableChannels.Find(c => c.Name == LinkArgument)); - break; - case LinkAction.OpenEditorTimestamp: - game?.LoadEditorTimestamp(); - break; - case LinkAction.JoinMultiplayerMatch: - if (int.TryParse(LinkArgument, out int matchId)) - game?.JoinMultiplayerMatch(matchId); - break; - case LinkAction.Spectate: - // todo: implement this when spectating exists - break; - case LinkAction.External: - base.OnLinkClicked(); - break; - default: - throw new NotImplementedException($"This {nameof(Chat.LinkAction)} ({LinkAction.ToString()}) is missing an associated action."); - } - } - - public string TooltipText - { - get - { - if (Url == Text) - return null; - - switch (LinkAction) - { - case LinkAction.OpenChannel: - return "Switch to channel " + LinkArgument; - case LinkAction.OpenEditorTimestamp: - return "Go to " + LinkArgument; - default: - return Url; - } - } - } - - [BackgroundDependencyLoader(true)] - private void load(BeatmapSetOverlay beatmapSetOverlay, ChatOverlay chat, OsuGame game) - { - this.beatmapSetOverlay = beatmapSetOverlay; - this.chat = chat; - // this will be null in tests - this.game = game; - } - } -} diff --git a/osu.Game/Online/Chat/DrawableLinkCompiler.cs b/osu.Game/Online/Chat/DrawableLinkCompiler.cs new file mode 100644 index 0000000000..f1249031c1 --- /dev/null +++ b/osu.Game/Online/Chat/DrawableLinkCompiler.cs @@ -0,0 +1,43 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics.Cursor; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using OpenTK; + +namespace osu.Game.Online.Chat +{ + /// + /// An invisible drawable that brings multiple pieces together to form a consumable clickable link. + /// + public class DrawableLinkCompiler : OsuHoverContainer, IHasTooltip + { + /// + /// Each word part of a chat link (split for word-wrap support). + /// + public List Parts; + + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => Parts.Any(d => d.ReceiveMouseInputAt(screenSpacePos)); + + public DrawableLinkCompiler(IEnumerable parts) + { + Parts = parts.ToList(); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + IdleColour = colours.Blue; + } + + protected override IEnumerable EffectTargets => Parts; + + public string TooltipText { get; set; } + } +} diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index caab77609d..6f847d6f01 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -83,9 +83,9 @@ namespace osu.Game.Overlays.Chat private Message message; private OsuSpriteText username; - private ChatFlowContainer contentFlow; + private LinkFlowContainer contentFlow; - public ChatFlowContainer ContentFlow => contentFlow; + public LinkFlowContainer ContentFlow => contentFlow; public Message Message { @@ -191,14 +191,14 @@ namespace osu.Game.Overlays.Chat Padding = new MarginPadding { Left = message_padding + padding }, Children = new Drawable[] { - contentFlow = new ChatFlowContainer(t => + contentFlow = new LinkFlowContainer(t => { if (Message.IsAction) { t.Font = @"Exo2.0-MediumItalic"; if (senderHasBackground) - t.TextColour = OsuColour.FromHex(message.Sender.Colour); + t.Colour = OsuColour.FromHex(message.Sender.Colour); } t.TextSize = text_size; @@ -252,7 +252,7 @@ namespace osu.Game.Overlays.Chat } } - contentFlow.AddLink(message.Content.Substring(link.Index, link.Length), link.Url, link.Action, link.Argument); + contentFlow.AddLink(message.Content.Substring(link.Index, link.Length), link.Url); } var lastLink = message.Links[message.Links.Count - 1]; diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 35f5e2cd73..25dd56ffdf 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -2,6 +2,7 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Diagnostics; using OpenTK; using OpenTK.Graphics; using osu.Framework.Allocation; @@ -9,6 +10,7 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; 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; @@ -16,7 +18,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Users; -using osu.Framework.Graphics.Cursor; namespace osu.Game.Overlays.Profile { @@ -435,6 +436,28 @@ namespace osu.Game.Overlays.Profile infoTextRight.NewLine(); } + private class ProfileLink : OsuHoverContainer, IHasTooltip + { + public string TooltipText => "View Profile in Browser"; + + public override bool HandleInput => true; + + public ProfileLink(User user) + { + Action = () => Process.Start($@"https://osu.ppy.sh/users/{user.Id}"); + + AutoSizeAxes = Axes.Both; + + Child = new OsuSpriteText + { + Text = user.Username, + Font = @"Exo2.0-RegularItalic", + TextSize = 30, + }; + } + } + + private class GradeBadge : Container { private const float width = 50; @@ -472,34 +495,5 @@ namespace osu.Game.Overlays.Profile badge.Texture = textures.Get($"Grades/{grade}"); } } - - private class LinkFlowContainer : OsuTextFlowContainer - { - public override bool HandleInput => true; - - public LinkFlowContainer(Action defaultCreationParameters = null) - : base(defaultCreationParameters) - { - } - - protected override SpriteText CreateSpriteText() => new OsuSpriteLink(); - - public void AddLink(string text, string url) => AddText(text, sprite => ((OsuSpriteLink)sprite).Url = url); - } - - private class ProfileLink : OsuSpriteLink, IHasTooltip - { - public string TooltipText => "View Profile in Browser"; - - public override bool HandleInput => true; - - public ProfileLink(User user) - { - Text = user.Username; - Url = $@"https://osu.ppy.sh/users/{user.Id}"; - Font = @"Exo2.0-RegularItalic"; - TextSize = 30; - } - } } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 2e97545c72..d7096a5691 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -267,12 +267,11 @@ - + - @@ -295,7 +294,7 @@ - +