From 01bea3bada24b6540631dfe7eadb695c93dc33a6 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Fri, 1 Dec 2017 10:56:48 +0100 Subject: [PATCH 001/138] Re-implemented message formatting (mostly taken from osu-stable code) --- osu.Game/Online/Chat/MessageFormatter.cs | 169 +++++++++++++++++++++++ 1 file changed, 169 insertions(+) create mode 100644 osu.Game/Online/Chat/MessageFormatter.cs diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs new file mode 100644 index 0000000000..df77b5d058 --- /dev/null +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -0,0 +1,169 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace osu.Game.Online.Chat +{ + public static class MessageFormatter + { + // [[Performance Points]] -> wiki:Performance Points (https://osu.ppy.sh/wiki/Performance_Points) + private static Regex wikiRegex = new Regex(@"\[\[([^\]]+)\]\]"); + + // (test)[https://osu.ppy.sh/b/1234] -> test (https://osu.ppy.sh/b/1234) + private static Regex oldLinkRegex = new Regex(@"\(([^\)]*)\)\[([a-z]+://[^ ]+)\]"); + + // [https://osu.ppy.sh/b/1234 Beatmap [Hard] (poop)] -> Beatmap [hard] (poop) (https://osu.ppy.sh/b/1234) + private static Regex newLinkRegex = new Regex(@"\[([a-z]+://[^ ]+) ([^\[\]]*(((?\[)[^\[\]]*)+((?\])[^\[\]]*)+)*(?(open)(?!)))\]"); + + // advanced, RFC-compatible regular expression that matches any possible URL, *but* allows certain invalid characters that are widely used + // This is in the format (, [optional]): + // http[s]://.[:port][/path][?query][#fragment] + private static Regex advancedLinkRegex = new Regex(@"(?\([^)]*)?" + + @"(?https?:\/\/" + + @"(?(?:[a-z0-9]\.|[a-z0-9][a-z0-9-]*[a-z0-9]\.)*[a-z][a-z0-9-]*[a-z0-9]" + // domain, TLD + @"(?::\d+)?)" + // port + @"(?(?:(?:\/+(?:[a-z0-9$_\.\+!\*\',;:\(\)@&~=-]|%[0-9a-f]{2})*)*" + // path + @"(?:\?(?:[a-z0-9$_\+!\*\',;:\(\)@&=\/~-]|%[0-9a-f]{2})*)?)?" + // query + @"(?:#(?:[a-z0-9$_\+!\*\',;:\(\)@&=\/~-]|%[0-9a-f]{2})*)?)?)", // fragment + RegexOptions.IgnoreCase); + + // 00:00:000 (1,2,3) - test + private static Regex timeRegex = new Regex(@"\d\d:\d\d:\d\d\d? [^-]*"); + + // #osu + private static Regex channelRegex = new Regex(@"#[a-zA-Z]+[a-zA-Z0-9]+"); + + // Unicode emojis + private static Regex emojiRegex = new Regex(@"(\uD83D[\uDC00-\uDE4F])"); + + private static void handleMatches(Regex regex, string display, string link, MessageFormatterResult result, int startIndex = 0) + { + int captureOffset = 0; + foreach (Match m in regex.Matches(result.Text, startIndex)) + { + var index = m.Index - captureOffset; + + var displayText = string.Format(display, + m.Groups[0], + m.Groups.Count > 1 ? m.Groups[1].Value : "", + m.Groups.Count > 2 ? m.Groups[2].Value : "").Trim(); + + var linkText = string.Format(link, + m.Groups[0], + m.Groups.Count > 1 ? m.Groups[1].Value : "", + m.Groups.Count > 2 ? m.Groups[2].Value : "").Trim(); + + var testText = string.Format(link, m.Groups).Trim(); + + if (displayText.Length == 0 || linkText.Length == 0) continue; + + // Check for encapsulated links + if (result.Links.Find(l => (l.Index <= index && l.Index + l.Length >= index + m.Length) || index <= l.Index && index + m.Length >= l.Index + l.Length) == null) + { + result.Text = result.Text.Remove(index, m.Length).Insert(index, displayText); + + //since we just changed the line display text, offset any already processed links. + result.Links.ForEach(l => l.Index -= l.Index > index ? m.Length - displayText.Length : 0); + + result.Links.Add(new Link(linkText, index, displayText.Length)); + + //adjust the offset for processing the current matches group. + captureOffset += (m.Length - displayText.Length); + } + } + } + + private static void handleAdvanced(Regex regex, MessageFormatterResult result, int startIndex = 0) + { + foreach (Match m in regex.Matches(result.Text, startIndex)) + { + var index = m.Index; + var prefix = m.Groups["paren"].Value; + var link = m.Groups["link"].Value; + var indexLength = link.Length; + + if (!String.IsNullOrEmpty(prefix)) + { + index += prefix.Length; + if (link.EndsWith(")")) + { + indexLength = indexLength - 1; + link = link.Remove(link.Length - 1); + } + } + + result.Links.Add(new Link(link, index, indexLength)); + } + } + + private static MessageFormatterResult format(string toFormat, int startIndex = 0, int space = 3) + { + var result = new MessageFormatterResult(toFormat); + + // handle the [link display] format + handleMatches(newLinkRegex, "{2}", "{1}", result, startIndex); + + // handle the ()[] link format + handleMatches(oldLinkRegex, "{1}", "{2}", result, startIndex); + + // handle wiki links + handleMatches(wikiRegex, "wiki:{1}", "https://osu.ppy.sh/wiki/{1}", result, startIndex); + + // handle bare links + handleAdvanced(advancedLinkRegex, result, startIndex); + + // handle editor times + handleMatches(timeRegex, "{0}", "osu://edit/{0}", result, startIndex); + + // handle channels + handleMatches(channelRegex, "{0}", "osu://chan/{0}", result, startIndex); + + var empty = ""; + while (space-- > 0) + empty += "\0"; + + handleMatches(emojiRegex, empty, "{0}", result, startIndex); + + return result; + } + + public static FormattedMessage FormatMessage(Message inputMessage) + { + var result = format(inputMessage.Content); + + FormattedMessage formatted = inputMessage as FormattedMessage; + formatted.Content = result.Text; + formatted.Links = result.Links; + return formatted; + } + + public class MessageFormatterResult + { + public List Links = new List(); + public string Text; + public string OriginalText; + + public MessageFormatterResult(string text) + { + OriginalText = Text = text; + } + } + + public class Link + { + public string Url; + public int Index; + public int Length; + + public Link(string url, int startIndex, int length) + { + Url = url; + Index = startIndex; + Length = length; + } + } + } +} From f5f287bed57949df1a429e2f4889e613c27ca353 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Fri, 1 Dec 2017 20:25:02 +0100 Subject: [PATCH 002/138] Rolled back the idea that there should be a separate class for formatted messages --- osu.Game/Online/Chat/Message.cs | 4 ++++ osu.Game/Online/Chat/MessageFormatter.cs | 11 ++++++----- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs index 79b5c4fc1a..355abfda59 100644 --- a/osu.Game/Online/Chat/Message.cs +++ b/osu.Game/Online/Chat/Message.cs @@ -2,6 +2,8 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; +using System.Collections; +using System.Collections.Generic; using System.ComponentModel; using Newtonsoft.Json; using osu.Game.Users; @@ -40,6 +42,8 @@ namespace osu.Game.Online.Chat { } + public List Links; + public Message(long? id) { Id = id; diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index df77b5d058..a712cb1f2b 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -1,4 +1,7 @@ -using System; +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; using System.Collections.Generic; using System.Linq; using System.Text; @@ -56,8 +59,6 @@ namespace osu.Game.Online.Chat m.Groups.Count > 1 ? m.Groups[1].Value : "", m.Groups.Count > 2 ? m.Groups[2].Value : "").Trim(); - var testText = string.Format(link, m.Groups).Trim(); - if (displayText.Length == 0 || linkText.Length == 0) continue; // Check for encapsulated links @@ -130,11 +131,11 @@ namespace osu.Game.Online.Chat return result; } - public static FormattedMessage FormatMessage(Message inputMessage) + public static Message FormatMessage(Message inputMessage) { var result = format(inputMessage.Content); + var formatted = inputMessage; - FormattedMessage formatted = inputMessage as FormattedMessage; formatted.Content = result.Text; formatted.Links = result.Links; return formatted; From 1f1c7dd70fb12bf2b2d80bb8956d4a9935f08537 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Fri, 1 Dec 2017 20:26:51 +0100 Subject: [PATCH 003/138] Moved LinkFlowContainer out of ProfileHeader to make it available for other uses too (e.g. chat) and renamed it to LinkTextFlowContainer bc it can contain both links and text, not only one --- .../Containers/OsuLinkTextFlowContainer.cs | 44 +++++++++++++ osu.Game/Overlays/Profile/ProfileHeader.cs | 61 +++---------------- 2 files changed, 54 insertions(+), 51 deletions(-) create mode 100644 osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs diff --git a/osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs b/osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs new file mode 100644 index 0000000000..b5490d42f9 --- /dev/null +++ b/osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs @@ -0,0 +1,44 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input; +using osu.Game.Graphics.Sprites; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace osu.Game.Graphics.Containers +{ + public class OsuLinkTextFlowContainer : OsuLinkTextFlowContainer + { + public OsuLinkTextFlowContainer(Action defaultCreationParameters = null) + : base(defaultCreationParameters) + { + } + } + + public class OsuLinkTextFlowContainer : OsuTextFlowContainer + where T : OsuLinkSpriteText, new() + { + public override bool HandleInput => true; + + public OsuLinkTextFlowContainer(Action defaultCreationParameters = null) : base(defaultCreationParameters) + { + } + + protected override SpriteText CreateSpriteText() => new T(); + + public void AddLink(string text, string url, Action creationParameters = null) + { + AddText(text, link => + { + ((T)link).Url = url; + creationParameters?.Invoke(link); + }); + } + } +} diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index c7bc5c1d93..af3e97db27 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.Profile public class ProfileHeader : Container { private readonly OsuTextFlowContainer infoTextLeft; - private readonly LinkFlowContainer infoTextRight; + private readonly OsuLinkTextFlowContainer infoTextRight; private readonly FillFlowContainer scoreText, scoreNumberText; private readonly Container coverContainer, chartContainer, supporterTag; @@ -119,7 +119,7 @@ namespace osu.Game.Overlays.Profile } } }, - new LinkFlowContainer.ProfileLink(user) + new ProfileLink(user) { Anchor = Anchor.BottomLeft, Origin = Anchor.BottomLeft, @@ -160,7 +160,7 @@ namespace osu.Game.Overlays.Profile ParagraphSpacing = 0.8f, LineSpacing = 0.2f }, - infoTextRight = new LinkFlowContainer(t => + infoTextRight = new OsuLinkTextFlowContainer(t => { t.TextSize = 14; t.Font = @"Exo2.0-RegularItalic"; @@ -488,57 +488,16 @@ namespace osu.Game.Overlays.Profile } } - private class LinkFlowContainer : OsuTextFlowContainer + private class ProfileLink : OsuLinkSpriteText, IHasTooltip { - public override bool HandleInput => true; + public string TooltipText => "View Profile in Browser"; - public LinkFlowContainer(Action defaultCreationParameters = null) : base(defaultCreationParameters) + public ProfileLink(User user) { - } - - protected override SpriteText CreateSpriteText() => new LinkText(); - - public void AddLink(string text, string url) => AddText(text, link => ((LinkText)link).Url = url); - - public class LinkText : OsuSpriteText - { - private readonly OsuHoverContainer content; - - public override bool HandleInput => content.Action != null; - - protected override Container Content => content ?? (Container)this; - - protected override IEnumerable FlowingChildren => Children; - - public string Url - { - set - { - if(value != null) - content.Action = () => Process.Start(value); - } - } - - public LinkText() - { - AddInternal(content = new OsuHoverContainer - { - AutoSizeAxes = Axes.Both, - }); - } - } - - public class ProfileLink : LinkText, IHasTooltip - { - public string TooltipText => "View Profile in Browser"; - - public ProfileLink(User user) - { - Text = user.Username; - Url = $@"https://osu.ppy.sh/users/{user.Id}"; - Font = @"Exo2.0-RegularItalic"; - TextSize = 30; - } + Text = user.Username; + Url = $@"https://osu.ppy.sh/users/{user.Id}"; + Font = @"Exo2.0-RegularItalic"; + TextSize = 30; } } } From 86302716a6047f69d3e793592ae4c000be57e46e Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Fri, 1 Dec 2017 20:32:08 +0100 Subject: [PATCH 004/138] Also moved LinkText to its own file so the chat could reuse it (ProfileHeader's private class ProfileLink also still inherits from this, though) --- .../Graphics/Sprites/OsuLinkSpriteText.cs | 55 +++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs diff --git a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs new file mode 100644 index 0000000000..111ba6a49a --- /dev/null +++ b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs @@ -0,0 +1,55 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Game.Graphics.Containers; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace osu.Game.Graphics.Sprites +{ + public class OsuLinkSpriteText : OsuSpriteText + { + private readonly OsuHoverContainer content; + + public override bool HandleInput => content.Action != null; + + protected override Container Content => content ?? (Container)this; + + protected override IEnumerable FlowingChildren => Children; + + private string url; + + public string Url + { + get + { + return url; + } + set + { + if (value != null) + { + url = value; + content.Action = () => Process.Start(value); + } + } + } + + public OsuLinkSpriteText() + { + AddInternal(content = new OsuHoverContainer + { + AutoSizeAxes = Axes.Both, + }); + } + } +} From 152eb83c42c4abc3f6608830e8f0514fbaaa0e0d Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Fri, 1 Dec 2017 20:33:27 +0100 Subject: [PATCH 005/138] Added new class for chat lines, that colour the messages after formatting. URLs will become blue, and on hover (also defined here) be turned yellow-ish --- osu.Game/Online/Chat/ChatLinkSpriteText.cs | 63 ++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 osu.Game/Online/Chat/ChatLinkSpriteText.cs diff --git a/osu.Game/Online/Chat/ChatLinkSpriteText.cs b/osu.Game/Online/Chat/ChatLinkSpriteText.cs new file mode 100644 index 0000000000..a47ee7cbb3 --- /dev/null +++ b/osu.Game/Online/Chat/ChatLinkSpriteText.cs @@ -0,0 +1,63 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace osu.Game.Online.Chat +{ + public class ChatLinkSpriteText : OsuLinkSpriteText + { + private Color4 hoverColour; + private Color4 urlColour; + + protected override bool OnHover(InputState state) + { + var otherSpritesWithSameLink = ((Container)Parent).Children.Where(child => (child as OsuLinkSpriteText)?.Url == Url && !Equals(child)); + + var hoverResult = base.OnHover(state); + + if (!otherSpritesWithSameLink.Any(sprite => sprite.IsHovered)) + foreach (ChatLinkSpriteText sprite in otherSpritesWithSameLink) + sprite.TriggerOnHover(state); + + Content.FadeColour(hoverColour, 500, Easing.OutQuint); + + return hoverResult; + } + + protected override void OnHoverLost(InputState state) + { + var spritesWithSameLink = ((Container)Parent).Children.Where(child => (child as OsuLinkSpriteText)?.Url == Url); + + if (spritesWithSameLink.Any(sprite => sprite.IsHovered)) + { + // We have to do this so this sprite does not fade its colour back + Content.FadeColour(hoverColour, 500, Easing.OutQuint); + return; + } + + foreach (ChatLinkSpriteText sprite in spritesWithSameLink) + sprite.Content.FadeColour(urlColour, 500, Easing.OutQuint); + + base.OnHoverLost(state); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + hoverColour = colours.Yellow; + urlColour = colours.Blue; + } + } +} From 78ff5d81d3cc9a5f253327f8a61b9bfba108ca29 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Fri, 1 Dec 2017 21:03:41 +0100 Subject: [PATCH 006/138] Fixed casting --- osu.Game/Online/Chat/ChatLinkSpriteText.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Chat/ChatLinkSpriteText.cs b/osu.Game/Online/Chat/ChatLinkSpriteText.cs index a47ee7cbb3..a1856af6b0 100644 --- a/osu.Game/Online/Chat/ChatLinkSpriteText.cs +++ b/osu.Game/Online/Chat/ChatLinkSpriteText.cs @@ -23,7 +23,8 @@ namespace osu.Game.Online.Chat protected override bool OnHover(InputState state) { - var otherSpritesWithSameLink = ((Container)Parent).Children.Where(child => (child as OsuLinkSpriteText)?.Url == Url && !Equals(child)); + // Every word is one sprite in chat (for word wrap) so we need to find all other sprites that display the same link + var otherSpritesWithSameLink = ((Container)Parent).Children.Where(child => (child as OsuLinkSpriteText)?.Url == Url && !Equals(child)); var hoverResult = base.OnHover(state); @@ -38,7 +39,7 @@ namespace osu.Game.Online.Chat protected override void OnHoverLost(InputState state) { - var spritesWithSameLink = ((Container)Parent).Children.Where(child => (child as OsuLinkSpriteText)?.Url == Url); + var spritesWithSameLink = ((Container)Parent).Children.Where(child => (child as OsuLinkSpriteText)?.Url == Url); if (spritesWithSameLink.Any(sprite => sprite.IsHovered)) { From 7f1f886406450b146c415c862343f3a54174a9c4 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Fri, 1 Dec 2017 21:04:24 +0100 Subject: [PATCH 007/138] implemented method formatting into chat. Also added all necessary files to the .csproj --- osu.Game/Overlays/Chat/ChatLine.cs | 42 +++++++++++++++++++++++------- osu.Game/osu.Game.csproj | 4 +++ 2 files changed, 37 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 32f933ff42..0e1d383f2a 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -67,12 +67,13 @@ namespace osu.Game.Overlays.Chat private const float text_size = 20; private Color4 customUsernameColour; + private Color4 urlColour; private OsuSpriteText timestamp; public ChatLine(Message message) { - Message = message; + Message = MessageFormatter.FormatMessage(message); RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -82,7 +83,7 @@ namespace osu.Game.Overlays.Chat private Message message; private OsuSpriteText username; - private OsuTextFlowContainer contentFlow; + private OsuLinkTextFlowContainer contentFlow; public Message Message { @@ -104,6 +105,7 @@ namespace osu.Game.Overlays.Chat private void load(OsuColour colours) { customUsernameColour = colours.ChatBlue; + urlColour = colours.Blue; } private bool senderHasBackground => !string.IsNullOrEmpty(message.Sender.Colour); @@ -187,7 +189,7 @@ namespace osu.Game.Overlays.Chat Padding = new MarginPadding { Left = message_padding + padding }, Children = new Drawable[] { - contentFlow = new OsuTextFlowContainer(t => { t.TextSize = text_size; }) + contentFlow = new OsuLinkTextFlowContainer(t => { t.TextSize = text_size; }) { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, @@ -210,16 +212,38 @@ namespace osu.Game.Overlays.Chat timestamp.Text = $@"{message.Timestamp.LocalDateTime:HH:mm:ss}"; username.Text = $@"{message.Sender.Username}" + (senderHasBackground ? "" : ":"); - if (message.IsAction) + contentFlow.Clear(); + + if (message.Links == null || message.Links.Count == 0) { - contentFlow.Clear(); - contentFlow.AddText("[", sprite => sprite.Padding = new MarginPadding { Right = action_padding }); - contentFlow.AddText(message.Content, sprite => sprite.Font = @"Exo2.0-MediumItalic"); - contentFlow.AddText("]", sprite => sprite.Padding = new MarginPadding { Left = action_padding }); + contentFlow.AddText(message.Content, sprite => + { + if (message.IsAction) + sprite.Font = @"Exo2.0-MediumItalic"; + }); + + return; } else - contentFlow.Text = message.Content; + { + int prevIndex = 0; + foreach (var link in message.Links) + { + contentFlow.AddText(message.Content.Substring(prevIndex, link.Index - prevIndex)); + prevIndex = link.Index + link.Length; + contentFlow.AddLink(message.Content.Substring(link.Index, link.Length), link.Url, sprite => + { + if (message.IsAction) + sprite.Font = @"Exo2.0-MediumItalic"; + sprite.Colour = urlColour; + }); + } + + // Add text after the last link + var lastLink = message.Links[message.Links.Count - 1]; + contentFlow.AddText(message.Content.Substring(lastLink.Index + lastLink.Length)); + } } private class MessageSender : OsuClickableContainer, IHasContextMenu diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index f814cbb3d3..665acd2102 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -268,6 +268,8 @@ + + @@ -286,6 +288,8 @@ + + From ade7311c152caed05ec6904ca4c1622fbe402789 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Fri, 1 Dec 2017 21:31:12 +0100 Subject: [PATCH 008/138] Updated implementation to be based around a "LinkId" (atm the position of the link, anything unique to a link inside its message will be fine), which does not allow matching (OnHover related) between different links --- osu.Game/Online/Chat/ChatLinkSpriteText.cs | 6 ++++-- osu.Game/Overlays/Chat/ChatLine.cs | 2 ++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Chat/ChatLinkSpriteText.cs b/osu.Game/Online/Chat/ChatLinkSpriteText.cs index a1856af6b0..560579a007 100644 --- a/osu.Game/Online/Chat/ChatLinkSpriteText.cs +++ b/osu.Game/Online/Chat/ChatLinkSpriteText.cs @@ -18,13 +18,15 @@ namespace osu.Game.Online.Chat { public class ChatLinkSpriteText : OsuLinkSpriteText { + public int LinkId; + private Color4 hoverColour; private Color4 urlColour; protected override bool OnHover(InputState state) { // Every word is one sprite in chat (for word wrap) so we need to find all other sprites that display the same link - var otherSpritesWithSameLink = ((Container)Parent).Children.Where(child => (child as OsuLinkSpriteText)?.Url == Url && !Equals(child)); + var otherSpritesWithSameLink = ((Container)Parent).Children.Where(child => (child as ChatLinkSpriteText)?.LinkId == LinkId && !Equals(child)); var hoverResult = base.OnHover(state); @@ -39,7 +41,7 @@ namespace osu.Game.Online.Chat protected override void OnHoverLost(InputState state) { - var spritesWithSameLink = ((Container)Parent).Children.Where(child => (child as OsuLinkSpriteText)?.Url == Url); + var spritesWithSameLink = ((Container)Parent).Children.Where(child => (child as ChatLinkSpriteText)?.LinkId == LinkId); if (spritesWithSameLink.Any(sprite => sprite.IsHovered)) { diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 0e1d383f2a..3ebf5cf513 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -237,6 +237,8 @@ namespace osu.Game.Overlays.Chat if (message.IsAction) sprite.Font = @"Exo2.0-MediumItalic"; sprite.Colour = urlColour; + // We want to use something that is unique to every formatted link, so I just use the position of the link + ((ChatLinkSpriteText)sprite).LinkId = prevIndex; }); } From d22a9df1400d8416b9ae173b517b82e7c3a8395a Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Sat, 2 Dec 2017 09:47:20 +0100 Subject: [PATCH 009/138] Added new request for getting the beatmapset from only a difficulty map ID --- .../Online/API/Requests/GetBeatmapRequest.cs | 21 +++++++++++++++++++ osu.Game/Overlays/BeatmapSetOverlay.cs | 14 +++++++++++++ 2 files changed, 35 insertions(+) create mode 100644 osu.Game/Online/API/Requests/GetBeatmapRequest.cs diff --git a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs new file mode 100644 index 0000000000..7a72402c1e --- /dev/null +++ b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs @@ -0,0 +1,21 @@ +using osu.Game.Beatmaps; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace osu.Game.Online.API.Requests +{ + public class GetBeatmapRequest : APIRequest + { + private readonly int beatmapId; + + public GetBeatmapRequest(int beatmapId) + { + this.beatmapId = beatmapId; + } + + protected override string Target => $@"beatmaps/{beatmapId}"; + } +} diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 0d658b346e..17fbe907ca 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -139,6 +139,20 @@ namespace osu.Game.Overlays return true; } + public void ShowBeatmap(int beatmapId) + { + var req = new GetBeatmapRequest(beatmapId); + req.Success += res => + { + if (!res.OnlineBeatmapSetID.HasValue) + return; + + ShowBeatmapSet(res.OnlineBeatmapSetID.Value); + }; + + api.Queue(req); + } + public void ShowBeatmapSet(int beatmapSetId) { // todo: display the overlay while we are loading here. we need to support setting BeatmapSet to null for this to work. From 0aced859087138736fa1606ad8c6af53f4dcb8b4 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Sat, 2 Dec 2017 09:47:59 +0100 Subject: [PATCH 010/138] Changed the LinkID to the link's own Index instead of the previous one (just makes more sense imo) --- osu.Game/Overlays/Chat/ChatLine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 3ebf5cf513..bf5855c45b 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -238,7 +238,7 @@ namespace osu.Game.Overlays.Chat sprite.Font = @"Exo2.0-MediumItalic"; sprite.Colour = urlColour; // We want to use something that is unique to every formatted link, so I just use the position of the link - ((ChatLinkSpriteText)sprite).LinkId = prevIndex; + ((ChatLinkSpriteText)sprite).LinkId = link.Index; }); } From 6d9dcc66913d1118b5070ce0b973d95e78bbea79 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Sat, 2 Dec 2017 09:48:55 +0100 Subject: [PATCH 011/138] Added all files to the .csproj and also introduced basic action filtering when you set the URL on an OsuLinkSpriteText object --- .../Graphics/Sprites/OsuLinkSpriteText.cs | 45 ++++++++++++++++++- osu.Game/osu.Game.csproj | 1 + 2 files changed, 45 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs index 111ba6a49a..f41bca62ec 100644 --- a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Game.Graphics.Containers; +using osu.Game.Overlays; using System; using System.Collections.Generic; using System.Diagnostics; @@ -20,6 +21,8 @@ namespace osu.Game.Graphics.Sprites { private readonly OsuHoverContainer content; + private BeatmapSetOverlay beatmapSetOverlay; + public override bool HandleInput => content.Action != null; protected override Container Content => content ?? (Container)this; @@ -39,7 +42,7 @@ namespace osu.Game.Graphics.Sprites if (value != null) { url = value; - content.Action = () => Process.Start(value); + loadAction(); } } } @@ -51,5 +54,45 @@ namespace osu.Game.Graphics.Sprites AutoSizeAxes = Axes.Both, }); } + + [BackgroundDependencyLoader] + private void load(BeatmapSetOverlay beatmapSetOverlay) + { + this.beatmapSetOverlay = beatmapSetOverlay; + } + + private void loadAction() + { + if (Url == null || String.IsNullOrEmpty(Url)) + return; + + var url = Url; + + if (url.StartsWith("https://")) url = url.Substring(8); + else if (url.StartsWith("http://")) url = url.Substring(7); + else content.Action = () => Process.Start(Url); + + if (url.StartsWith("osu.ppy.sh/")) + { + url = url.Substring(11); + if (url.StartsWith("s") || url.StartsWith("beatmapsets")) + content.Action = () => beatmapSetOverlay.ShowBeatmapSet(getIdFromUrl(url)); + else if (url.StartsWith("b") || url.StartsWith("beatmaps")) + content.Action = () => beatmapSetOverlay.ShowBeatmap(getIdFromUrl(url)); + // else if (url.StartsWith("d")) Maybe later + } + } + + private int getIdFromUrl(string url) + { + var lastSlashIndex = url.LastIndexOf('/'); + if (lastSlashIndex == url.Length) + { + url = url.Substring(url.Length - 1); + lastSlashIndex = url.LastIndexOf('/'); + } + + return int.Parse(url.Substring(lastSlashIndex + 1)); + } } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index a2d8a4bbe4..24ca01a3ad 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -286,6 +286,7 @@ 20171119065731_AddBeatmapOnlineIDUniqueConstraint.cs + From efe6245e533cf514dbb6c715aa148f3f8f5a0e17 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Sat, 2 Dec 2017 10:42:38 +0100 Subject: [PATCH 012/138] Fixed a bug where Drawable.Width could potentially be set to NaN (0/0) by checking if last variable > 0. --- osu.Game/Overlays/BeatmapSet/PreviewButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/PreviewButton.cs b/osu.Game/Overlays/BeatmapSet/PreviewButton.cs index 52edd1714f..ef248c02d3 100644 --- a/osu.Game/Overlays/BeatmapSet/PreviewButton.cs +++ b/osu.Game/Overlays/BeatmapSet/PreviewButton.cs @@ -80,7 +80,7 @@ namespace osu.Game.Overlays.BeatmapSet { base.Update(); - if (Playing.Value && preview != null) + if (Playing.Value && preview != null && preview.Length > 0) { progress.Width = (float)(preview.CurrentTime / preview.Length); } From 7f029a382b5da0b6c10f6d832554686b4a458968 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Sat, 2 Dec 2017 10:43:19 +0100 Subject: [PATCH 013/138] Made the Chat testcase include a beatmapsetoverlay so links can be clicked from in there. Also had to implement private DI to make it work --- osu.Game.Tests/Visual/TestCaseChatDisplay.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game.Tests/Visual/TestCaseChatDisplay.cs b/osu.Game.Tests/Visual/TestCaseChatDisplay.cs index 85ee224a5e..38f5a7cbe0 100644 --- a/osu.Game.Tests/Visual/TestCaseChatDisplay.cs +++ b/osu.Game.Tests/Visual/TestCaseChatDisplay.cs @@ -2,7 +2,9 @@ // 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.Online.API; using osu.Game.Overlays; namespace osu.Game.Tests.Visual @@ -10,12 +12,26 @@ namespace osu.Game.Tests.Visual [Description("Testing chat api and overlay")] internal class TestCaseChatDisplay : OsuTestCase { + private BeatmapSetOverlay beatmapSetOverlay; + + private DependencyContainer dependencies; + + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); + public TestCaseChatDisplay() { Add(new ChatOverlay { State = Visibility.Visible }); + + Add(beatmapSetOverlay = new BeatmapSetOverlay()); + } + + [BackgroundDependencyLoader] + private void load() + { + dependencies.Cache(beatmapSetOverlay); } } } From 735dbddd17ff76cedba34f3fa71b51328de3acc1 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Sun, 3 Dec 2017 06:52:57 +0100 Subject: [PATCH 014/138] Changed URL detection to be more reliable and generally work better --- .../Graphics/Sprites/OsuLinkSpriteText.cs | 31 +++++++++++++------ 1 file changed, 21 insertions(+), 10 deletions(-) diff --git a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs index f41bca62ec..e39131fa1c 100644 --- a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; +using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; using osu.Game.Overlays; using System; @@ -68,30 +69,40 @@ namespace osu.Game.Graphics.Sprites var url = Url; - if (url.StartsWith("https://")) url = url.Substring(8); - else if (url.StartsWith("http://")) url = url.Substring(7); - else content.Action = () => Process.Start(Url); - - if (url.StartsWith("osu.ppy.sh/")) + if (url.StartsWith("http://") || url.StartsWith("https://")) { - url = url.Substring(11); - if (url.StartsWith("s") || url.StartsWith("beatmapsets")) + var osuUrlIndex = url.IndexOf("osu.ppy.sh/"); + if (osuUrlIndex == -1) + { + content.Action = () => Process.Start(url); + return; + } + + url = url.Substring(osuUrlIndex + 11); + if (url.StartsWith("s/") || url.StartsWith("beatmapsets/") || url.StartsWith("d/")) content.Action = () => beatmapSetOverlay.ShowBeatmapSet(getIdFromUrl(url)); - else if (url.StartsWith("b") || url.StartsWith("beatmaps")) + else if (url.StartsWith("b/") || url.StartsWith("beatmaps/")) content.Action = () => beatmapSetOverlay.ShowBeatmap(getIdFromUrl(url)); - // else if (url.StartsWith("d")) Maybe later } + else + content.Action = () => Process.Start(url); } private int getIdFromUrl(string url) { var lastSlashIndex = url.LastIndexOf('/'); + // Remove possible trailing slash if (lastSlashIndex == url.Length) { - url = url.Substring(url.Length - 1); + url = url.Substring(0, url.Length - 1); lastSlashIndex = url.LastIndexOf('/'); } + var lastQuestionMarkIndex = url.LastIndexOf('?'); + // Filter out possible queries like mode specifications (e.g. /b/252238?m=0) + if (lastQuestionMarkIndex > lastSlashIndex) + url = url.Substring(0, lastQuestionMarkIndex); + return int.Parse(url.Substring(lastSlashIndex + 1)); } } From c574cc43087063bdea99f77224591067e074b124 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Sun, 3 Dec 2017 11:16:34 +0100 Subject: [PATCH 015/138] Removed unnecessary "using" statements --- osu.Game/Online/Chat/ChatLinkSpriteText.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Online/Chat/ChatLinkSpriteText.cs b/osu.Game/Online/Chat/ChatLinkSpriteText.cs index 560579a007..4b87894a0c 100644 --- a/osu.Game/Online/Chat/ChatLinkSpriteText.cs +++ b/osu.Game/Online/Chat/ChatLinkSpriteText.cs @@ -8,11 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using System; -using System.Collections.Generic; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace osu.Game.Online.Chat { From 2d270a1cfefcf049adada74d3d280a002668a213 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Sun, 3 Dec 2017 20:45:30 +0100 Subject: [PATCH 016/138] Added ability to open other channels from chat links. --- .../Graphics/Sprites/OsuLinkSpriteText.cs | 31 +++++++++++++++++-- osu.Game/Overlays/ChatOverlay.cs | 5 +++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs index e39131fa1c..2105008ef3 100644 --- a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs @@ -20,6 +20,8 @@ namespace osu.Game.Graphics.Sprites { public class OsuLinkSpriteText : OsuSpriteText { + private ChatOverlay chat; + private readonly OsuHoverContainer content; private BeatmapSetOverlay beatmapSetOverlay; @@ -57,9 +59,10 @@ namespace osu.Game.Graphics.Sprites } [BackgroundDependencyLoader] - private void load(BeatmapSetOverlay beatmapSetOverlay) + private void load(BeatmapSetOverlay beatmapSetOverlay, ChatOverlay chat) { this.beatmapSetOverlay = beatmapSetOverlay; + this.chat = chat; } private void loadAction() @@ -69,7 +72,31 @@ namespace osu.Game.Graphics.Sprites var url = Url; - if (url.StartsWith("http://") || url.StartsWith("https://")) + // Client-internal stuff + if (url.StartsWith("osu://")) + { + var firstPath = url.Substring(6, 5); + url = url.Substring(11); + + if (firstPath == "chan/") + { + var nextSlashIndex = url.IndexOf('/'); + var channelName = url.Substring(0, nextSlashIndex != -1 ? nextSlashIndex - 1 : url.Length - 1); + + var foundChannel = chat.AvailableChannels.Find(channel => channel.Name == channelName); + + if (foundChannel != null) + chat.OpenChannel(foundChannel); + } + else if (firstPath == "edit/") + { + // Open editor here, then goto specified time + // how to push new screen from here? we'll see + } + else + throw new ArgumentException($"Unknown osu:// link at {nameof(OsuLinkSpriteText)} ({firstPath})."); + } + else if (url.StartsWith("http://") || url.StartsWith("https://")) { var osuUrlIndex = url.IndexOf("osu.ppy.sh/"); if (osuUrlIndex == -1) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 9f40a08ad2..9e3e60a988 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -60,6 +60,7 @@ namespace osu.Game.Overlays public Bindable ChatHeight { get; set; } + public List AvailableChannels { get; private set; } private readonly Container channelSelectionContainer; private readonly ChannelSelectionOverlay channelSelection; @@ -190,6 +191,8 @@ namespace osu.Game.Overlays private double startDragChatHeight; private bool isDragging; + public void OpenChannel(Channel channel) => addChannel(channel); + protected override bool OnDragStart(InputState state) { isDragging = tabsArea.IsHovered; @@ -300,6 +303,8 @@ namespace osu.Game.Overlays ListChannelsRequest req = new ListChannelsRequest(); req.Success += delegate (List channels) { + AvailableChannels = channels; + Scheduler.Add(delegate { addChannel(channels.Find(c => c.Name == @"#lazer")); From 3f336b8e61aad47b03bedeaef7045a2166fc8f48 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Sun, 3 Dec 2017 21:02:05 +0100 Subject: [PATCH 017/138] Made the style a bit better, fixed a bug or two --- osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs | 9 +++++---- osu.Game/Overlays/Chat/ChatLine.cs | 2 ++ 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs index 2105008ef3..080f1c1f83 100644 --- a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs @@ -81,17 +81,18 @@ namespace osu.Game.Graphics.Sprites if (firstPath == "chan/") { var nextSlashIndex = url.IndexOf('/'); - var channelName = url.Substring(0, nextSlashIndex != -1 ? nextSlashIndex - 1 : url.Length - 1); + var channelName = nextSlashIndex != -1 ? url.Remove(nextSlashIndex) : url; var foundChannel = chat.AvailableChannels.Find(channel => channel.Name == channelName); if (foundChannel != null) - chat.OpenChannel(foundChannel); + content.Action = () => chat.OpenChannel(foundChannel); } else if (firstPath == "edit/") { // Open editor here, then goto specified time // how to push new screen from here? we'll see + content.Action = () => new Framework.Screens.Screen().Push(new Screens.Edit.Editor()); } else throw new ArgumentException($"Unknown osu:// link at {nameof(OsuLinkSpriteText)} ({firstPath})."); @@ -121,14 +122,14 @@ namespace osu.Game.Graphics.Sprites // Remove possible trailing slash if (lastSlashIndex == url.Length) { - url = url.Substring(0, url.Length - 1); + url = url.Remove(url.Length - 1); lastSlashIndex = url.LastIndexOf('/'); } var lastQuestionMarkIndex = url.LastIndexOf('?'); // Filter out possible queries like mode specifications (e.g. /b/252238?m=0) if (lastQuestionMarkIndex > lastSlashIndex) - url = url.Substring(0, lastQuestionMarkIndex); + url = url.Remove(lastQuestionMarkIndex); return int.Parse(url.Substring(lastSlashIndex + 1)); } diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index bf5855c45b..fda23ca679 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -236,6 +236,8 @@ namespace osu.Game.Overlays.Chat { if (message.IsAction) sprite.Font = @"Exo2.0-MediumItalic"; + + // TODO: Somehow check (if channel link) that this is a real channel sprite.Colour = urlColour; // We want to use something that is unique to every formatted link, so I just use the position of the link ((ChatLinkSpriteText)sprite).LinkId = link.Index; From a839d0e91dbef1616624972174b91baf512c2361 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Sun, 3 Dec 2017 23:55:00 +0100 Subject: [PATCH 018/138] LoadComponent before assigning URL to ensure dependency loading --- osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs b/osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs index b5490d42f9..821d1bd0ce 100644 --- a/osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs @@ -36,7 +36,7 @@ namespace osu.Game.Graphics.Containers { AddText(text, link => { - ((T)link).Url = url; + LoadComponentAsync(link, d => ((T)d).Url = url); creationParameters?.Invoke(link); }); } From bf97f8b1b1edc3715bc428f862fcc621cbf6b228 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Sun, 3 Dec 2017 23:55:34 +0100 Subject: [PATCH 019/138] Added osu.ppy.sh/ss (screenshot) URL handling and calculate ID at assignment so it's not on click anymore --- osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs index 080f1c1f83..2199800d64 100644 --- a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs @@ -107,10 +107,13 @@ namespace osu.Game.Graphics.Sprites } url = url.Substring(osuUrlIndex + 11); + var id = getIdFromUrl(url); if (url.StartsWith("s/") || url.StartsWith("beatmapsets/") || url.StartsWith("d/")) - content.Action = () => beatmapSetOverlay.ShowBeatmapSet(getIdFromUrl(url)); + content.Action = () => beatmapSetOverlay.ShowBeatmapSet(id); else if (url.StartsWith("b/") || url.StartsWith("beatmaps/")) - content.Action = () => beatmapSetOverlay.ShowBeatmap(getIdFromUrl(url)); + content.Action = () => beatmapSetOverlay.ShowBeatmap(id); + else if (url.StartsWith("ss/")) + content.Action = () => Process.Start($"https://osu.ppy.sh/{url}"); } else content.Action = () => Process.Start(url); From dcdc186a53dba35d7992910c0f7a3731ede4b077 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Mon, 4 Dec 2017 13:27:14 +0100 Subject: [PATCH 020/138] Added chatoverlay caching to testcase so test still works (chat needs to be injected so channels can be opened) --- osu.Game.Tests/Visual/TestCaseChatDisplay.cs | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseChatDisplay.cs b/osu.Game.Tests/Visual/TestCaseChatDisplay.cs index 38f5a7cbe0..e23531f46a 100644 --- a/osu.Game.Tests/Visual/TestCaseChatDisplay.cs +++ b/osu.Game.Tests/Visual/TestCaseChatDisplay.cs @@ -13,6 +13,7 @@ namespace osu.Game.Tests.Visual internal class TestCaseChatDisplay : OsuTestCase { private BeatmapSetOverlay beatmapSetOverlay; + private readonly ChatOverlay chat; private DependencyContainer dependencies; @@ -20,10 +21,12 @@ namespace osu.Game.Tests.Visual public TestCaseChatDisplay() { - Add(new ChatOverlay + chat = new ChatOverlay { State = Visibility.Visible - }); + }; + + Add(chat); Add(beatmapSetOverlay = new BeatmapSetOverlay()); } @@ -31,6 +34,7 @@ namespace osu.Game.Tests.Visual [BackgroundDependencyLoader] private void load() { + dependencies.Cache(chat); dependencies.Cache(beatmapSetOverlay); } } From 319f43e209e1d5b872c0e17d5a39b115dd1dbcbc Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Mon, 4 Dec 2017 13:29:56 +0100 Subject: [PATCH 021/138] Added "getIdFromUrl" call back to content.action because performance impact is small and no unnecessary id calculations are done --- osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs index 2199800d64..ddce36c77d 100644 --- a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs @@ -107,12 +107,11 @@ namespace osu.Game.Graphics.Sprites } url = url.Substring(osuUrlIndex + 11); - var id = getIdFromUrl(url); if (url.StartsWith("s/") || url.StartsWith("beatmapsets/") || url.StartsWith("d/")) - content.Action = () => beatmapSetOverlay.ShowBeatmapSet(id); + content.Action = () => beatmapSetOverlay.ShowBeatmapSet(getIdFromUrl(url)); else if (url.StartsWith("b/") || url.StartsWith("beatmaps/")) - content.Action = () => beatmapSetOverlay.ShowBeatmap(id); - else if (url.StartsWith("ss/")) + content.Action = () => beatmapSetOverlay.ShowBeatmap(getIdFromUrl(url)); + else content.Action = () => Process.Start($"https://osu.ppy.sh/{url}"); } else From cf96323980c102881cd5de31259d8402a3d6df30 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Mon, 4 Dec 2017 13:33:42 +0100 Subject: [PATCH 022/138] Added new OsuLinkSpriteText.TextColour property that sets the internal content (OsuHoverContainer)'s colour instead of the whole container, so that text colour is always changed through that (e.g. link colouring, link hover fade). Implemented it to be used when adding text to an OsuLinkTextFlowContainer. --- .../Containers/OsuLinkTextFlowContainer.cs | 19 +++++++++++++++++++ .../Graphics/Sprites/OsuLinkSpriteText.cs | 7 +++++++ osu.Game/Overlays/Chat/ChatLine.cs | 6 ++---- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs b/osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs index 821d1bd0ce..559b3eef17 100644 --- a/osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs @@ -1,6 +1,8 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using OpenTK.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Input; @@ -32,6 +34,12 @@ namespace osu.Game.Graphics.Containers protected override SpriteText CreateSpriteText() => new T(); + /// + /// The colour for normal text (links ignore this). This should be set before text is added. + /// Default is white. + /// + public ColourInfo? TextColour; + public void AddLink(string text, string url, Action creationParameters = null) { AddText(text, link => @@ -40,5 +48,16 @@ namespace osu.Game.Graphics.Containers creationParameters?.Invoke(link); }); } + + public IEnumerable AddText(string text, Action creationParameters = null) + { + return base.AddText(text, sprite => + { + if (TextColour.HasValue) + ((OsuLinkSpriteText)sprite).TextColour = TextColour.Value; + + creationParameters?.Invoke(sprite); + }); + } } } diff --git a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs index ddce36c77d..3727b79322 100644 --- a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs @@ -4,6 +4,7 @@ using OpenTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Game.Beatmaps; @@ -58,6 +59,12 @@ namespace osu.Game.Graphics.Sprites }); } + public ColourInfo TextColour + { + get { return Content.Colour; } + set { Content.Colour = value; } + } + [BackgroundDependencyLoader] private void load(BeatmapSetOverlay beatmapSetOverlay, ChatOverlay chat) { diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index fda23ca679..b6da2fbbce 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -198,7 +198,7 @@ namespace osu.Game.Overlays.Chat } }; if (message.IsAction && senderHasBackground) - contentFlow.Colour = OsuColour.FromHex(message.Sender.Colour); + contentFlow.TextColour = OsuColour.FromHex(message.Sender.Colour); updateMessageContent(); FinishTransforms(true); @@ -237,9 +237,7 @@ namespace osu.Game.Overlays.Chat if (message.IsAction) sprite.Font = @"Exo2.0-MediumItalic"; - // TODO: Somehow check (if channel link) that this is a real channel - sprite.Colour = urlColour; - // We want to use something that is unique to every formatted link, so I just use the position of the link + // We want to use something that is unique to every formatted link PER MESSAGE ((ChatLinkSpriteText)sprite).LinkId = link.Index; }); } From fd13bacf4ad9a4ac32ee29701e2faee32e4f7a26 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Mon, 4 Dec 2017 13:34:16 +0100 Subject: [PATCH 023/138] Made default link ID -1 (if no link is present) because linkId is currently being set to link.index which can be 0. --- osu.Game/Online/Chat/ChatLinkSpriteText.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/ChatLinkSpriteText.cs b/osu.Game/Online/Chat/ChatLinkSpriteText.cs index 4b87894a0c..adbdd9ed0c 100644 --- a/osu.Game/Online/Chat/ChatLinkSpriteText.cs +++ b/osu.Game/Online/Chat/ChatLinkSpriteText.cs @@ -14,7 +14,7 @@ namespace osu.Game.Online.Chat { public class ChatLinkSpriteText : OsuLinkSpriteText { - public int LinkId; + public int LinkId = -1; private Color4 hoverColour; private Color4 urlColour; @@ -57,6 +57,8 @@ namespace osu.Game.Online.Chat { hoverColour = colours.Yellow; urlColour = colours.Blue; + if (LinkId != -1) + Content.Colour = urlColour; } } } From 9b866d2248e197f8d5279a70fb6727eb302b3bfc Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Mon, 4 Dec 2017 13:46:07 +0100 Subject: [PATCH 024/138] Made it so the link ID is always added before loading the SpriteTexts (fixed weird bug where some sprites would be white instead of blue). Also improved XML doc on TextColour --- osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs b/osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs index 559b3eef17..ebb4cca0e0 100644 --- a/osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs @@ -35,7 +35,7 @@ namespace osu.Game.Graphics.Containers protected override SpriteText CreateSpriteText() => new T(); /// - /// The colour for normal text (links ignore this). This should be set before text is added. + /// The colour for normal text (links ignore this). Will only be used for new text elements. /// Default is white. /// public ColourInfo? TextColour; @@ -44,8 +44,8 @@ namespace osu.Game.Graphics.Containers { AddText(text, link => { - LoadComponentAsync(link, d => ((T)d).Url = url); creationParameters?.Invoke(link); + LoadComponentAsync(link, d => ((T)d).Url = url); }); } From bb138ccaf710f17658ced90490ba65e430941297 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Mon, 4 Dec 2017 13:55:57 +0100 Subject: [PATCH 025/138] Added licence header and removed unnecessary whitespace --- osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs | 2 +- osu.Game/Online/API/Requests/GetBeatmapRequest.cs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs index 3727b79322..17f7cf88d5 100644 --- a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs @@ -118,7 +118,7 @@ namespace osu.Game.Graphics.Sprites content.Action = () => beatmapSetOverlay.ShowBeatmapSet(getIdFromUrl(url)); else if (url.StartsWith("b/") || url.StartsWith("beatmaps/")) content.Action = () => beatmapSetOverlay.ShowBeatmap(getIdFromUrl(url)); - else + else content.Action = () => Process.Start($"https://osu.ppy.sh/{url}"); } else diff --git a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs index 7a72402c1e..1df695c795 100644 --- a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs +++ b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs @@ -1,4 +1,7 @@ -using osu.Game.Beatmaps; +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Beatmaps; using System; using System.Collections.Generic; using System.Linq; From 63a6a8b669fdbb2d75fc61ae2599e1b9cef807a8 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Mon, 4 Dec 2017 19:31:48 +0100 Subject: [PATCH 026/138] Fixed messages sent by yourself not being formatted --- osu.Game/Overlays/Chat/ChatLine.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index b6da2fbbce..5703c437e6 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -73,7 +73,7 @@ namespace osu.Game.Overlays.Chat public ChatLine(Message message) { - Message = MessageFormatter.FormatMessage(message); + Message = message; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; @@ -92,7 +92,7 @@ namespace osu.Game.Overlays.Chat { if (message == value) return; - message = value; + message = MessageFormatter.FormatMessage(value); if (!IsLoaded) return; From b04ddba2a0b6062b90e5bc45dc7eb52a075d2c20 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Wed, 6 Dec 2017 10:27:30 +0100 Subject: [PATCH 027/138] Added basic "on click" actions to the in-chat links --- .../Graphics/Sprites/OsuLinkSpriteText.cs | 102 ++++++++++++------ .../Online/API/Requests/GetUserRequest.cs | 11 +- 2 files changed, 82 insertions(+), 31 deletions(-) diff --git a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs index 17f7cf88d5..da11bc5043 100644 --- a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs @@ -7,9 +7,13 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Input; +using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; using osu.Game.Overlays; +using osu.Game.Screens.Edit; using System; using System.Collections.Generic; using System.Diagnostics; @@ -25,6 +29,7 @@ namespace osu.Game.Graphics.Sprites private readonly OsuHoverContainer content; + private APIAccess api; private BeatmapSetOverlay beatmapSetOverlay; public override bool HandleInput => content.Action != null; @@ -43,10 +48,10 @@ namespace osu.Game.Graphics.Sprites } set { - if (value != null) + if (!string.IsNullOrEmpty(value)) { url = value; - loadAction(); + content.Action = onClickAction; } } } @@ -66,63 +71,100 @@ namespace osu.Game.Graphics.Sprites } [BackgroundDependencyLoader] - private void load(BeatmapSetOverlay beatmapSetOverlay, ChatOverlay chat) + private void load(APIAccess api, BeatmapSetOverlay beatmapSetOverlay, ChatOverlay chat) { + this.api = api; this.beatmapSetOverlay = beatmapSetOverlay; this.chat = chat; } - private void loadAction() + private void onClickAction() { - if (Url == null || String.IsNullOrEmpty(Url)) - return; - var url = Url; - // Client-internal stuff if (url.StartsWith("osu://")) { - var firstPath = url.Substring(6, 5); - url = url.Substring(11); + url = url.Substring(6); + var args = url.Split('/'); - if (firstPath == "chan/") + switch (args[0]) { - var nextSlashIndex = url.IndexOf('/'); - var channelName = nextSlashIndex != -1 ? url.Remove(nextSlashIndex) : url; + case "chan": + var foundChannel = chat.AvailableChannels.Find(channel => channel.Name == args[1]); - var foundChannel = chat.AvailableChannels.Find(channel => channel.Name == channelName); + if (foundChannel == null) + TextColour = Color4.White; + else + chat.OpenChannel(foundChannel); - if (foundChannel != null) - content.Action = () => chat.OpenChannel(foundChannel); - } - else if (firstPath == "edit/") - { - // Open editor here, then goto specified time - // how to push new screen from here? we'll see - content.Action = () => new Framework.Screens.Screen().Push(new Screens.Edit.Editor()); - } - else - throw new ArgumentException($"Unknown osu:// link at {nameof(OsuLinkSpriteText)} ({firstPath})."); + break; + case "edit": + // TODO: Change screen to editor + break; + case "b": + if (args.Length > 1 && int.TryParse(args[1], out int mapId)) + beatmapSetOverlay.ShowBeatmap(mapId); + + break; + case "s": + case "dl": + if (args.Length > 1 && int.TryParse(args[1], out int mapSetId)) + beatmapSetOverlay.ShowBeatmapSet(mapSetId); + + break; + case "spectate": + GetUserRequest req; + if (int.TryParse(args[1], out int userId)) + req = new GetUserRequest(userId); + else + // Get by username instead + req = new GetUserRequest(args[1]); + + req.Success += user => + { + // TODO: Open spectator screen and start spectating + + }; + // api.Queue(req); + + break; + default: + throw new ArgumentException($"Unknown osu:// link at {nameof(OsuLinkSpriteText)} (https://osu.ppy.sh/{args[0]})."); + } + } + else if (url.StartsWith("osump://")) + { + url = url.Substring(8); + if (!int.TryParse(url.Split('/').ElementAtOrDefault(1), out int multiId)) + return; + + // TODO: Join the specified multiplayer lobby here with multiId } else if (url.StartsWith("http://") || url.StartsWith("https://")) { var osuUrlIndex = url.IndexOf("osu.ppy.sh/"); if (osuUrlIndex == -1) { - content.Action = () => Process.Start(url); + Process.Start(url); return; } url = url.Substring(osuUrlIndex + 11); if (url.StartsWith("s/") || url.StartsWith("beatmapsets/") || url.StartsWith("d/")) - content.Action = () => beatmapSetOverlay.ShowBeatmapSet(getIdFromUrl(url)); + { + var id = getIdFromUrl(url); + beatmapSetOverlay.ShowBeatmapSet(id); + } else if (url.StartsWith("b/") || url.StartsWith("beatmaps/")) - content.Action = () => beatmapSetOverlay.ShowBeatmap(getIdFromUrl(url)); + { + var id = getIdFromUrl(url); + beatmapSetOverlay.ShowBeatmap(id); + } else - content.Action = () => Process.Start($"https://osu.ppy.sh/{url}"); + Process.Start($"https://osu.ppy.sh/{url}"); } else - content.Action = () => Process.Start(url); + Process.Start(url); } private int getIdFromUrl(string url) diff --git a/osu.Game/Online/API/Requests/GetUserRequest.cs b/osu.Game/Online/API/Requests/GetUserRequest.cs index 2e3e7b01c8..47038b8ccb 100644 --- a/osu.Game/Online/API/Requests/GetUserRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRequest.cs @@ -8,12 +8,21 @@ namespace osu.Game.Online.API.Requests public class GetUserRequest : APIRequest { private long? userId; + private string userName; + /// The user's ID. public GetUserRequest(long? userId = null) { this.userId = userId; } - protected override string Target => userId.HasValue ? $@"users/{userId}" : @"me"; + /// The user's username. + public GetUserRequest(string userName) + { + this.userName = userName; + } + + // Prefer ID over name + protected override string Target => userId.HasValue ? $@"users/{userId}" : ((!string.IsNullOrEmpty(userName)) ? $@"users/{userName}" : @"me"); } } From 94eb853d3d14d69155709f13c105adcf0fed669a Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Wed, 6 Dec 2017 16:41:57 +0100 Subject: [PATCH 028/138] Added centralised handling for some chat links --- .../Graphics/Sprites/OsuLinkSpriteText.cs | 5 ++-- osu.Game/OsuGame.cs | 28 +++++++++++++++++++ osu.Game/Overlays/ChatOverlay.cs | 5 +++- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs index da11bc5043..75b3f281d0 100644 --- a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs @@ -99,7 +99,7 @@ namespace osu.Game.Graphics.Sprites break; case "edit": - // TODO: Change screen to editor + chat.Game.LoadEditorTimestamp(); break; case "b": if (args.Length > 1 && int.TryParse(args[1], out int mapId)) @@ -126,6 +126,7 @@ namespace osu.Game.Graphics.Sprites }; // api.Queue(req); + chat.Game.LoadSpectatorScreen(); break; default: @@ -138,7 +139,7 @@ namespace osu.Game.Graphics.Sprites if (!int.TryParse(url.Split('/').ElementAtOrDefault(1), out int multiId)) return; - // TODO: Join the specified multiplayer lobby here with multiId + chat.Game.LoadMultiplayerLobby(); } else if (url.StartsWith("http://") || url.StartsWith("https://")) { diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 4745733bd9..900a55293d 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -115,6 +115,34 @@ namespace osu.Game private ScheduledDelegate scoreLoad; + // TODO: Implement this properly as soon as the Editor is done + internal void LoadEditorTimestamp() + { + notificationOverlay.Post(new SimpleNotification + { + Text = @"Sorry, but this is not fully implemented yet!", + Icon = FontAwesome.fa_life_saver, + }); + } + + internal void LoadSpectatorScreen() + { + notificationOverlay.Post(new SimpleNotification + { + Text = @"Sorry, but spectating is not implemented yet!", + Icon = FontAwesome.fa_life_saver, + }); + } + + internal void LoadMultiplayerLobby() + { + notificationOverlay.Post(new SimpleNotification + { + Text = @"Sorry, but the multiplayer lobby is not implemented yet!", + Icon = FontAwesome.fa_life_saver, + }); + } + protected void LoadScore(Score s) { scoreLoad?.Cancel(); diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 9e3e60a988..eaf58cde5e 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -41,6 +41,8 @@ namespace osu.Game.Overlays private readonly FocusedTextBox textbox; + public OsuGame Game; + private APIAccess api; private const int transition_length = 500; @@ -271,8 +273,9 @@ namespace osu.Game.Overlays } [BackgroundDependencyLoader] - private void load(APIAccess api, OsuConfigManager config, OsuColour colours) + private void load(APIAccess api, OsuConfigManager config, OsuColour colours, OsuGame game) { + Game = game; this.api = api; api.Register(this); From 7bf25bdd4dfdfe0980b492ec8486d2b6ed0d2337 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 10:23:57 +0100 Subject: [PATCH 029/138] Changed it so the ":" character does not appear after usernames when the displayed message is an action (e.g. /np). Just makes more sense imo --- osu.Game/Overlays/Chat/ChatLine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 5703c437e6..ba88a9ac77 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -210,7 +210,7 @@ namespace osu.Game.Overlays.Chat timestamp.FadeTo(message is LocalEchoMessage ? 0 : 1, 500, Easing.OutQuint); timestamp.Text = $@"{message.Timestamp.LocalDateTime:HH:mm:ss}"; - username.Text = $@"{message.Sender.Username}" + (senderHasBackground ? "" : ":"); + username.Text = $@"{message.Sender.Username}" + (senderHasBackground || message.IsAction ? "" : ":"); contentFlow.Clear(); From 541c25e99538c458c2f772decc077ff18fad0c04 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 10:28:12 +0100 Subject: [PATCH 030/138] Renamed ChatLinkSpriteText to ChatLink for convenience --- .../Online/Chat/{ChatLinkSpriteText.cs => ChatLink.cs} | 10 +++++----- osu.Game/Overlays/Chat/ChatLine.cs | 6 +++--- osu.Game/osu.Game.csproj | 2 +- 3 files changed, 9 insertions(+), 9 deletions(-) rename osu.Game/Online/Chat/{ChatLinkSpriteText.cs => ChatLink.cs} (80%) diff --git a/osu.Game/Online/Chat/ChatLinkSpriteText.cs b/osu.Game/Online/Chat/ChatLink.cs similarity index 80% rename from osu.Game/Online/Chat/ChatLinkSpriteText.cs rename to osu.Game/Online/Chat/ChatLink.cs index adbdd9ed0c..e375ace1a0 100644 --- a/osu.Game/Online/Chat/ChatLinkSpriteText.cs +++ b/osu.Game/Online/Chat/ChatLink.cs @@ -12,7 +12,7 @@ using System.Linq; namespace osu.Game.Online.Chat { - public class ChatLinkSpriteText : OsuLinkSpriteText + public class ChatLink : OsuLinkSpriteText { public int LinkId = -1; @@ -22,12 +22,12 @@ namespace osu.Game.Online.Chat protected override bool OnHover(InputState state) { // Every word is one sprite in chat (for word wrap) so we need to find all other sprites that display the same link - var otherSpritesWithSameLink = ((Container)Parent).Children.Where(child => (child as ChatLinkSpriteText)?.LinkId == LinkId && !Equals(child)); + var otherSpritesWithSameLink = ((Container)Parent).Children.Where(child => (child as ChatLink)?.LinkId == LinkId && !Equals(child)); var hoverResult = base.OnHover(state); if (!otherSpritesWithSameLink.Any(sprite => sprite.IsHovered)) - foreach (ChatLinkSpriteText sprite in otherSpritesWithSameLink) + foreach (ChatLink sprite in otherSpritesWithSameLink) sprite.TriggerOnHover(state); Content.FadeColour(hoverColour, 500, Easing.OutQuint); @@ -37,7 +37,7 @@ namespace osu.Game.Online.Chat protected override void OnHoverLost(InputState state) { - var spritesWithSameLink = ((Container)Parent).Children.Where(child => (child as ChatLinkSpriteText)?.LinkId == LinkId); + var spritesWithSameLink = ((Container)Parent).Children.Where(child => (child as ChatLink)?.LinkId == LinkId); if (spritesWithSameLink.Any(sprite => sprite.IsHovered)) { @@ -46,7 +46,7 @@ namespace osu.Game.Online.Chat return; } - foreach (ChatLinkSpriteText sprite in spritesWithSameLink) + foreach (ChatLink sprite in spritesWithSameLink) sprite.Content.FadeColour(urlColour, 500, Easing.OutQuint); base.OnHoverLost(state); diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index ba88a9ac77..475a49b12e 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -83,7 +83,7 @@ namespace osu.Game.Overlays.Chat private Message message; private OsuSpriteText username; - private OsuLinkTextFlowContainer contentFlow; + private OsuLinkTextFlowContainer contentFlow; public Message Message { @@ -189,7 +189,7 @@ namespace osu.Game.Overlays.Chat Padding = new MarginPadding { Left = message_padding + padding }, Children = new Drawable[] { - contentFlow = new OsuLinkTextFlowContainer(t => { t.TextSize = text_size; }) + contentFlow = new OsuLinkTextFlowContainer(t => { t.TextSize = text_size; }) { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, @@ -238,7 +238,7 @@ namespace osu.Game.Overlays.Chat sprite.Font = @"Exo2.0-MediumItalic"; // We want to use something that is unique to every formatted link PER MESSAGE - ((ChatLinkSpriteText)sprite).LinkId = link.Index; + ((ChatLink)sprite).LinkId = link.Index; }); } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 24ca01a3ad..d1941fb046 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -289,7 +289,7 @@ - + From 65afbd5c1b9957603769d631ad2d3c89470e510c Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 10:29:33 +0100 Subject: [PATCH 031/138] Added new test case for URL parsing / link display in chat --- osu.Game.Tests/Visual/TestCaseChatLink.cs | 112 ++++++++++++++++++++++ osu.Game.Tests/osu.Game.Tests.csproj | 1 + 2 files changed, 113 insertions(+) create mode 100644 osu.Game.Tests/Visual/TestCaseChatLink.cs diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs new file mode 100644 index 0000000000..a2e73a56fb --- /dev/null +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -0,0 +1,112 @@ +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Online.Chat; +using osu.Game.Overlays; +using osu.Game.Overlays.Chat; +using osu.Game.Users; +using System; +using System.Linq; + +namespace osu.Game.Tests.Visual +{ + class TestCaseChatLink : OsuTestCase + { + private readonly BeatmapSetOverlay beatmapSetOverlay; + private readonly ChatOverlay chat; + + private DependencyContainer dependencies; + + private readonly ChatLineContainer textContainer; + private ChatLine[] testSprites; + + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); + + public TestCaseChatLink() + { + chat = new ChatOverlay(); + Add(beatmapSetOverlay = new BeatmapSetOverlay { Depth = float.MaxValue }); + + Add(textContainer = new ChatLineContainer + { + Padding = new MarginPadding { Left = 20, Right = 20 }, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + }); + + testSprites = new[] + { + new ChatLine(new DummyMessage("Test!")), + new ChatLine(new DummyMessage("osu.ppy.sh!")), + new ChatLine(new DummyMessage("long message to test word wrap: use https://encrypted.google.com instead of https://google.com or even worse, [http://google.com Unencrypted google]")), + new ChatLine(new DummyMessage("https://osu.ppy.sh!")), + new ChatLine(new DummyMessage("00:12:345 (1,2) - Test?")), + new ChatLine(new DummyMessage("Wiki link for tasty [[Performance Points]]")), + new ChatLine(new DummyMessage("is now playing [https://osu.ppy.sh/s/93523 IMAGE -MATERIAL- ]", true)), + new ChatLine(new DummyMessage("is now playing [https://osu.ppy.sh/b/252238 IMAGE -MATERIAL- ]", true)), + }; + } + + [BackgroundDependencyLoader] + private void load() + { + dependencies.Cache(chat); + dependencies.Cache(beatmapSetOverlay); + + textContainer.AddRange(testSprites); + } + + private class DummyMessage : Message + { + private static long messageCounter = 0; + private static User sender = new User + { + Username = @"Somebody", + Id = 1, + Country = new Country { FullName = @"Alien" }, + CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", + JoinDate = DateTimeOffset.Now.AddDays(-1), + LastVisit = DateTimeOffset.Now, + Age = 1, + ProfileOrder = new[] { "me" }, + CountryRank = 1, + Statistics = new UserStatistics + { + Rank = 2148, + PP = 4567.89m + }, + RankHistory = new User.RankHistoryData + { + Mode = @"osu", + Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray() + } + }; + + public new long Id = 42; + public new TargetType TargetType = TargetType.Channel; + public new int TargetId = 1; + public new bool IsAction; + public new DateTimeOffset Timestamp = DateTimeOffset.Now; + + public DummyMessage(string text, bool isAction = false) + : base(messageCounter++) + { + Content = text; + IsAction = isAction; + Sender = sender; + } + } + + private class ChatLineContainer : FillFlowContainer + { + protected override int Compare(Drawable x, Drawable y) + { + var xC = (ChatLine)x; + var yC = (ChatLine)y; + + return xC.Message.CompareTo(yC.Message); + } + } + } +} diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index b4242052d5..1fc58593ed 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -121,6 +121,7 @@ + From 006ac44e117e6d732aa56e42b61062c882dc825e Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 10:29:54 +0100 Subject: [PATCH 032/138] Fixed up style in the ChatDisplay test --- osu.Game.Tests/Visual/TestCaseChatDisplay.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseChatDisplay.cs b/osu.Game.Tests/Visual/TestCaseChatDisplay.cs index e23531f46a..092f92530b 100644 --- a/osu.Game.Tests/Visual/TestCaseChatDisplay.cs +++ b/osu.Game.Tests/Visual/TestCaseChatDisplay.cs @@ -12,7 +12,7 @@ namespace osu.Game.Tests.Visual [Description("Testing chat api and overlay")] internal class TestCaseChatDisplay : OsuTestCase { - private BeatmapSetOverlay beatmapSetOverlay; + private readonly BeatmapSetOverlay beatmapSetOverlay; private readonly ChatOverlay chat; private DependencyContainer dependencies; @@ -21,12 +21,10 @@ namespace osu.Game.Tests.Visual public TestCaseChatDisplay() { - chat = new ChatOverlay + Add(chat = new ChatOverlay { State = Visibility.Visible - }; - - Add(chat); + }); Add(beatmapSetOverlay = new BeatmapSetOverlay()); } From ec8b5c246576e3a72ef84f7779ee963e13178484 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 10:31:02 +0100 Subject: [PATCH 033/138] Permitted nulls in ChatOverlay.load() so that in testing, no "OsuGame" instance is required. Also added null checks to the links' on click actions --- osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs | 12 +++++------- osu.Game/Overlays/ChatOverlay.cs | 6 +++++- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs index 75b3f281d0..d99562092d 100644 --- a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs @@ -99,7 +99,7 @@ namespace osu.Game.Graphics.Sprites break; case "edit": - chat.Game.LoadEditorTimestamp(); + chat.Game?.LoadEditorTimestamp(); break; case "b": if (args.Length > 1 && int.TryParse(args[1], out int mapId)) @@ -122,16 +122,14 @@ namespace osu.Game.Graphics.Sprites req.Success += user => { - // TODO: Open spectator screen and start spectating - + chat.Game?.LoadSpectatorScreen(); }; - // api.Queue(req); - chat.Game.LoadSpectatorScreen(); + api.Queue(req); break; default: throw new ArgumentException($"Unknown osu:// link at {nameof(OsuLinkSpriteText)} (https://osu.ppy.sh/{args[0]})."); - } + } } else if (url.StartsWith("osump://")) { @@ -139,7 +137,7 @@ namespace osu.Game.Graphics.Sprites if (!int.TryParse(url.Split('/').ElementAtOrDefault(1), out int multiId)) return; - chat.Game.LoadMultiplayerLobby(); + chat.Game?.LoadMultiplayerLobby(); } else if (url.StartsWith("http://") || url.StartsWith("https://")) { diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index eaf58cde5e..c09908ca1d 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -41,6 +41,9 @@ namespace osu.Game.Overlays private readonly FocusedTextBox textbox; + /// + /// The current OsuGame instance. Will be null for Tests. + /// public OsuGame Game; private APIAccess api; @@ -272,9 +275,10 @@ namespace osu.Game.Overlays base.PopOut(); } - [BackgroundDependencyLoader] + [BackgroundDependencyLoader(true)] private void load(APIAccess api, OsuConfigManager config, OsuColour colours, OsuGame game) { + // game will be null in testing, so some links will not work Game = game; this.api = api; api.Register(this); From 1b971c01e663d0cc5a2e9fb1bd52a0efbd79d547 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 10:31:44 +0100 Subject: [PATCH 034/138] Fixed a bug where links would be out of order in their List which would cause the game to crash --- osu.Game/Online/Chat/MessageFormatter.cs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index a712cb1f2b..5853d50c1f 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -137,6 +137,9 @@ namespace osu.Game.Online.Chat var formatted = inputMessage; formatted.Content = result.Text; + + // Sometimes, regex matches are not in order + result.Links.Sort(); formatted.Links = result.Links; return formatted; } @@ -153,7 +156,7 @@ namespace osu.Game.Online.Chat } } - public class Link + public class Link : IComparable { public string Url; public int Index; @@ -165,6 +168,8 @@ namespace osu.Game.Online.Chat Index = startIndex; Length = length; } + + public int CompareTo(Link otherLink) => Index > otherLink.Index ? 1 : -1; } } } From f3f3d1d0fc96ec45a78f99b0c7b5c5fbbf642422 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 11:11:43 +0100 Subject: [PATCH 035/138] Various test fixes --- osu.Game.Tests/Visual/TestCaseChatLink.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index a2e73a56fb..a37d431760 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual private DependencyContainer dependencies; - private readonly ChatLineContainer textContainer; + private readonly TestChatLineContainer textContainer; private ChatLine[] testSprites; protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); @@ -25,9 +25,9 @@ namespace osu.Game.Tests.Visual public TestCaseChatLink() { chat = new ChatOverlay(); - Add(beatmapSetOverlay = new BeatmapSetOverlay { Depth = float.MaxValue }); + Add(beatmapSetOverlay = new BeatmapSetOverlay { Depth = float.MinValue }); - Add(textContainer = new ChatLineContainer + Add(textContainer = new TestChatLineContainer { Padding = new MarginPadding { Left = 20, Right = 20 }, RelativeSizeAxes = Axes.X, @@ -39,11 +39,15 @@ namespace osu.Game.Tests.Visual { new ChatLine(new DummyMessage("Test!")), new ChatLine(new DummyMessage("osu.ppy.sh!")), - new ChatLine(new DummyMessage("long message to test word wrap: use https://encrypted.google.com instead of https://google.com or even worse, [http://google.com Unencrypted google]")), + new ChatLine(new DummyMessage("http://lookatmy.horse/")), new ChatLine(new DummyMessage("https://osu.ppy.sh!")), new ChatLine(new DummyMessage("00:12:345 (1,2) - Test?")), + // TODO: Remove prefix and add tooltips with links new ChatLine(new DummyMessage("Wiki link for tasty [[Performance Points]]")), - new ChatLine(new DummyMessage("is now playing [https://osu.ppy.sh/s/93523 IMAGE -MATERIAL- ]", true)), + new ChatLine(new DummyMessage("(osu forums)[https://osu.ppy.sh/forum] (old link format)")), + new ChatLine(new DummyMessage("[https://osu.ppy.sh/home New site] (new link format)")), + new ChatLine(new DummyMessage("long message to test word wrap: use https://encrypted.google.com instead of https://google.com or even worse, [http://google.com Unencrypted google]")), + new ChatLine(new DummyMessage("is now listening to [https://osu.ppy.sh/s/93523 IMAGE -MATERIAL- ]", true)), new ChatLine(new DummyMessage("is now playing [https://osu.ppy.sh/b/252238 IMAGE -MATERIAL- ]", true)), }; } @@ -86,7 +90,6 @@ namespace osu.Game.Tests.Visual public new long Id = 42; public new TargetType TargetType = TargetType.Channel; public new int TargetId = 1; - public new bool IsAction; public new DateTimeOffset Timestamp = DateTimeOffset.Now; public DummyMessage(string text, bool isAction = false) @@ -98,7 +101,7 @@ namespace osu.Game.Tests.Visual } } - private class ChatLineContainer : FillFlowContainer + private class TestChatLineContainer : FillFlowContainer { protected override int Compare(Drawable x, Drawable y) { From bd11124e6dcaacec6d035a089573e5934647a599 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 11:12:12 +0100 Subject: [PATCH 036/138] Removed unnecessary copy (pass-by-reference anyways) --- osu.Game/Online/Chat/MessageFormatter.cs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 5853d50c1f..43bfa36fed 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -134,14 +134,13 @@ namespace osu.Game.Online.Chat public static Message FormatMessage(Message inputMessage) { var result = format(inputMessage.Content); - var formatted = inputMessage; - formatted.Content = result.Text; + inputMessage.Content = result.Text; // Sometimes, regex matches are not in order result.Links.Sort(); - formatted.Links = result.Links; - return formatted; + inputMessage.Links = result.Links; + return inputMessage; } public class MessageFormatterResult From 8a88040ef50fa47401a799e8cae198906946a779 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 11:12:21 +0100 Subject: [PATCH 037/138] Added tooltip to links --- osu.Game/Online/Chat/ChatLink.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/ChatLink.cs b/osu.Game/Online/Chat/ChatLink.cs index e375ace1a0..9fb557492c 100644 --- a/osu.Game/Online/Chat/ChatLink.cs +++ b/osu.Game/Online/Chat/ChatLink.cs @@ -5,6 +5,7 @@ using OpenTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Input; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; @@ -12,13 +13,15 @@ using System.Linq; namespace osu.Game.Online.Chat { - public class ChatLink : OsuLinkSpriteText + public class ChatLink : OsuLinkSpriteText, IHasTooltip { public int LinkId = -1; private Color4 hoverColour; private Color4 urlColour; + public string TooltipText => LinkId != -1 ? Url : null; + protected override bool OnHover(InputState state) { // Every word is one sprite in chat (for word wrap) so we need to find all other sprites that display the same link From f4f1291919ac2255726098b938471277df0967b8 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 11:23:31 +0100 Subject: [PATCH 038/138] Removed "wiki:" prefix from wiki links (links are visible on tooltips so this is unnecessary now) --- osu.Game/Online/Chat/MessageFormatter.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 43bfa36fed..f3b6bc93f7 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -111,7 +111,7 @@ namespace osu.Game.Online.Chat handleMatches(oldLinkRegex, "{1}", "{2}", result, startIndex); // handle wiki links - handleMatches(wikiRegex, "wiki:{1}", "https://osu.ppy.sh/wiki/{1}", result, startIndex); + handleMatches(wikiRegex, "{1}", "https://osu.ppy.sh/wiki/{1}", result, startIndex); // handle bare links handleAdvanced(advancedLinkRegex, result, startIndex); From a8599a1b75c59f39ee7e329d5a5f70f8e1fda2c7 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 13:12:36 +0100 Subject: [PATCH 039/138] Implemented new interface which allows parent containers to decide on whether a "OnHover" sound should be played. --- osu.Game/Graphics/Containers/OsuClickableContainer.cs | 4 +++- osu.Game/Graphics/UserInterface/HoverSounds.cs | 8 +++++++- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuClickableContainer.cs b/osu.Game/Graphics/Containers/OsuClickableContainer.cs index 8df533ad6e..2cae413de8 100644 --- a/osu.Game/Graphics/Containers/OsuClickableContainer.cs +++ b/osu.Game/Graphics/Containers/OsuClickableContainer.cs @@ -8,7 +8,7 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Graphics.Containers { - public class OsuClickableContainer : ClickableContainer + public class OsuClickableContainer : ClickableContainer, IHasHoverSounds { private readonly HoverSampleSet sampleSet; @@ -16,6 +16,8 @@ namespace osu.Game.Graphics.Containers protected override Container Content => content; + public bool ShouldPlayHoverSound => true; + public OsuClickableContainer(HoverSampleSet sampleSet = HoverSampleSet.Normal) { this.sampleSet = sampleSet; diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs index 24dbe37567..ea452650b4 100644 --- a/osu.Game/Graphics/UserInterface/HoverSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverSounds.cs @@ -9,6 +9,7 @@ using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; +using static osu.Game.Online.Chat.ChatLink; namespace osu.Game.Graphics.UserInterface { @@ -30,7 +31,7 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnHover(InputState state) { - sampleHover?.Play(); + if ((Parent as IHasHoverSounds).ShouldPlayHoverSound) sampleHover?.Play(); return base.OnHover(state); } @@ -50,4 +51,9 @@ namespace osu.Game.Graphics.UserInterface [Description("-softer")] Soft } + + public interface IHasHoverSounds + { + bool ShouldPlayHoverSound { get; } + } } From 8ba66015f4a837182a1e6591810e47bbc544c2d9 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 13:17:40 +0100 Subject: [PATCH 040/138] Implemented the new IHasHoverSounds interface in a private "ChatHoverContainer" class which is now used for ChatLink instances. Also moved the overhead for finding all sprites in the same line that reference the same URL to the LoadComplete (used to be every hover, now only once). --- osu.Game/Online/Chat/ChatLink.cs | 42 +++++++++++++++++++++++++------- 1 file changed, 33 insertions(+), 9 deletions(-) diff --git a/osu.Game/Online/Chat/ChatLink.cs b/osu.Game/Online/Chat/ChatLink.cs index 9fb557492c..a5af3bfb3d 100644 --- a/osu.Game/Online/Chat/ChatLink.cs +++ b/osu.Game/Online/Chat/ChatLink.cs @@ -8,7 +8,11 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Input; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Overlays.Chat; +using System.Collections.Generic; using System.Linq; namespace osu.Game.Online.Chat @@ -20,17 +24,34 @@ namespace osu.Game.Online.Chat private Color4 hoverColour; private Color4 urlColour; + private readonly ChatHoverContainer content; + + protected IEnumerable SameLinkSprites { get; private set; } + + protected override Container Content => content ?? base.Content; + public string TooltipText => LinkId != -1 ? Url : null; + public ChatLink() + { + AddInternal(content = new ChatHoverContainer + { + AutoSizeAxes = Axes.Both, + }); + + OnLoadComplete = d => + { + // All sprites in the same chatline that represent the same URL + SameLinkSprites = (d.Parent as Container).Children.Where(child => (child as ChatLink)?.LinkId == LinkId && !d.Equals(child)).Cast(); + }; + } + protected override bool OnHover(InputState state) { - // Every word is one sprite in chat (for word wrap) so we need to find all other sprites that display the same link - var otherSpritesWithSameLink = ((Container)Parent).Children.Where(child => (child as ChatLink)?.LinkId == LinkId && !Equals(child)); - var hoverResult = base.OnHover(state); - if (!otherSpritesWithSameLink.Any(sprite => sprite.IsHovered)) - foreach (ChatLink sprite in otherSpritesWithSameLink) + if (!SameLinkSprites.Any(sprite => sprite.IsHovered)) + foreach (ChatLink sprite in SameLinkSprites) sprite.TriggerOnHover(state); Content.FadeColour(hoverColour, 500, Easing.OutQuint); @@ -40,16 +61,14 @@ namespace osu.Game.Online.Chat protected override void OnHoverLost(InputState state) { - var spritesWithSameLink = ((Container)Parent).Children.Where(child => (child as ChatLink)?.LinkId == LinkId); - - if (spritesWithSameLink.Any(sprite => sprite.IsHovered)) + if (SameLinkSprites.Any(sprite => sprite.IsHovered)) { // We have to do this so this sprite does not fade its colour back Content.FadeColour(hoverColour, 500, Easing.OutQuint); return; } - foreach (ChatLink sprite in spritesWithSameLink) + foreach (ChatLink sprite in SameLinkSprites) sprite.Content.FadeColour(urlColour, 500, Easing.OutQuint); base.OnHoverLost(state); @@ -63,5 +82,10 @@ namespace osu.Game.Online.Chat if (LinkId != -1) Content.Colour = urlColour; } + + private class ChatHoverContainer : OsuHoverContainer, IHasHoverSounds + { + public bool ShouldPlayHoverSound => ((ChatLink)Parent).SameLinkSprites.All(sprite => !sprite.IsHovered); + } } } From 07660a660027139230461279eb44c573c958f023 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 13:17:51 +0100 Subject: [PATCH 041/138] Added licence header to the new test case --- osu.Game.Tests/Visual/TestCaseChatLink.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index a37d431760..239ba33c26 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -1,4 +1,7 @@ -using osu.Framework.Allocation; +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Online.Chat; @@ -83,10 +86,10 @@ namespace osu.Game.Tests.Visual RankHistory = new User.RankHistoryData { Mode = @"osu", - Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray() + Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray(), } }; - + public new long Id = 42; public new TargetType TargetType = TargetType.Channel; public new int TargetId = 1; From 5ded6e877c27717c6e0621af8a3c3aa15063e574 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 13:49:44 +0100 Subject: [PATCH 042/138] Added some tooltip text handling. Also fixed a bug caused by SameLinkSprites where "this" is actually supposed to be included by just adding a manual function call. --- osu.Game/Online/Chat/ChatLink.cs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/ChatLink.cs b/osu.Game/Online/Chat/ChatLink.cs index a5af3bfb3d..bfee170fd1 100644 --- a/osu.Game/Online/Chat/ChatLink.cs +++ b/osu.Game/Online/Chat/ChatLink.cs @@ -12,6 +12,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Chat; +using System; using System.Collections.Generic; using System.Linq; @@ -26,11 +27,29 @@ namespace osu.Game.Online.Chat private readonly ChatHoverContainer content; + /// + /// Every other sprite in the containing ChatLine that represents the same link. + /// protected IEnumerable SameLinkSprites { get; private set; } protected override Container Content => content ?? base.Content; - public string TooltipText => LinkId != -1 ? Url : null; + public string TooltipText + { + get + { + if (LinkId == -1 || Url == Text) + return null; + + if (Url.StartsWith("osu://chan/")) + return "Switch to channel " + Url.Substring(11); + + if (Url.StartsWith("osu://edit/") && TimeSpan.TryParse(Url.Substring(11).Split(' ')[0], out TimeSpan editorTimeStamp)) + return "Go to " + editorTimeStamp.ToString(); + + return Url; + } + } public ChatLink() { @@ -68,6 +87,8 @@ namespace osu.Game.Online.Chat return; } + Content.FadeColour(urlColour, 500, Easing.OutQuint); + foreach (ChatLink sprite in SameLinkSprites) sprite.Content.FadeColour(urlColour, 500, Easing.OutQuint); From ca40db2b97836d1a6ecabebe14a37cbdbea3d556 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 13:57:29 +0100 Subject: [PATCH 043/138] Fixed a bug where the hover wouldn't work correctly due to OsuLinkSpriteText assigning Action to a private property. --- osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs index d99562092d..ad5045ff10 100644 --- a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs @@ -51,7 +51,12 @@ namespace osu.Game.Graphics.Sprites if (!string.IsNullOrEmpty(value)) { url = value; + content.Action = onClickAction; + + // For inheriting classes + if (Content is OsuHoverContainer hover) + hover.Action = onClickAction; } } } From 2129d6cede8ec2ffdfc30ded337afacd36eda80d Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 16:23:43 +0100 Subject: [PATCH 044/138] Renamed the "on click" method to OnClick() and moved most of the implementation to ChatLink. Also fixed the tooltip text up --- .../Graphics/Sprites/OsuLinkSpriteText.cs | 125 +---------------- osu.Game/Online/Chat/ChatLink.cs | 130 +++++++++++++++++- 2 files changed, 127 insertions(+), 128 deletions(-) diff --git a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs index ad5045ff10..cc19a50f24 100644 --- a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs @@ -25,13 +25,8 @@ namespace osu.Game.Graphics.Sprites { public class OsuLinkSpriteText : OsuSpriteText { - private ChatOverlay chat; - private readonly OsuHoverContainer content; - private APIAccess api; - private BeatmapSetOverlay beatmapSetOverlay; - public override bool HandleInput => content.Action != null; protected override Container Content => content ?? (Container)this; @@ -52,11 +47,7 @@ namespace osu.Game.Graphics.Sprites { url = value; - content.Action = onClickAction; - - // For inheriting classes - if (Content is OsuHoverContainer hover) - hover.Action = onClickAction; + content.Action = OnClick; } } } @@ -75,118 +66,6 @@ namespace osu.Game.Graphics.Sprites set { Content.Colour = value; } } - [BackgroundDependencyLoader] - private void load(APIAccess api, BeatmapSetOverlay beatmapSetOverlay, ChatOverlay chat) - { - this.api = api; - this.beatmapSetOverlay = beatmapSetOverlay; - this.chat = chat; - } - - private void onClickAction() - { - var url = Url; - - if (url.StartsWith("osu://")) - { - url = url.Substring(6); - var args = url.Split('/'); - - switch (args[0]) - { - case "chan": - var foundChannel = chat.AvailableChannels.Find(channel => channel.Name == args[1]); - - if (foundChannel == null) - TextColour = Color4.White; - else - chat.OpenChannel(foundChannel); - - break; - case "edit": - chat.Game?.LoadEditorTimestamp(); - break; - case "b": - if (args.Length > 1 && int.TryParse(args[1], out int mapId)) - beatmapSetOverlay.ShowBeatmap(mapId); - - break; - case "s": - case "dl": - if (args.Length > 1 && int.TryParse(args[1], out int mapSetId)) - beatmapSetOverlay.ShowBeatmapSet(mapSetId); - - break; - case "spectate": - GetUserRequest req; - if (int.TryParse(args[1], out int userId)) - req = new GetUserRequest(userId); - else - // Get by username instead - req = new GetUserRequest(args[1]); - - req.Success += user => - { - chat.Game?.LoadSpectatorScreen(); - }; - api.Queue(req); - - break; - default: - throw new ArgumentException($"Unknown osu:// link at {nameof(OsuLinkSpriteText)} (https://osu.ppy.sh/{args[0]})."); - } - } - else if (url.StartsWith("osump://")) - { - url = url.Substring(8); - if (!int.TryParse(url.Split('/').ElementAtOrDefault(1), out int multiId)) - return; - - chat.Game?.LoadMultiplayerLobby(); - } - else if (url.StartsWith("http://") || url.StartsWith("https://")) - { - var osuUrlIndex = url.IndexOf("osu.ppy.sh/"); - if (osuUrlIndex == -1) - { - Process.Start(url); - return; - } - - url = url.Substring(osuUrlIndex + 11); - if (url.StartsWith("s/") || url.StartsWith("beatmapsets/") || url.StartsWith("d/")) - { - var id = getIdFromUrl(url); - beatmapSetOverlay.ShowBeatmapSet(id); - } - else if (url.StartsWith("b/") || url.StartsWith("beatmaps/")) - { - var id = getIdFromUrl(url); - beatmapSetOverlay.ShowBeatmap(id); - } - else - Process.Start($"https://osu.ppy.sh/{url}"); - } - else - Process.Start(url); - } - - private int getIdFromUrl(string url) - { - var lastSlashIndex = url.LastIndexOf('/'); - // Remove possible trailing slash - if (lastSlashIndex == url.Length) - { - url = url.Remove(url.Length - 1); - lastSlashIndex = url.LastIndexOf('/'); - } - - var lastQuestionMarkIndex = url.LastIndexOf('?'); - // Filter out possible queries like mode specifications (e.g. /b/252238?m=0) - if (lastQuestionMarkIndex > lastSlashIndex) - url = url.Remove(lastQuestionMarkIndex); - - return int.Parse(url.Substring(lastSlashIndex + 1)); - } + protected virtual void OnClick() => Process.Start(Url); } } diff --git a/osu.Game/Online/Chat/ChatLink.cs b/osu.Game/Online/Chat/ChatLink.cs index bfee170fd1..4aa182b994 100644 --- a/osu.Game/Online/Chat/ChatLink.cs +++ b/osu.Game/Online/Chat/ChatLink.cs @@ -11,9 +11,13 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Overlays; using osu.Game.Overlays.Chat; using System; using System.Collections.Generic; +using System.Diagnostics; using System.Linq; namespace osu.Game.Online.Chat @@ -22,6 +26,10 @@ namespace osu.Game.Online.Chat { public int LinkId = -1; + private APIAccess api; + private BeatmapSetOverlay beatmapSetOverlay; + private ChatOverlay chat; + private Color4 hoverColour; private Color4 urlColour; @@ -34,6 +42,107 @@ namespace osu.Game.Online.Chat protected override Container Content => content ?? base.Content; + protected override void OnClick() + { + var url = Url; + + if (url.StartsWith("osu://")) + { + url = url.Substring(6); + var args = url.Split('/'); + + switch (args[0]) + { + case "chan": + var foundChannel = chat.AvailableChannels.Find(channel => channel.Name == args[1]); + + if (foundChannel == null) + throw new ArgumentException($"Unknown channel name ({args[1]})."); + else + chat.OpenChannel(foundChannel); + + break; + case "edit": + chat.Game?.LoadEditorTimestamp(); + break; + case "b": + if (args.Length > 1 && int.TryParse(args[1], out int mapId)) + beatmapSetOverlay.ShowBeatmap(mapId); + + break; + case "s": + case "dl": + if (args.Length > 1 && int.TryParse(args[1], out int mapSetId)) + beatmapSetOverlay.ShowBeatmapSet(mapSetId); + + break; + case "spectate": + GetUserRequest req; + if (int.TryParse(args[1], out int userId)) + req = new GetUserRequest(userId); + else + // Get by username instead + req = new GetUserRequest(args[1]); + + req.Success += user => + { + chat.Game?.LoadSpectatorScreen(); + }; + api.Queue(req); + + break; + default: + throw new ArgumentException($"Unknown osu:// link at {nameof(OsuLinkSpriteText)} (https://osu.ppy.sh/{args[0]})."); + } + } + else if (url.StartsWith("osump://")) + { + url = url.Substring(8); + if (!int.TryParse(url.Split('/').ElementAtOrDefault(1), out int multiId)) + return; + + chat.Game?.LoadMultiplayerLobby(); + } + else if (url.StartsWith("http://") || url.StartsWith("https://") && url.IndexOf("osu.ppy.sh/") != -1) + { + var osuUrlIndex = url.IndexOf("osu.ppy.sh/"); + + url = url.Substring(osuUrlIndex + 11); + if (url.StartsWith("s/") || url.StartsWith("beatmapsets/") || url.StartsWith("d/")) + { + var id = getIdFromUrl(url); + beatmapSetOverlay.ShowBeatmapSet(id); + } + else if (url.StartsWith("b/") || url.StartsWith("beatmaps/")) + { + var id = getIdFromUrl(url); + beatmapSetOverlay.ShowBeatmap(id); + } + else + base.OnClick(); + } + else + base.OnClick(); + } + + private int getIdFromUrl(string url) + { + var lastSlashIndex = url.LastIndexOf('/'); + // Remove possible trailing slash + if (lastSlashIndex == url.Length) + { + url = url.Remove(url.Length - 1); + lastSlashIndex = url.LastIndexOf('/'); + } + + var lastQuestionMarkIndex = url.LastIndexOf('?'); + // Filter out possible queries like mode specifications (e.g. /b/252238?m=0) + if (lastQuestionMarkIndex > lastSlashIndex) + url = url.Remove(lastQuestionMarkIndex); + + return int.Parse(url.Substring(lastSlashIndex + 1)); + } + public string TooltipText { get @@ -41,11 +150,18 @@ namespace osu.Game.Online.Chat if (LinkId == -1 || Url == Text) return null; - if (Url.StartsWith("osu://chan/")) - return "Switch to channel " + Url.Substring(11); + if (Url.StartsWith("osu://")) + { + var args = Url.Substring(6).Split('/'); - if (Url.StartsWith("osu://edit/") && TimeSpan.TryParse(Url.Substring(11).Split(' ')[0], out TimeSpan editorTimeStamp)) - return "Go to " + editorTimeStamp.ToString(); + if (args.Length < 2) + return Url; + + if (args[0] == "chan") + return "Switch to channel " + args[1]; + if (args[0] == "edit") + return "Go to " + args[1].Remove(9).TrimEnd(); + } return Url; } @@ -96,8 +212,12 @@ namespace osu.Game.Online.Chat } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(APIAccess api, BeatmapSetOverlay beatmapSetOverlay, ChatOverlay chat, OsuColour colours) { + this.api = api; + this.beatmapSetOverlay = beatmapSetOverlay; + this.chat = chat; + hoverColour = colours.Yellow; urlColour = colours.Blue; if (LinkId != -1) From 18eabd35f6633c1234944529a85e18d8f6e562a7 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 16:25:30 +0100 Subject: [PATCH 045/138] Set an empty list as default for AvailableChannels (mostly so that tests don't break, but also so that if no connection exists, the links don't break) --- osu.Game/Overlays/ChatOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index c09908ca1d..61e37b6e2f 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -65,7 +65,7 @@ namespace osu.Game.Overlays public Bindable ChatHeight { get; set; } - public List AvailableChannels { get; private set; } + public List AvailableChannels { get; private set; } = new List(); private readonly Container channelSelectionContainer; private readonly ChannelSelectionOverlay channelSelection; From d90eb2cdce7386926b58439e65cb24ceacc8f904 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 16:26:28 +0100 Subject: [PATCH 046/138] Moved "Does this channel exist" check to ChatLine so that if a #name does not exist as a channel, it does not get added as a link (and does not contain a URL or anything else to prevent jankiness) --- osu.Game/Overlays/Chat/ChatLine.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 475a49b12e..db7c255eb4 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -102,8 +102,9 @@ namespace osu.Game.Overlays.Chat } [BackgroundDependencyLoader(true)] - private void load(OsuColour colours) + private void load(OsuColour colours, ChatOverlay chat) { + this.chat = chat; customUsernameColour = colours.ChatBlue; urlColour = colours.Blue; } @@ -204,6 +205,8 @@ namespace osu.Game.Overlays.Chat FinishTransforms(true); } + private ChatOverlay chat; + private void updateMessageContent() { this.FadeTo(message is LocalEchoMessage ? 0.4f : 1.0f, 500, Easing.OutQuint); @@ -232,6 +235,17 @@ namespace osu.Game.Overlays.Chat contentFlow.AddText(message.Content.Substring(prevIndex, link.Index - prevIndex)); prevIndex = link.Index + link.Length; + // If a channel doesn't exist, add it as normal text instead + if (link.Url.StartsWith("osu://chan/")) + { + var channelName = link.Url.Substring(11).Split('/')[0]; + if (chat.AvailableChannels.TrueForAll(c => c.Name != channelName)) + { + contentFlow.AddText(message.Content.Substring(link.Index, link.Length)); + continue; + } + } + contentFlow.AddLink(message.Content.Substring(link.Index, link.Length), link.Url, sprite => { if (message.IsAction) From 334cb3dd10150be74171aa08033754437c8588da Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 16:26:50 +0100 Subject: [PATCH 047/138] Removed TODO and added another test line --- osu.Game.Tests/Visual/TestCaseChatLink.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index 239ba33c26..8371298d28 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -40,18 +40,19 @@ namespace osu.Game.Tests.Visual testSprites = new[] { + new ChatLine(new DummyMessage("Test!")), new ChatLine(new DummyMessage("osu.ppy.sh!")), new ChatLine(new DummyMessage("http://lookatmy.horse/")), new ChatLine(new DummyMessage("https://osu.ppy.sh!")), new ChatLine(new DummyMessage("00:12:345 (1,2) - Test?")), - // TODO: Remove prefix and add tooltips with links new ChatLine(new DummyMessage("Wiki link for tasty [[Performance Points]]")), new ChatLine(new DummyMessage("(osu forums)[https://osu.ppy.sh/forum] (old link format)")), new ChatLine(new DummyMessage("[https://osu.ppy.sh/home New site] (new link format)")), new ChatLine(new DummyMessage("long message to test word wrap: use https://encrypted.google.com instead of https://google.com or even worse, [http://google.com Unencrypted google]")), new ChatLine(new DummyMessage("is now listening to [https://osu.ppy.sh/s/93523 IMAGE -MATERIAL- ]", true)), new ChatLine(new DummyMessage("is now playing [https://osu.ppy.sh/b/252238 IMAGE -MATERIAL- ]", true)), + new ChatLine(new DummyMessage("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present).")), }; } From 2ceb073b5dce2a885f341fec23e0467318cabf8a Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 16:30:15 +0100 Subject: [PATCH 048/138] Renamed OnClick to OnLinkClicked to (what should be) obvious reasons --- osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs | 4 ++-- osu.Game/Online/Chat/ChatLink.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs index cc19a50f24..24a6f4b667 100644 --- a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs @@ -47,7 +47,7 @@ namespace osu.Game.Graphics.Sprites { url = value; - content.Action = OnClick; + content.Action = OnLinkClicked; } } } @@ -66,6 +66,6 @@ namespace osu.Game.Graphics.Sprites set { Content.Colour = value; } } - protected virtual void OnClick() => Process.Start(Url); + protected virtual void OnLinkClicked() => Process.Start(Url); } } diff --git a/osu.Game/Online/Chat/ChatLink.cs b/osu.Game/Online/Chat/ChatLink.cs index 4aa182b994..0fbe9820f7 100644 --- a/osu.Game/Online/Chat/ChatLink.cs +++ b/osu.Game/Online/Chat/ChatLink.cs @@ -42,7 +42,7 @@ namespace osu.Game.Online.Chat protected override Container Content => content ?? base.Content; - protected override void OnClick() + protected override void OnLinkClicked() { var url = Url; @@ -119,10 +119,10 @@ namespace osu.Game.Online.Chat beatmapSetOverlay.ShowBeatmap(id); } else - base.OnClick(); + base.OnLinkClicked(); } else - base.OnClick(); + base.OnLinkClicked(); } private int getIdFromUrl(string url) From 465f92af073a136fa6a534eed93e4a7d9980b3e9 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 16:31:02 +0100 Subject: [PATCH 049/138] Removed unnecessary whitespace --- osu.Game.Tests/Visual/TestCaseChatLink.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index 8371298d28..bed404bfc0 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -40,7 +40,6 @@ namespace osu.Game.Tests.Visual testSprites = new[] { - new ChatLine(new DummyMessage("Test!")), new ChatLine(new DummyMessage("osu.ppy.sh!")), new ChatLine(new DummyMessage("http://lookatmy.horse/")), From 772bba27be0608fc04f2bda6cf49ca8e411fdecd Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 16:41:46 +0100 Subject: [PATCH 050/138] Small style changes --- osu.Game.Tests/Visual/TestCaseChatLink.cs | 4 ++-- osu.Game/Overlays/Chat/ChatLine.cs | 1 - 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index bed404bfc0..1b6556e836 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -66,8 +66,8 @@ namespace osu.Game.Tests.Visual private class DummyMessage : Message { - private static long messageCounter = 0; - private static User sender = new User + private static long messageCounter; + private readonly static User sender = new User { Username = @"Somebody", Id = 1, diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index db7c255eb4..963fee5aa9 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -256,7 +256,6 @@ namespace osu.Game.Overlays.Chat }); } - // Add text after the last link var lastLink = message.Links[message.Links.Count - 1]; contentFlow.AddText(message.Content.Substring(lastLink.Index + lastLink.Length)); } From ce9b003e9a76e7044d5cc6ce318ffb9336107a4e Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 16:52:43 +0100 Subject: [PATCH 051/138] Reverted GetUserRequest because spectating is not implemented yet, and thus the additions are not needed (yet) --- osu.Game/Online/API/Requests/GetUserRequest.cs | 9 +-------- osu.Game/Online/Chat/ChatLink.cs | 3 +-- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetUserRequest.cs b/osu.Game/Online/API/Requests/GetUserRequest.cs index 47038b8ccb..3fbfd6bf51 100644 --- a/osu.Game/Online/API/Requests/GetUserRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRequest.cs @@ -8,7 +8,6 @@ namespace osu.Game.Online.API.Requests public class GetUserRequest : APIRequest { private long? userId; - private string userName; /// The user's ID. public GetUserRequest(long? userId = null) @@ -16,13 +15,7 @@ namespace osu.Game.Online.API.Requests this.userId = userId; } - /// The user's username. - public GetUserRequest(string userName) - { - this.userName = userName; - } - // Prefer ID over name - protected override string Target => userId.HasValue ? $@"users/{userId}" : ((!string.IsNullOrEmpty(userName)) ? $@"users/{userName}" : @"me"); + protected override string Target => userId.HasValue ? $@"users/{userId}" : @"me"; } } diff --git a/osu.Game/Online/Chat/ChatLink.cs b/osu.Game/Online/Chat/ChatLink.cs index 0fbe9820f7..2f0f3c0329 100644 --- a/osu.Game/Online/Chat/ChatLink.cs +++ b/osu.Game/Online/Chat/ChatLink.cs @@ -81,8 +81,7 @@ namespace osu.Game.Online.Chat if (int.TryParse(args[1], out int userId)) req = new GetUserRequest(userId); else - // Get by username instead - req = new GetUserRequest(args[1]); + return; req.Success += user => { From 6b0b518fd2207000b2fda761aa0ed543c30f7581 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 16:58:16 +0100 Subject: [PATCH 052/138] *ahem* REVERTED the changes to GetUserRequest because they're not needed (yet) --- osu.Game/Online/API/Requests/GetUserRequest.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Online/API/Requests/GetUserRequest.cs b/osu.Game/Online/API/Requests/GetUserRequest.cs index 3fbfd6bf51..2e3e7b01c8 100644 --- a/osu.Game/Online/API/Requests/GetUserRequest.cs +++ b/osu.Game/Online/API/Requests/GetUserRequest.cs @@ -9,13 +9,11 @@ namespace osu.Game.Online.API.Requests { private long? userId; - /// The user's ID. public GetUserRequest(long? userId = null) { this.userId = userId; } - // Prefer ID over name protected override string Target => userId.HasValue ? $@"users/{userId}" : @"me"; } } From e05618a415ee44f641400ea1f074f3bc2c4cfc92 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 19:36:56 +0100 Subject: [PATCH 053/138] Removed unnecessary "using" directives --- osu.Game.Tests/Visual/TestCaseChatDisplay.cs | 1 - .../Graphics/Containers/OsuLinkTextFlowContainer.cs | 6 ------ osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs | 13 ------------- osu.Game/Online/API/Requests/GetBeatmapRequest.cs | 5 ----- osu.Game/Online/Chat/Message.cs | 1 - osu.Game/Overlays/Profile/ProfileHeader.cs | 2 -- 6 files changed, 28 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseChatDisplay.cs b/osu.Game.Tests/Visual/TestCaseChatDisplay.cs index 092f92530b..3105d1c91b 100644 --- a/osu.Game.Tests/Visual/TestCaseChatDisplay.cs +++ b/osu.Game.Tests/Visual/TestCaseChatDisplay.cs @@ -4,7 +4,6 @@ using System.ComponentModel; using osu.Framework.Allocation; using osu.Framework.Graphics.Containers; -using osu.Game.Online.API; using osu.Game.Overlays; namespace osu.Game.Tests.Visual diff --git a/osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs b/osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs index ebb4cca0e0..382d2d73f4 100644 --- a/osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs @@ -1,17 +1,11 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Graphics; using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; -using osu.Framework.Input; using osu.Game.Graphics.Sprites; using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace osu.Game.Graphics.Containers { diff --git a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs index 24a6f4b667..771903c8f3 100644 --- a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs @@ -1,25 +1,12 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Graphics; -using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; -using osu.Framework.Input; -using osu.Framework.Screens; -using osu.Game.Beatmaps; using osu.Game.Graphics.Containers; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests; -using osu.Game.Overlays; -using osu.Game.Screens.Edit; -using System; using System.Collections.Generic; using System.Diagnostics; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace osu.Game.Graphics.Sprites { diff --git a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs index 1df695c795..76d67e7afc 100644 --- a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs +++ b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs @@ -2,11 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Game.Beatmaps; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace osu.Game.Online.API.Requests { diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs index 355abfda59..ac14d3a88f 100644 --- a/osu.Game/Online/Chat/Message.cs +++ b/osu.Game/Online/Chat/Message.cs @@ -2,7 +2,6 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; -using System.Collections; using System.Collections.Generic; using System.ComponentModel; using Newtonsoft.Json; diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index af3e97db27..52038ce810 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -16,8 +16,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Users; -using System.Diagnostics; -using System.Collections.Generic; using osu.Framework.Graphics.Cursor; namespace osu.Game.Overlays.Profile From 908553ffafc164149ddd48c626dd491961bb4d2d Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 19:38:03 +0100 Subject: [PATCH 054/138] Added parameter for LoadMultiplayerLobby (CI warning for unused int) --- osu.Game/Online/Chat/ChatLink.cs | 10 ++++------ osu.Game/OsuGame.cs | 2 +- 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Chat/ChatLink.cs b/osu.Game/Online/Chat/ChatLink.cs index 2f0f3c0329..b9e13e3464 100644 --- a/osu.Game/Online/Chat/ChatLink.cs +++ b/osu.Game/Online/Chat/ChatLink.cs @@ -14,10 +14,8 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Overlays; -using osu.Game.Overlays.Chat; using System; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; namespace osu.Game.Online.Chat @@ -100,11 +98,11 @@ namespace osu.Game.Online.Chat if (!int.TryParse(url.Split('/').ElementAtOrDefault(1), out int multiId)) return; - chat.Game?.LoadMultiplayerLobby(); + chat.Game?.LoadMultiplayerLobby(multiId); } - else if (url.StartsWith("http://") || url.StartsWith("https://") && url.IndexOf("osu.ppy.sh/") != -1) + else if (url.StartsWith("http://") || url.StartsWith("https://") && url.IndexOf("osu.ppy.sh/", StringComparison.InvariantCultureIgnoreCase) != -1) { - var osuUrlIndex = url.IndexOf("osu.ppy.sh/"); + var osuUrlIndex = url.IndexOf("osu.ppy.sh/", StringComparison.InvariantCultureIgnoreCase); url = url.Substring(osuUrlIndex + 11); if (url.StartsWith("s/") || url.StartsWith("beatmapsets/") || url.StartsWith("d/")) @@ -176,7 +174,7 @@ namespace osu.Game.Online.Chat OnLoadComplete = d => { // All sprites in the same chatline that represent the same URL - SameLinkSprites = (d.Parent as Container).Children.Where(child => (child as ChatLink)?.LinkId == LinkId && !d.Equals(child)).Cast(); + SameLinkSprites = ((Container)d.Parent).Children.Where(child => (child as ChatLink)?.LinkId == LinkId && !d.Equals(child)).Cast(); }; } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 900a55293d..00c8736f44 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -134,7 +134,7 @@ namespace osu.Game }); } - internal void LoadMultiplayerLobby() + internal void LoadMultiplayerLobby(int lobbyId) { notificationOverlay.Post(new SimpleNotification { From 34a37935e35e20769307a1717dcfe61c232596a7 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 19:38:59 +0100 Subject: [PATCH 055/138] Removed unused property and unnecessary return statement (CI) --- osu.Game/Overlays/Chat/ChatLine.cs | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 963fee5aa9..5fbfed5a67 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -67,7 +67,6 @@ namespace osu.Game.Overlays.Chat private const float text_size = 20; private Color4 customUsernameColour; - private Color4 urlColour; private OsuSpriteText timestamp; @@ -106,7 +105,6 @@ namespace osu.Game.Overlays.Chat { this.chat = chat; customUsernameColour = colours.ChatBlue; - urlColour = colours.Blue; } private bool senderHasBackground => !string.IsNullOrEmpty(message.Sender.Colour); @@ -224,8 +222,6 @@ namespace osu.Game.Overlays.Chat if (message.IsAction) sprite.Font = @"Exo2.0-MediumItalic"; }); - - return; } else { From c950d1359acb9528335f15d6db5987ea188c0f04 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 19:39:13 +0100 Subject: [PATCH 056/138] various CI adjustments --- osu.Game.Tests/Visual/TestCaseChatLink.cs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index 1b6556e836..6a36411407 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -13,7 +13,7 @@ using System.Linq; namespace osu.Game.Tests.Visual { - class TestCaseChatLink : OsuTestCase + internal class TestCaseChatLink : OsuTestCase { private readonly BeatmapSetOverlay beatmapSetOverlay; private readonly ChatOverlay chat; @@ -21,7 +21,7 @@ namespace osu.Game.Tests.Visual private DependencyContainer dependencies; private readonly TestChatLineContainer textContainer; - private ChatLine[] testSprites; + private readonly ChatLine[] testSprites; protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual private class DummyMessage : Message { private static long messageCounter; - private readonly static User sender = new User + private static readonly User sender = new User { Username = @"Somebody", Id = 1, @@ -90,9 +90,6 @@ namespace osu.Game.Tests.Visual } }; - public new long Id = 42; - public new TargetType TargetType = TargetType.Channel; - public new int TargetId = 1; public new DateTimeOffset Timestamp = DateTimeOffset.Now; public DummyMessage(string text, bool isAction = false) From c5a7f5b16321f030669a7d956504ecfbf33ebf55 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 19:39:39 +0100 Subject: [PATCH 057/138] Renamed the static variables and made them readonly, aswell as other small adjustments (CI) --- osu.Game/Online/Chat/MessageFormatter.cs | 35 +++++++++++------------- 1 file changed, 16 insertions(+), 19 deletions(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index f3b6bc93f7..81551b08f4 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -3,28 +3,25 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; using System.Text.RegularExpressions; -using System.Threading.Tasks; namespace osu.Game.Online.Chat { public static class MessageFormatter { // [[Performance Points]] -> wiki:Performance Points (https://osu.ppy.sh/wiki/Performance_Points) - private static Regex wikiRegex = new Regex(@"\[\[([^\]]+)\]\]"); + private static readonly Regex wiki_regex = new Regex(@"\[\[([^\]]+)\]\]"); // (test)[https://osu.ppy.sh/b/1234] -> test (https://osu.ppy.sh/b/1234) - private static Regex oldLinkRegex = new Regex(@"\(([^\)]*)\)\[([a-z]+://[^ ]+)\]"); + private static readonly Regex old_link_regex = new Regex(@"\(([^\)]*)\)\[([a-z]+://[^ ]+)\]"); // [https://osu.ppy.sh/b/1234 Beatmap [Hard] (poop)] -> Beatmap [hard] (poop) (https://osu.ppy.sh/b/1234) - private static Regex newLinkRegex = new Regex(@"\[([a-z]+://[^ ]+) ([^\[\]]*(((?\[)[^\[\]]*)+((?\])[^\[\]]*)+)*(?(open)(?!)))\]"); + private static readonly Regex new_link_regex = new Regex(@"\[([a-z]+://[^ ]+) ([^\[\]]*(((?\[)[^\[\]]*)+((?\])[^\[\]]*)+)*(?(open)(?!)))\]"); // advanced, RFC-compatible regular expression that matches any possible URL, *but* allows certain invalid characters that are widely used // This is in the format (, [optional]): // http[s]://.[:port][/path][?query][#fragment] - private static Regex advancedLinkRegex = new Regex(@"(?\([^)]*)?" + + private static readonly Regex advanced_link_regex = new Regex(@"(?\([^)]*)?" + @"(?https?:\/\/" + @"(?(?:[a-z0-9]\.|[a-z0-9][a-z0-9-]*[a-z0-9]\.)*[a-z][a-z0-9-]*[a-z0-9]" + // domain, TLD @"(?::\d+)?)" + // port @@ -34,13 +31,13 @@ namespace osu.Game.Online.Chat RegexOptions.IgnoreCase); // 00:00:000 (1,2,3) - test - private static Regex timeRegex = new Regex(@"\d\d:\d\d:\d\d\d? [^-]*"); + private static readonly Regex time_regex = new Regex(@"\d\d:\d\d:\d\d\d? [^-]*"); // #osu - private static Regex channelRegex = new Regex(@"#[a-zA-Z]+[a-zA-Z0-9]+"); + private static readonly Regex channel_regex = new Regex(@"#[a-zA-Z]+[a-zA-Z0-9]+"); // Unicode emojis - private static Regex emojiRegex = new Regex(@"(\uD83D[\uDC00-\uDE4F])"); + private static readonly Regex emoji_regex = new Regex(@"(\uD83D[\uDC00-\uDE4F])"); private static void handleMatches(Regex regex, string display, string link, MessageFormatterResult result, int startIndex = 0) { @@ -62,7 +59,7 @@ namespace osu.Game.Online.Chat if (displayText.Length == 0 || linkText.Length == 0) continue; // Check for encapsulated links - if (result.Links.Find(l => (l.Index <= index && l.Index + l.Length >= index + m.Length) || index <= l.Index && index + m.Length >= l.Index + l.Length) == null) + if (result.Links.Find(l => l.Index <= index && l.Index + l.Length >= index + m.Length || index <= l.Index && index + m.Length >= l.Index + l.Length) == null) { result.Text = result.Text.Remove(index, m.Length).Insert(index, displayText); @@ -72,7 +69,7 @@ namespace osu.Game.Online.Chat result.Links.Add(new Link(linkText, index, displayText.Length)); //adjust the offset for processing the current matches group. - captureOffset += (m.Length - displayText.Length); + captureOffset += m.Length - displayText.Length; } } } @@ -105,28 +102,28 @@ namespace osu.Game.Online.Chat var result = new MessageFormatterResult(toFormat); // handle the [link display] format - handleMatches(newLinkRegex, "{2}", "{1}", result, startIndex); + handleMatches(new_link_regex, "{2}", "{1}", result, startIndex); // handle the ()[] link format - handleMatches(oldLinkRegex, "{1}", "{2}", result, startIndex); + handleMatches(old_link_regex, "{1}", "{2}", result, startIndex); // handle wiki links - handleMatches(wikiRegex, "{1}", "https://osu.ppy.sh/wiki/{1}", result, startIndex); + handleMatches(wiki_regex, "{1}", "https://osu.ppy.sh/wiki/{1}", result, startIndex); // handle bare links - handleAdvanced(advancedLinkRegex, result, startIndex); + handleAdvanced(advanced_link_regex, result, startIndex); // handle editor times - handleMatches(timeRegex, "{0}", "osu://edit/{0}", result, startIndex); + handleMatches(time_regex, "{0}", "osu://edit/{0}", result, startIndex); // handle channels - handleMatches(channelRegex, "{0}", "osu://chan/{0}", result, startIndex); + handleMatches(channel_regex, "{0}", "osu://chan/{0}", result, startIndex); var empty = ""; while (space-- > 0) empty += "\0"; - handleMatches(emojiRegex, empty, "{0}", result, startIndex); + handleMatches(emoji_regex, empty, "{0}", result, startIndex); return result; } From 68255095a65a5e63e1b8988e71a8817a3a45c00e Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 19:52:40 +0100 Subject: [PATCH 058/138] Renamed the IHasHoverSounds interface to ICanDisableHoverSounds and made it so that if the interface is not implemented, it is just ignored (samples will always be played). If it is implemented, the ShouldPlayHoverSound bool is decisive of whether sounds are played or not --- .../Graphics/Containers/OsuClickableContainer.cs | 4 +--- osu.Game/Graphics/UserInterface/HoverSounds.cs | 12 ++++++++---- osu.Game/Online/Chat/ChatLink.cs | 2 +- 3 files changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuClickableContainer.cs b/osu.Game/Graphics/Containers/OsuClickableContainer.cs index 2cae413de8..8df533ad6e 100644 --- a/osu.Game/Graphics/Containers/OsuClickableContainer.cs +++ b/osu.Game/Graphics/Containers/OsuClickableContainer.cs @@ -8,7 +8,7 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Graphics.Containers { - public class OsuClickableContainer : ClickableContainer, IHasHoverSounds + public class OsuClickableContainer : ClickableContainer { private readonly HoverSampleSet sampleSet; @@ -16,8 +16,6 @@ namespace osu.Game.Graphics.Containers protected override Container Content => content; - public bool ShouldPlayHoverSound => true; - public OsuClickableContainer(HoverSampleSet sampleSet = HoverSampleSet.Normal) { this.sampleSet = sampleSet; diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs index ea452650b4..ccdb5a50e0 100644 --- a/osu.Game/Graphics/UserInterface/HoverSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverSounds.cs @@ -9,7 +9,6 @@ using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; -using static osu.Game.Online.Chat.ChatLink; namespace osu.Game.Graphics.UserInterface { @@ -20,7 +19,7 @@ namespace osu.Game.Graphics.UserInterface public class HoverSounds : CompositeDrawable { private SampleChannel sampleHover; - + protected readonly HoverSampleSet SampleSet; public HoverSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal) @@ -31,7 +30,8 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnHover(InputState state) { - if ((Parent as IHasHoverSounds).ShouldPlayHoverSound) sampleHover?.Play(); + // If Parent does not implement the interface, still play the sample + if ((Parent as ICanDisableHoverSounds)?.ShouldPlayHoverSound != false) sampleHover?.Play(); return base.OnHover(state); } @@ -52,7 +52,11 @@ namespace osu.Game.Graphics.UserInterface Soft } - public interface IHasHoverSounds + /// + /// Classes implementing this interface can choose whether or not the HoverSounds should be played. + /// If this is not implemented, the sounds will always be played when OnHover is triggered. + /// + public interface ICanDisableHoverSounds { bool ShouldPlayHoverSound { get; } } diff --git a/osu.Game/Online/Chat/ChatLink.cs b/osu.Game/Online/Chat/ChatLink.cs index b9e13e3464..42f5ea3e16 100644 --- a/osu.Game/Online/Chat/ChatLink.cs +++ b/osu.Game/Online/Chat/ChatLink.cs @@ -221,7 +221,7 @@ namespace osu.Game.Online.Chat Content.Colour = urlColour; } - private class ChatHoverContainer : OsuHoverContainer, IHasHoverSounds + private class ChatHoverContainer : OsuHoverContainer, ICanDisableHoverSounds { public bool ShouldPlayHoverSound => ((ChatLink)Parent).SameLinkSprites.All(sprite => !sprite.IsHovered); } From 5546b8c3160f2f9c7b38a543ae56e5bd20101655 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 7 Dec 2017 19:55:29 +0100 Subject: [PATCH 059/138] Trimmed whitespace (CI) --- osu.Game/Graphics/UserInterface/HoverSounds.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs index ccdb5a50e0..43adea49dd 100644 --- a/osu.Game/Graphics/UserInterface/HoverSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverSounds.cs @@ -19,7 +19,7 @@ namespace osu.Game.Graphics.UserInterface public class HoverSounds : CompositeDrawable { private SampleChannel sampleHover; - + protected readonly HoverSampleSet SampleSet; public HoverSounds(HoverSampleSet sampleSet = HoverSampleSet.Normal) From 2568ac1bf78b613849abb708af049bb803c68bd1 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Mon, 11 Dec 2017 10:42:05 +0100 Subject: [PATCH 060/138] Fixed small merging mistake --- osu.Game/osu.Game.csproj | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index ed371f6c39..683035f3a4 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -294,7 +294,6 @@ - @@ -861,4 +860,4 @@ - + \ No newline at end of file From 13bc50ad56251c5cece3c7da8c6bea3c37ca9d02 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Mon, 11 Dec 2017 10:42:36 +0100 Subject: [PATCH 061/138] Removed ICanDisableHoverSounds interface --- osu.Game/Graphics/UserInterface/HoverSounds.cs | 12 +----------- osu.Game/Online/Chat/ChatLink.cs | 9 ++++----- 2 files changed, 5 insertions(+), 16 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/HoverSounds.cs b/osu.Game/Graphics/UserInterface/HoverSounds.cs index 43adea49dd..24dbe37567 100644 --- a/osu.Game/Graphics/UserInterface/HoverSounds.cs +++ b/osu.Game/Graphics/UserInterface/HoverSounds.cs @@ -30,8 +30,7 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnHover(InputState state) { - // If Parent does not implement the interface, still play the sample - if ((Parent as ICanDisableHoverSounds)?.ShouldPlayHoverSound != false) sampleHover?.Play(); + sampleHover?.Play(); return base.OnHover(state); } @@ -51,13 +50,4 @@ namespace osu.Game.Graphics.UserInterface [Description("-softer")] Soft } - - /// - /// Classes implementing this interface can choose whether or not the HoverSounds should be played. - /// If this is not implemented, the sounds will always be played when OnHover is triggered. - /// - public interface ICanDisableHoverSounds - { - bool ShouldPlayHoverSound { get; } - } } diff --git a/osu.Game/Online/Chat/ChatLink.cs b/osu.Game/Online/Chat/ChatLink.cs index 42f5ea3e16..83b1429993 100644 --- a/osu.Game/Online/Chat/ChatLink.cs +++ b/osu.Game/Online/Chat/ChatLink.cs @@ -4,6 +4,7 @@ using OpenTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Input; @@ -180,15 +181,13 @@ namespace osu.Game.Online.Chat protected override bool OnHover(InputState state) { - var hoverResult = base.OnHover(state); - if (!SameLinkSprites.Any(sprite => sprite.IsHovered)) foreach (ChatLink sprite in SameLinkSprites) sprite.TriggerOnHover(state); Content.FadeColour(hoverColour, 500, Easing.OutQuint); - return hoverResult; + return true; } protected override void OnHoverLost(InputState state) @@ -221,9 +220,9 @@ namespace osu.Game.Online.Chat Content.Colour = urlColour; } - private class ChatHoverContainer : OsuHoverContainer, ICanDisableHoverSounds + private class ChatHoverContainer : OsuHoverContainer { - public bool ShouldPlayHoverSound => ((ChatLink)Parent).SameLinkSprites.All(sprite => !sprite.IsHovered); + } } } From 4d475f1c1bcfa420ead2ea077c4e191f09d3806b Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Mon, 11 Dec 2017 11:05:32 +0100 Subject: [PATCH 062/138] Changed it so ChatLinks handle hover and click sounds themselves --- .../Graphics/Sprites/OsuLinkSpriteText.cs | 15 ++++++----- osu.Game/Online/Chat/ChatLink.cs | 25 +++++++++++++------ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs index 771903c8f3..21b62ee37e 100644 --- a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs @@ -4,6 +4,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Framework.Input; using osu.Game.Graphics.Containers; using System.Collections.Generic; using System.Diagnostics; @@ -12,7 +13,7 @@ namespace osu.Game.Graphics.Sprites { public class OsuLinkSpriteText : OsuSpriteText { - private readonly OsuHoverContainer content; + private readonly OsuClickableContainer content; public override bool HandleInput => content.Action != null; @@ -20,6 +21,12 @@ namespace osu.Game.Graphics.Sprites protected override IEnumerable FlowingChildren => Children; + protected override bool OnClick(InputState state) + { + OnLinkClicked(); + return true; + } + private string url; public string Url @@ -31,17 +38,13 @@ namespace osu.Game.Graphics.Sprites set { if (!string.IsNullOrEmpty(value)) - { url = value; - - content.Action = OnLinkClicked; - } } } public OsuLinkSpriteText() { - AddInternal(content = new OsuHoverContainer + AddInternal(content = new OsuClickableContainer { AutoSizeAxes = Axes.Both, }); diff --git a/osu.Game/Online/Chat/ChatLink.cs b/osu.Game/Online/Chat/ChatLink.cs index 83b1429993..08a3184034 100644 --- a/osu.Game/Online/Chat/ChatLink.cs +++ b/osu.Game/Online/Chat/ChatLink.cs @@ -32,7 +32,8 @@ namespace osu.Game.Online.Chat private Color4 hoverColour; private Color4 urlColour; - private readonly ChatHoverContainer content; + private readonly Container content; + private readonly HoverClickSounds hoverClickSounds; /// /// Every other sprite in the containing ChatLine that represents the same link. @@ -41,6 +42,12 @@ namespace osu.Game.Online.Chat protected override Container Content => content ?? base.Content; + protected override bool OnClick(InputState state) + { + hoverClickSounds.TriggerOnClick(state); + return base.OnClick(state); + } + protected override void OnLinkClicked() { var url = Url; @@ -167,7 +174,9 @@ namespace osu.Game.Online.Chat public ChatLink() { - AddInternal(content = new ChatHoverContainer + hoverClickSounds = new HoverClickSounds(); + + AddInternal(content = new Container { AutoSizeAxes = Axes.Both, }); @@ -182,8 +191,12 @@ namespace osu.Game.Online.Chat protected override bool OnHover(InputState state) { if (!SameLinkSprites.Any(sprite => sprite.IsHovered)) + { + hoverClickSounds.TriggerOnHover(state); + foreach (ChatLink sprite in SameLinkSprites) sprite.TriggerOnHover(state); + } Content.FadeColour(hoverColour, 500, Easing.OutQuint); @@ -210,6 +223,9 @@ namespace osu.Game.Online.Chat [BackgroundDependencyLoader] private void load(APIAccess api, BeatmapSetOverlay beatmapSetOverlay, ChatOverlay chat, OsuColour colours) { + // Should be ok, inexpensive operation + LoadComponentAsync(hoverClickSounds); + this.api = api; this.beatmapSetOverlay = beatmapSetOverlay; this.chat = chat; @@ -219,10 +235,5 @@ namespace osu.Game.Online.Chat if (LinkId != -1) Content.Colour = urlColour; } - - private class ChatHoverContainer : OsuHoverContainer - { - - } } } From bb0a32b55509e1c6b43b18db57c5ced2b216d3d8 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Mon, 11 Dec 2017 11:28:16 +0100 Subject: [PATCH 063/138] Removed private OsuHoverContainer "content" from OsuLinkSpriteText for more customization. --- osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs | 16 +--------------- osu.Game/Overlays/Profile/ProfileHeader.cs | 13 +++++++++++++ 2 files changed, 14 insertions(+), 15 deletions(-) diff --git a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs index 21b62ee37e..cc40ac2214 100644 --- a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs @@ -13,18 +13,12 @@ namespace osu.Game.Graphics.Sprites { public class OsuLinkSpriteText : OsuSpriteText { - private readonly OsuClickableContainer content; - - public override bool HandleInput => content.Action != null; - - protected override Container Content => content ?? (Container)this; - protected override IEnumerable FlowingChildren => Children; protected override bool OnClick(InputState state) { OnLinkClicked(); - return true; + return base.OnClick(state); } private string url; @@ -42,14 +36,6 @@ namespace osu.Game.Graphics.Sprites } } - public OsuLinkSpriteText() - { - AddInternal(content = new OsuClickableContainer - { - AutoSizeAxes = Axes.Both, - }); - } - public ColourInfo TextColour { get { return Content.Colour; } diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 3954330d33..7bdd09e2e0 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -17,6 +17,7 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Users; using osu.Framework.Graphics.Cursor; +using osu.Framework.Input; namespace osu.Game.Overlays.Profile { @@ -490,8 +491,20 @@ namespace osu.Game.Overlays.Profile { public string TooltipText => "View Profile in Browser"; + private readonly OsuHoverContainer content; + + protected override Container Content => content ?? (Container)this; + + public override bool HandleInput => true; + public ProfileLink(User user) { + AddInternal(content = new OsuHoverContainer + { + Action = OnLinkClicked, + AutoSizeAxes = Axes.Both, + }); + Text = user.Username; Url = $@"https://osu.ppy.sh/users/{user.Id}"; Font = @"Exo2.0-RegularItalic"; From 63698895a51302da1fa3e0509c5688d77c3352eb Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Mon, 11 Dec 2017 11:40:04 +0100 Subject: [PATCH 064/138] Removed unnecessary container and fixed "HandleInput" for ChatLinks --- osu.Game/Online/Chat/ChatLink.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Chat/ChatLink.cs b/osu.Game/Online/Chat/ChatLink.cs index 08a3184034..90d2e46f62 100644 --- a/osu.Game/Online/Chat/ChatLink.cs +++ b/osu.Game/Online/Chat/ChatLink.cs @@ -23,6 +23,10 @@ namespace osu.Game.Online.Chat { public class ChatLink : OsuLinkSpriteText, IHasTooltip { + /// + /// Identifier unique to every link in a message. + /// A value of -1 means that this instance does not contain a link. + /// public int LinkId = -1; private APIAccess api; @@ -32,7 +36,6 @@ namespace osu.Game.Online.Chat private Color4 hoverColour; private Color4 urlColour; - private readonly Container content; private readonly HoverClickSounds hoverClickSounds; /// @@ -40,7 +43,7 @@ namespace osu.Game.Online.Chat /// protected IEnumerable SameLinkSprites { get; private set; } - protected override Container Content => content ?? base.Content; + public override bool HandleInput => LinkId != -1; protected override bool OnClick(InputState state) { @@ -176,11 +179,6 @@ namespace osu.Game.Online.Chat { hoverClickSounds = new HoverClickSounds(); - AddInternal(content = new Container - { - AutoSizeAxes = Axes.Both, - }); - OnLoadComplete = d => { // All sprites in the same chatline that represent the same URL From 8a02507d441adcc814921295c61e7e7b37242ff0 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Mon, 11 Dec 2017 12:40:37 +0100 Subject: [PATCH 065/138] Let regex handle "getIdFromUrl" --- osu.Game/Online/Chat/ChatLink.cs | 19 ++----------------- 1 file changed, 2 insertions(+), 17 deletions(-) diff --git a/osu.Game/Online/Chat/ChatLink.cs b/osu.Game/Online/Chat/ChatLink.cs index 90d2e46f62..acea78586b 100644 --- a/osu.Game/Online/Chat/ChatLink.cs +++ b/osu.Game/Online/Chat/ChatLink.cs @@ -18,6 +18,7 @@ using osu.Game.Overlays; using System; using System.Collections.Generic; using System.Linq; +using System.Text.RegularExpressions; namespace osu.Game.Online.Chat { @@ -133,23 +134,7 @@ namespace osu.Game.Online.Chat base.OnLinkClicked(); } - private int getIdFromUrl(string url) - { - var lastSlashIndex = url.LastIndexOf('/'); - // Remove possible trailing slash - if (lastSlashIndex == url.Length) - { - url = url.Remove(url.Length - 1); - lastSlashIndex = url.LastIndexOf('/'); - } - - var lastQuestionMarkIndex = url.LastIndexOf('?'); - // Filter out possible queries like mode specifications (e.g. /b/252238?m=0) - if (lastQuestionMarkIndex > lastSlashIndex) - url = url.Remove(lastQuestionMarkIndex); - - return int.Parse(url.Substring(lastSlashIndex + 1)); - } + private int getIdFromUrl(string url) => int.Parse(Regex.Match(url, @"\/(\d+)\/?").Groups[1].Value); public string TooltipText { From bfa9beb7b2d5bea4e2439ca4cb015d6a2cdab5c0 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Mon, 11 Dec 2017 13:43:47 +0100 Subject: [PATCH 066/138] Switched to regex to handle most of the decision on what to do on click. Also updated the getIdFromUrl method to adapt to the changes --- osu.Game/Online/Chat/ChatLink.cs | 155 +++++++++++++++++-------------- 1 file changed, 87 insertions(+), 68 deletions(-) diff --git a/osu.Game/Online/Chat/ChatLink.cs b/osu.Game/Online/Chat/ChatLink.cs index acea78586b..d251580812 100644 --- a/osu.Game/Online/Chat/ChatLink.cs +++ b/osu.Game/Online/Chat/ChatLink.cs @@ -54,87 +54,106 @@ namespace osu.Game.Online.Chat protected override void OnLinkClicked() { - var url = Url; - - if (url.StartsWith("osu://")) + var urlMatch = Regex.Matches(Url, @"^(?osu(?:mp)?|https?):\/\/(?.*)")[0]; + if (urlMatch.Success) { - url = url.Substring(6); - var args = url.Split('/'); + var args = urlMatch.Groups["content"].Value.Split('/'); - switch (args[0]) + switch (urlMatch.Groups["protocol"].Value) { - case "chan": - var foundChannel = chat.AvailableChannels.Find(channel => channel.Name == args[1]); - - if (foundChannel == null) - throw new ArgumentException($"Unknown channel name ({args[1]})."); - else - chat.OpenChannel(foundChannel); - - break; - case "edit": - chat.Game?.LoadEditorTimestamp(); - break; - case "b": - if (args.Length > 1 && int.TryParse(args[1], out int mapId)) - beatmapSetOverlay.ShowBeatmap(mapId); - - break; - case "s": - case "dl": - if (args.Length > 1 && int.TryParse(args[1], out int mapSetId)) - beatmapSetOverlay.ShowBeatmapSet(mapSetId); - - break; - case "spectate": - GetUserRequest req; - if (int.TryParse(args[1], out int userId)) - req = new GetUserRequest(userId); - else - return; - - req.Success += user => + case "osu": + if (args.Length == 1) { - chat.Game?.LoadSpectatorScreen(); - }; - api.Queue(req); + base.OnLinkClicked(); + break; + } + switch (args[0]) + { + case "chan": + var foundChannel = chat.AvailableChannels.Find(channel => channel.Name == args[1]); + + if (foundChannel == null) + throw new ArgumentException($"Unknown channel name ({args[1]})."); + else + chat.OpenChannel(foundChannel); + + break; + case "edit": + chat.Game?.LoadEditorTimestamp(); + break; + case "b": + if (args.Length > 1 && int.TryParse(args[1], out int mapId)) + beatmapSetOverlay.ShowBeatmap(mapId); + + break; + case "s": + case "dl": + if (args.Length > 1 && int.TryParse(args[1], out int mapSetId)) + beatmapSetOverlay.ShowBeatmapSet(mapSetId); + + break; + case "spectate": + GetUserRequest req; + if (int.TryParse(args[1], out int userId)) + req = new GetUserRequest(userId); + else + return; + + req.Success += user => + { + chat.Game?.LoadSpectatorScreen(); + }; + api.Queue(req); + + break; + default: + throw new ArgumentException($"Unknown osu:// link at {nameof(ChatLink)} ({urlMatch.Groups["content"].Value})."); + } + + break; + case "osump": + if (args.Length > 1 && int.TryParse(args[1], out int multiId)) + chat.Game?.LoadMultiplayerLobby(multiId); + + break; + case "http": + case "https": + if (args[0] == "osu.ppy.sh" && args.Length > 2) + { + switch (args[1]) + { + case "b": + case "beatmaps": + beatmapSetOverlay.ShowBeatmap(getId(args[2])); + break; + case "s": + case "beatmapsets": + case "d": + beatmapSetOverlay.ShowBeatmapSet(getId(args[2])); + break; + default: + base.OnLinkClicked(); + break; + } + } + else + base.OnLinkClicked(); break; default: - throw new ArgumentException($"Unknown osu:// link at {nameof(OsuLinkSpriteText)} (https://osu.ppy.sh/{args[0]})."); + base.OnLinkClicked(); + break; } } - else if (url.StartsWith("osump://")) - { - url = url.Substring(8); - if (!int.TryParse(url.Split('/').ElementAtOrDefault(1), out int multiId)) - return; - - chat.Game?.LoadMultiplayerLobby(multiId); - } - else if (url.StartsWith("http://") || url.StartsWith("https://") && url.IndexOf("osu.ppy.sh/", StringComparison.InvariantCultureIgnoreCase) != -1) - { - var osuUrlIndex = url.IndexOf("osu.ppy.sh/", StringComparison.InvariantCultureIgnoreCase); - - url = url.Substring(osuUrlIndex + 11); - if (url.StartsWith("s/") || url.StartsWith("beatmapsets/") || url.StartsWith("d/")) - { - var id = getIdFromUrl(url); - beatmapSetOverlay.ShowBeatmapSet(id); - } - else if (url.StartsWith("b/") || url.StartsWith("beatmaps/")) - { - var id = getIdFromUrl(url); - beatmapSetOverlay.ShowBeatmap(id); - } - else - base.OnLinkClicked(); - } else base.OnLinkClicked(); } - private int getIdFromUrl(string url) => int.Parse(Regex.Match(url, @"\/(\d+)\/?").Groups[1].Value); + private int getId(string input) + { + var index = input.IndexOf('#'); + return int.Parse(index > 0 ? input.Remove(index) : input); + } public string TooltipText { From d0b7c92b463c1e536caa232732c4f5a000c5a88b Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Mon, 11 Dec 2017 13:46:23 +0100 Subject: [PATCH 067/138] Removed unnecessary usings. --- osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs | 2 -- osu.Game/Online/Chat/ChatLink.cs | 2 -- osu.Game/Overlays/Profile/ProfileHeader.cs | 1 - 3 files changed, 5 deletions(-) diff --git a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs index cc40ac2214..15c607b593 100644 --- a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs @@ -3,9 +3,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Containers; using osu.Framework.Input; -using osu.Game.Graphics.Containers; using System.Collections.Generic; using System.Diagnostics; diff --git a/osu.Game/Online/Chat/ChatLink.cs b/osu.Game/Online/Chat/ChatLink.cs index d251580812..917de7bd70 100644 --- a/osu.Game/Online/Chat/ChatLink.cs +++ b/osu.Game/Online/Chat/ChatLink.cs @@ -4,12 +4,10 @@ using OpenTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Input; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 7bdd09e2e0..26352b756d 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -17,7 +17,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Users; using osu.Framework.Graphics.Cursor; -using osu.Framework.Input; namespace osu.Game.Overlays.Profile { From 4f8bec8dfc30603841aa026a4127ccb1b0a18aea Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Tue, 19 Dec 2017 19:35:42 +0100 Subject: [PATCH 068/138] Removed makeshift fix that has already been addressed elsewhere. --- osu.Game/Overlays/BeatmapSet/PreviewButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/PreviewButton.cs b/osu.Game/Overlays/BeatmapSet/PreviewButton.cs index ef248c02d3..52edd1714f 100644 --- a/osu.Game/Overlays/BeatmapSet/PreviewButton.cs +++ b/osu.Game/Overlays/BeatmapSet/PreviewButton.cs @@ -80,7 +80,7 @@ namespace osu.Game.Overlays.BeatmapSet { base.Update(); - if (Playing.Value && preview != null && preview.Length > 0) + if (Playing.Value && preview != null) { progress.Width = (float)(preview.CurrentTime / preview.Length); } From 270d81f8169c5a84347cd93c10d96858ecc1a56f Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Tue, 19 Dec 2017 19:37:43 +0100 Subject: [PATCH 069/138] Made the Link's OnClick() Method return true --- osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs index 15c607b593..e76b1781c4 100644 --- a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs @@ -16,7 +16,7 @@ namespace osu.Game.Graphics.Sprites protected override bool OnClick(InputState state) { OnLinkClicked(); - return base.OnClick(state); + return true; } private string url; From fc0b97065cd4d8e8e13460d55e67805ecec7e69e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 20 Dec 2017 15:54:13 +0900 Subject: [PATCH 070/138] Move hover sound/container to base implementation --- osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs | 11 +++++++++++ osu.Game/Overlays/Profile/ProfileHeader.cs | 6 ------ 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs index e76b1781c4..897df39d0c 100644 --- a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs @@ -6,6 +6,8 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Input; using System.Collections.Generic; using System.Diagnostics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Containers; namespace osu.Game.Graphics.Sprites { @@ -13,6 +15,15 @@ namespace osu.Game.Graphics.Sprites { protected override IEnumerable FlowingChildren => Children; + protected override Container Content => content; + + private readonly Container content; + + public OsuLinkSpriteText() + { + AddInternal(content = new OsuHoverContainer { AutoSizeAxes = Axes.Both }); + } + protected override bool OnClick(InputState state) { OnLinkClicked(); diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index dcf7d844a2..aa3e6ea90c 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -490,16 +490,10 @@ namespace osu.Game.Overlays.Profile { public string TooltipText => "View Profile in Browser"; - private readonly OsuHoverContainer content; - - protected override Container Content => content; - public override bool HandleInput => true; public ProfileLink(User user) { - AddInternal(content = new OsuHoverContainer { AutoSizeAxes = Axes.Both }); - Text = user.Username; Url = $@"https://osu.ppy.sh/users/{user.Id}"; Font = @"Exo2.0-RegularItalic"; From 025d3941a284041be125c522ae4430199e9fc693 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Mon, 25 Dec 2017 19:43:35 +0100 Subject: [PATCH 071/138] Fixed problems introduced by the merge --- osu.Game.Tests/Visual/TestCaseChatLink.cs | 2 +- osu.Game/OsuGame.cs | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index 6a36411407..b8e400aa60 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -13,7 +13,7 @@ using System.Linq; namespace osu.Game.Tests.Visual { - internal class TestCaseChatLink : OsuTestCase + public class TestCaseChatLink : OsuTestCase { private readonly BeatmapSetOverlay beatmapSetOverlay; private readonly ChatOverlay chat; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 3dcff9f66f..66d2347f47 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -118,7 +118,7 @@ namespace osu.Game // TODO: Implement this properly as soon as the Editor is done internal void LoadEditorTimestamp() { - notificationOverlay.Post(new SimpleNotification + notifications.Post(new SimpleNotification { Text = @"Sorry, but this is not fully implemented yet!", Icon = FontAwesome.fa_life_saver, @@ -127,7 +127,7 @@ namespace osu.Game internal void LoadSpectatorScreen() { - notificationOverlay.Post(new SimpleNotification + notifications.Post(new SimpleNotification { Text = @"Sorry, but spectating is not implemented yet!", Icon = FontAwesome.fa_life_saver, @@ -136,7 +136,7 @@ namespace osu.Game internal void LoadMultiplayerLobby(int lobbyId) { - notificationOverlay.Post(new SimpleNotification + notifications.Post(new SimpleNotification { Text = @"Sorry, but the multiplayer lobby is not implemented yet!", Icon = FontAwesome.fa_life_saver, From 962e4d7c8afeb9c0d66086466049848324db313e Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Mon, 25 Dec 2017 20:46:04 +0100 Subject: [PATCH 072/138] Removed LinkId and word wrapping (for now). Also reimplemented the OsuHoverContainer properly --- .../Graphics/Containers/OsuHoverContainer.cs | 4 +- .../Containers/OsuLinkTextFlowContainer.cs | 3 + .../Graphics/Sprites/OsuLinkSpriteText.cs | 21 ++--- osu.Game/Online/Chat/ChatLink.cs | 88 ++----------------- osu.Game/Overlays/Chat/ChatLine.cs | 7 +- 5 files changed, 26 insertions(+), 97 deletions(-) diff --git a/osu.Game/Graphics/Containers/OsuHoverContainer.cs b/osu.Game/Graphics/Containers/OsuHoverContainer.cs index 3f82ad2179..77b6b3773e 100644 --- a/osu.Game/Graphics/Containers/OsuHoverContainer.cs +++ b/osu.Game/Graphics/Containers/OsuHoverContainer.cs @@ -11,6 +11,7 @@ namespace osu.Game.Graphics.Containers public class OsuHoverContainer : OsuClickableContainer { private Color4 hoverColour; + private Color4 unhoverColour; protected override bool OnHover(InputState state) { @@ -20,7 +21,7 @@ namespace osu.Game.Graphics.Containers protected override void OnHoverLost(InputState state) { - this.FadeColour(Color4.White, 500, Easing.OutQuint); + this.FadeColour(unhoverColour, 500, Easing.OutQuint); base.OnHoverLost(state); } @@ -28,6 +29,7 @@ namespace osu.Game.Graphics.Containers private void load(OsuColour colours) { hoverColour = colours.Yellow; + unhoverColour = Colour; } } } diff --git a/osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs b/osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs index 382d2d73f4..bc23d128f2 100644 --- a/osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs +++ b/osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs @@ -36,6 +36,9 @@ namespace osu.Game.Graphics.Containers public void AddLink(string text, string url, Action creationParameters = null) { + // TODO: Remove this and get word wrapping working + text = text.Replace(' ', '_'); + AddText(text, link => { creationParameters?.Invoke(link); diff --git a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs index 897df39d0c..dc49ff4daf 100644 --- a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs @@ -21,23 +21,18 @@ namespace osu.Game.Graphics.Sprites public OsuLinkSpriteText() { - AddInternal(content = new OsuHoverContainer { AutoSizeAxes = Axes.Both }); - } - - protected override bool OnClick(InputState state) - { - OnLinkClicked(); - return true; + AddInternal(content = new OsuHoverContainer + { + AutoSizeAxes = Axes.Both, + Action = OnLinkClicked, + }); } private string url; public string Url { - get - { - return url; - } + get => url; set { if (!string.IsNullOrEmpty(value)) @@ -47,8 +42,8 @@ namespace osu.Game.Graphics.Sprites public ColourInfo TextColour { - get { return Content.Colour; } - set { Content.Colour = value; } + 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 index 917de7bd70..43a9792e52 100644 --- a/osu.Game/Online/Chat/ChatLink.cs +++ b/osu.Game/Online/Chat/ChatLink.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Input; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; @@ -22,33 +23,11 @@ namespace osu.Game.Online.Chat { public class ChatLink : OsuLinkSpriteText, IHasTooltip { - /// - /// Identifier unique to every link in a message. - /// A value of -1 means that this instance does not contain a link. - /// - public int LinkId = -1; - private APIAccess api; private BeatmapSetOverlay beatmapSetOverlay; private ChatOverlay chat; - private Color4 hoverColour; - private Color4 urlColour; - - private readonly HoverClickSounds hoverClickSounds; - - /// - /// Every other sprite in the containing ChatLine that represents the same link. - /// - protected IEnumerable SameLinkSprites { get; private set; } - - public override bool HandleInput => LinkId != -1; - - protected override bool OnClick(InputState state) - { - hoverClickSounds.TriggerOnClick(state); - return base.OnClick(state); - } + public override bool HandleInput => !string.IsNullOrEmpty(Url); protected override void OnLinkClicked() { @@ -145,19 +124,19 @@ namespace osu.Game.Online.Chat } else base.OnLinkClicked(); - } - private int getId(string input) - { - var index = input.IndexOf('#'); - return int.Parse(index > 0 ? input.Remove(index) : input); + int getId(string input) + { + var index = input.IndexOf('#'); + return int.Parse(index > 0 ? input.Remove(index) : input); + } } public string TooltipText { get { - if (LinkId == -1 || Url == Text) + if (Url == Text) return null; if (Url.StartsWith("osu://")) @@ -177,63 +156,12 @@ namespace osu.Game.Online.Chat } } - public ChatLink() - { - hoverClickSounds = new HoverClickSounds(); - - OnLoadComplete = d => - { - // All sprites in the same chatline that represent the same URL - SameLinkSprites = ((Container)d.Parent).Children.Where(child => (child as ChatLink)?.LinkId == LinkId && !d.Equals(child)).Cast(); - }; - } - - protected override bool OnHover(InputState state) - { - if (!SameLinkSprites.Any(sprite => sprite.IsHovered)) - { - hoverClickSounds.TriggerOnHover(state); - - foreach (ChatLink sprite in SameLinkSprites) - sprite.TriggerOnHover(state); - } - - Content.FadeColour(hoverColour, 500, Easing.OutQuint); - - return true; - } - - protected override void OnHoverLost(InputState state) - { - if (SameLinkSprites.Any(sprite => sprite.IsHovered)) - { - // We have to do this so this sprite does not fade its colour back - Content.FadeColour(hoverColour, 500, Easing.OutQuint); - return; - } - - Content.FadeColour(urlColour, 500, Easing.OutQuint); - - foreach (ChatLink sprite in SameLinkSprites) - sprite.Content.FadeColour(urlColour, 500, Easing.OutQuint); - - base.OnHoverLost(state); - } - [BackgroundDependencyLoader] private void load(APIAccess api, BeatmapSetOverlay beatmapSetOverlay, ChatOverlay chat, OsuColour colours) { - // Should be ok, inexpensive operation - LoadComponentAsync(hoverClickSounds); - this.api = api; this.beatmapSetOverlay = beatmapSetOverlay; this.chat = chat; - - hoverColour = colours.Yellow; - urlColour = colours.Blue; - if (LinkId != -1) - Content.Colour = urlColour; } } } diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 5fbfed5a67..b86b138012 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -104,6 +104,7 @@ namespace osu.Game.Overlays.Chat private void load(OsuColour colours, ChatOverlay chat) { this.chat = chat; + urlColour = colours.Blue; customUsernameColour = colours.ChatBlue; } @@ -204,6 +205,7 @@ namespace osu.Game.Overlays.Chat } private ChatOverlay chat; + private Color4 urlColour; private void updateMessageContent() { @@ -244,11 +246,10 @@ namespace osu.Game.Overlays.Chat contentFlow.AddLink(message.Content.Substring(link.Index, link.Length), link.Url, sprite => { + ((OsuLinkSpriteText)sprite).TextColour = urlColour; + if (message.IsAction) sprite.Font = @"Exo2.0-MediumItalic"; - - // We want to use something that is unique to every formatted link PER MESSAGE - ((ChatLink)sprite).LinkId = link.Index; }); } From 7c49becc83709d3676c088eb148073d5539a8882 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Mon, 25 Dec 2017 20:56:20 +0100 Subject: [PATCH 073/138] CI adjustments --- osu.Game/Online/Chat/ChatLink.cs | 11 +---------- 1 file changed, 1 insertion(+), 10 deletions(-) diff --git a/osu.Game/Online/Chat/ChatLink.cs b/osu.Game/Online/Chat/ChatLink.cs index 43a9792e52..ccf146286b 100644 --- a/osu.Game/Online/Chat/ChatLink.cs +++ b/osu.Game/Online/Chat/ChatLink.cs @@ -1,22 +1,13 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using OpenTK.Graphics; using osu.Framework.Allocation; -using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; -using osu.Framework.Input; -using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Overlays; using System; -using System.Collections.Generic; -using System.Linq; using System.Text.RegularExpressions; namespace osu.Game.Online.Chat @@ -157,7 +148,7 @@ namespace osu.Game.Online.Chat } [BackgroundDependencyLoader] - private void load(APIAccess api, BeatmapSetOverlay beatmapSetOverlay, ChatOverlay chat, OsuColour colours) + private void load(APIAccess api, BeatmapSetOverlay beatmapSetOverlay, ChatOverlay chat) { this.api = api; this.beatmapSetOverlay = beatmapSetOverlay; From cbfef7052e2615390c0750d175e58c0b5ab8ed69 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Mon, 25 Dec 2017 21:02:48 +0100 Subject: [PATCH 074/138] further CI adjustments --- osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs index dc49ff4daf..ccf7c80d07 100644 --- a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs @@ -3,7 +3,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; -using osu.Framework.Input; using System.Collections.Generic; using System.Diagnostics; using osu.Framework.Graphics.Containers; From c4f5754f947bbe13fd639fa547c3a58bf403ac9b Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Wed, 27 Dec 2017 02:14:45 +0100 Subject: [PATCH 075/138] Fixed only the links being italics for /me messages --- osu.Game/Overlays/Chat/ChatLine.cs | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index b86b138012..435399b670 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -189,7 +189,12 @@ namespace osu.Game.Overlays.Chat Padding = new MarginPadding { Left = message_padding + padding }, Children = new Drawable[] { - contentFlow = new OsuLinkTextFlowContainer(t => { t.TextSize = text_size; }) + contentFlow = new OsuLinkTextFlowContainer(t => + { + if (Message.IsAction) + t.Font = "Exo2.0-MediumItalic"; + t.TextSize = text_size; + }) { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, @@ -218,13 +223,7 @@ namespace osu.Game.Overlays.Chat contentFlow.Clear(); if (message.Links == null || message.Links.Count == 0) - { - contentFlow.AddText(message.Content, sprite => - { - if (message.IsAction) - sprite.Font = @"Exo2.0-MediumItalic"; - }); - } + contentFlow.AddText(message.Content); else { int prevIndex = 0; @@ -247,9 +246,6 @@ namespace osu.Game.Overlays.Chat contentFlow.AddLink(message.Content.Substring(link.Index, link.Length), link.Url, sprite => { ((OsuLinkSpriteText)sprite).TextColour = urlColour; - - if (message.IsAction) - sprite.Font = @"Exo2.0-MediumItalic"; }); } From d21ef14f750e9f0145412299752a6cff0aa39b9c Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Wed, 27 Dec 2017 17:14:08 +0100 Subject: [PATCH 076/138] Better style; removed initial sprites --- osu.Game.Tests/Visual/TestCaseChatLink.cs | 37 ++++++++++++----------- 1 file changed, 19 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index b8e400aa60..ca102963cb 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -21,7 +21,6 @@ namespace osu.Game.Tests.Visual private DependencyContainer dependencies; private readonly TestChatLineContainer textContainer; - private readonly ChatLine[] testSprites; protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); @@ -38,21 +37,25 @@ namespace osu.Game.Tests.Visual Direction = FillDirection.Vertical, }); - testSprites = new[] - { - new ChatLine(new DummyMessage("Test!")), - new ChatLine(new DummyMessage("osu.ppy.sh!")), - new ChatLine(new DummyMessage("http://lookatmy.horse/")), - new ChatLine(new DummyMessage("https://osu.ppy.sh!")), - new ChatLine(new DummyMessage("00:12:345 (1,2) - Test?")), - new ChatLine(new DummyMessage("Wiki link for tasty [[Performance Points]]")), - new ChatLine(new DummyMessage("(osu forums)[https://osu.ppy.sh/forum] (old link format)")), - new ChatLine(new DummyMessage("[https://osu.ppy.sh/home New site] (new link format)")), - new ChatLine(new DummyMessage("long message to test word wrap: use https://encrypted.google.com instead of https://google.com or even worse, [http://google.com Unencrypted google]")), - new ChatLine(new DummyMessage("is now listening to [https://osu.ppy.sh/s/93523 IMAGE -MATERIAL- ]", true)), - new ChatLine(new DummyMessage("is now playing [https://osu.ppy.sh/b/252238 IMAGE -MATERIAL- ]", true)), - new ChatLine(new DummyMessage("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present).")), - }; + testAddLinks(); + } + + private void testAddLinks() + { + int msgCounter = 0; + void addMessage(string text, bool isAction = false) => AddStep($"Add message #{++msgCounter}", () => textContainer.Add(new ChatLine(new DummyMessage(text, isAction)))); + + addMessage("Test!"); + addMessage("osu.ppy.sh!"); + addMessage("https://osu.ppy.sh!"); + addMessage("00:12:345 (1,2) - Test?"); + addMessage("Wiki link for tasty [[Performance Points]]"); + addMessage("(osu forums)[https://osu.ppy.sh/forum] (old link format)"); + addMessage("[https://osu.ppy.sh/home New site] (new link format)"); + addMessage("[https://osu.ppy.sh/home This is only a link to the new osu webpage but this is supposed to test word wrap.]"); + addMessage("is now listening to [https://osu.ppy.sh/s/93523 IMAGE -MATERIAL- ]", true); + addMessage("is now playing [https://osu.ppy.sh/b/252238 IMAGE -MATERIAL- ]", true); + addMessage("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present)."); } [BackgroundDependencyLoader] @@ -60,8 +63,6 @@ namespace osu.Game.Tests.Visual { dependencies.Cache(chat); dependencies.Cache(beatmapSetOverlay); - - textContainer.AddRange(testSprites); } private class DummyMessage : Message From 4681a0c47b3118a202fec27afeb4cf576607d123 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Wed, 27 Dec 2017 21:06:48 +0100 Subject: [PATCH 077/138] handle chat == null gracefully and remove link from message if channel not found --- osu.Game/Overlays/Chat/ChatLine.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 435399b670..455fbaace7 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -15,6 +15,7 @@ using osu.Game.Users; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; +using System.Collections.Generic; namespace osu.Game.Overlays.Chat { @@ -227,6 +228,8 @@ namespace osu.Game.Overlays.Chat else { int prevIndex = 0; + List linksToRemove = new List(); + foreach (var link in message.Links) { contentFlow.AddText(message.Content.Substring(prevIndex, link.Index - prevIndex)); @@ -236,8 +239,9 @@ namespace osu.Game.Overlays.Chat if (link.Url.StartsWith("osu://chan/")) { var channelName = link.Url.Substring(11).Split('/')[0]; - if (chat.AvailableChannels.TrueForAll(c => c.Name != channelName)) + if (chat?.AvailableChannels.TrueForAll(c => c.Name != channelName) != false) { + linksToRemove.Add(link); contentFlow.AddText(message.Content.Substring(link.Index, link.Length)); continue; } @@ -245,10 +249,13 @@ namespace osu.Game.Overlays.Chat contentFlow.AddLink(message.Content.Substring(link.Index, link.Length), link.Url, sprite => { - ((OsuLinkSpriteText)sprite).TextColour = urlColour; + ((ChatLink)sprite).TextColour = urlColour; }); } + foreach (var link in linksToRemove) + message.Links.Remove(link); + var lastLink = message.Links[message.Links.Count - 1]; contentFlow.AddText(message.Content.Substring(lastLink.Index + lastLink.Length)); } From 54a11e24b7455859963ba4db0726e8c9c26cd921 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Wed, 27 Dec 2017 21:13:32 +0100 Subject: [PATCH 078/138] Fixed links being removed too early --- osu.Game/Overlays/Chat/ChatLine.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 455fbaace7..aecfa27a66 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -253,11 +253,11 @@ namespace osu.Game.Overlays.Chat }); } - foreach (var link in linksToRemove) - message.Links.Remove(link); - var lastLink = message.Links[message.Links.Count - 1]; contentFlow.AddText(message.Content.Substring(lastLink.Index + lastLink.Length)); + + foreach (var link in linksToRemove) + message.Links.Remove(link); } } From 128603a99f26d5aea4e91838373fae22a55e09e1 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 28 Dec 2017 01:12:13 +0100 Subject: [PATCH 079/138] Added more and fixed the old automated tests --- osu.Game.Tests/Visual/TestCaseChatLink.cs | 98 ++++++++++++++++++----- osu.Game/Overlays/Chat/ChatLine.cs | 3 + 2 files changed, 83 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index ca102963cb..15a5e12546 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -1,8 +1,10 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using OpenTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Game.Online.Chat; using osu.Game.Overlays; @@ -37,25 +39,75 @@ namespace osu.Game.Tests.Visual Direction = FillDirection.Vertical, }); - testAddLinks(); + testLinksGeneral(); + testAddingLinks(); + testEcho(); } - private void testAddLinks() - { - int msgCounter = 0; - void addMessage(string text, bool isAction = false) => AddStep($"Add message #{++msgCounter}", () => textContainer.Add(new ChatLine(new DummyMessage(text, isAction)))); + private void clear() => AddStep("clear messages", textContainer.Clear); - addMessage("Test!"); - addMessage("osu.ppy.sh!"); - addMessage("https://osu.ppy.sh!"); - addMessage("00:12:345 (1,2) - Test?"); - addMessage("Wiki link for tasty [[Performance Points]]"); - addMessage("(osu forums)[https://osu.ppy.sh/forum] (old link format)"); - addMessage("[https://osu.ppy.sh/home New site] (new link format)"); - addMessage("[https://osu.ppy.sh/home This is only a link to the new osu webpage but this is supposed to test word wrap.]"); - addMessage("is now listening to [https://osu.ppy.sh/s/93523 IMAGE -MATERIAL- ]", true); - addMessage("is now playing [https://osu.ppy.sh/b/252238 IMAGE -MATERIAL- ]", true); - addMessage("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present)."); + private void addMessageWithChecks(string text, int linkAmount = 0, bool isAction = false) + { + var newLine = new ChatLine(new DummyMessage(text, isAction)); + textContainer.Add(newLine); + + AddAssert($"check msg having {linkAmount} link(s)", () => newLine.Message.Links.Count == linkAmount); + // todo: tidy this lambda up + AddAssert("check link(s) displaying", () => newLine.ContentFlow.Any() + && newLine.ContentFlow + .Cast() + .All(sprite => sprite.HandleInput && !sprite.TextColour.Equals((SRGBColour)Color4.White) + || !sprite.HandleInput && sprite.TextColour.Equals((SRGBColour)Color4.White))); + } + + private void testLinksGeneral() + { + addMessageWithChecks("test!"); + addMessageWithChecks("osu.ppy.sh!"); + addMessageWithChecks("https://osu.ppy.sh!", 1); + addMessageWithChecks("00:12:345 (1,2) - Test?", 1); + addMessageWithChecks("Wiki link for tasty [[Performance Points]]", 1); + addMessageWithChecks("(osu forums)[https://osu.ppy.sh/forum] (old link format)", 1); + addMessageWithChecks("[https://osu.ppy.sh/home New site] (new link format)", 1); + addMessageWithChecks("[https://osu.ppy.sh/home This is only a link to the new osu webpage but this is supposed to test word wrap.]", 1); + addMessageWithChecks("is now listening to [https://osu.ppy.sh/s/93523 IMAGE -MATERIAL- ]", 1, true); + addMessageWithChecks("is now playing [https://osu.ppy.sh/b/252238 IMAGE -MATERIAL- ]", 1, true); + addMessageWithChecks("Let's (try)[https://osu.ppy.sh/home] [https://osu.ppy.sh/home multiple links] https://osu.ppy.sh/home", 3); + // note that there's 0 links here (they get removed if a channel is not found) + addMessageWithChecks("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present)."); + } + + private void testAddingLinks() + { + const int count = 5; + + for (int i = 1; i <= count; i++) + AddStep($"add long msg #{i}", () => textContainer.Add(new ChatLine(new DummyMessage("alright let's just put a really long text here to see if it loads in correctly rather than adding the text sprites individually after the chat line appearing!")))); + + clear(); + } + + private void testEcho() + { + int echoCounter = 0; + + addEchoWithWait("sent!", "received!"); + addEchoWithWait("https://osu.ppy.sh/home", null, 500); + addEchoWithWait("[https://osu.ppy.sh/forum let's try multiple words too!]"); + addEchoWithWait("(long loading times! clickable while loading?)[https://osu.ppy.sh/home]", null, 5000); + + void addEchoWithWait(string text, string completeText = null, double delay = 250) + { + var newLine = new ChatLine(new DummyEchoMessage(text)); + + AddStep($"send msg #{++echoCounter} after {delay}ms", () => + { + textContainer.Add(newLine); + Scheduler.AddDelayed(() => newLine.Message = new DummyMessage(completeText ?? text), delay); + }); + + AddUntilStep(() => textContainer.All(line => line.Message is DummyMessage), $"wait for msg #{echoCounter}"); + } } [BackgroundDependencyLoader] @@ -65,10 +117,20 @@ namespace osu.Game.Tests.Visual dependencies.Cache(beatmapSetOverlay); } + private class DummyEchoMessage : LocalEchoMessage + { + public DummyEchoMessage(string text) + { + Content = text; + Timestamp = DateTimeOffset.Now; + Sender = DummyMessage.TEST_SENDER; + } + } + private class DummyMessage : Message { private static long messageCounter; - private static readonly User sender = new User + internal static readonly User TEST_SENDER = new User { Username = @"Somebody", Id = 1, @@ -98,7 +160,7 @@ namespace osu.Game.Tests.Visual { Content = text; IsAction = isAction; - Sender = sender; + Sender = TEST_SENDER; } } diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index aecfa27a66..26759825ee 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -101,6 +101,9 @@ namespace osu.Game.Overlays.Chat } } + // this is only used for testing + public OsuTextFlowContainer ContentFlow => contentFlow; + [BackgroundDependencyLoader(true)] private void load(OsuColour colours, ChatOverlay chat) { From 7454633f63e48cf5618b143366f76912db1ba143 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 28 Dec 2017 20:11:21 +0100 Subject: [PATCH 080/138] Refactor and general tidying up --- osu.Game.Tests/Visual/TestCaseChatLink.cs | 13 ++-- .../Containers/OsuLinkFlowContainer.cs | 59 ++++++++++++++++++ .../Containers/OsuLinkTextFlowContainer.cs | 60 ------------------- ...{OsuLinkSpriteText.cs => OsuSpriteLink.cs} | 4 +- osu.Game/Online/Chat/ChatLink.cs | 22 +++---- osu.Game/Overlays/Chat/ChatLine.cs | 10 ++-- osu.Game/Overlays/Profile/ProfileHeader.cs | 6 +- osu.Game/osu.Game.csproj | 4 +- 8 files changed, 89 insertions(+), 89 deletions(-) create mode 100644 osu.Game/Graphics/Containers/OsuLinkFlowContainer.cs delete mode 100644 osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs rename osu.Game/Graphics/Sprites/{OsuLinkSpriteText.cs => OsuSpriteLink.cs} (90%) diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index 15a5e12546..31544c9364 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Containers; using osu.Game.Online.Chat; using osu.Game.Overlays; using osu.Game.Overlays.Chat; @@ -51,13 +52,11 @@ namespace osu.Game.Tests.Visual var newLine = new ChatLine(new DummyMessage(text, isAction)); textContainer.Add(newLine); - AddAssert($"check msg having {linkAmount} link(s)", () => newLine.Message.Links.Count == linkAmount); - // todo: tidy this lambda up - AddAssert("check link(s) displaying", () => newLine.ContentFlow.Any() - && newLine.ContentFlow - .Cast() - .All(sprite => sprite.HandleInput && !sprite.TextColour.Equals((SRGBColour)Color4.White) - || !sprite.HandleInput && sprite.TextColour.Equals((SRGBColour)Color4.White))); + AddAssert($"msg #{textContainer.Count} has {linkAmount} link(s)", () => newLine.Message.Links.Count == linkAmount); + AddAssert($"msg #{textContainer.Count} shows link(s)", () => newLine.ContentFlow.Any() && isShowingLinks(newLine.ContentFlow)); + + bool isShowingLinks(OsuTextFlowContainer c) => c.Cast().All(sprite => sprite.HandleInput && !sprite.TextColour.Equals((SRGBColour)Color4.White) + || !sprite.HandleInput && sprite.TextColour.Equals((SRGBColour)Color4.White)); } private void testLinksGeneral() diff --git a/osu.Game/Graphics/Containers/OsuLinkFlowContainer.cs b/osu.Game/Graphics/Containers/OsuLinkFlowContainer.cs new file mode 100644 index 0000000000..5f318668a2 --- /dev/null +++ b/osu.Game/Graphics/Containers/OsuLinkFlowContainer.cs @@ -0,0 +1,59 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK.Graphics; +using osu.Framework.Graphics.Colour; +using osu.Framework.Graphics.Sprites; +using osu.Game.Graphics.Sprites; +using System; +using System.Collections.Generic; + +namespace osu.Game.Graphics.Containers +{ + public class OsuLinkFlowContainer : OsuLinkFlowContainer + { + public OsuLinkFlowContainer(Action defaultCreationParameters = null) + : base(defaultCreationParameters) + { + } + } + + public class OsuLinkFlowContainer : OsuTextFlowContainer + where T : OsuSpriteLink, new() + { + public override bool HandleInput => true; + + public OsuLinkFlowContainer(Action defaultCreationParameters = null) : base(defaultCreationParameters) + { + } + + protected override SpriteText CreateSpriteText() => new T(); + + /// + /// The colour for text (links override this). Will only be used for new text elements. + /// + public ColourInfo TextColour = Color4.White; + + public IEnumerable AddLink(string text, string url, Action creationParameters = null) + { + // TODO: Remove this and get word wrapping working + text = text.Replace(' ', '_'); + + return AddText(text, link => + { + creationParameters?.Invoke(link); + ((T)link).Url = url; + }); + } + + public new IEnumerable AddText(string text, Action creationParameters = null) + { + return base.AddText(text, sprite => + { + ((OsuSpriteLink)sprite).TextColour = TextColour; + + creationParameters?.Invoke(sprite); + }); + } + } +} diff --git a/osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs b/osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs deleted file mode 100644 index bc23d128f2..0000000000 --- a/osu.Game/Graphics/Containers/OsuLinkTextFlowContainer.cs +++ /dev/null @@ -1,60 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics.Sprites; -using System; -using System.Collections.Generic; - -namespace osu.Game.Graphics.Containers -{ - public class OsuLinkTextFlowContainer : OsuLinkTextFlowContainer - { - public OsuLinkTextFlowContainer(Action defaultCreationParameters = null) - : base(defaultCreationParameters) - { - } - } - - public class OsuLinkTextFlowContainer : OsuTextFlowContainer - where T : OsuLinkSpriteText, new() - { - public override bool HandleInput => true; - - public OsuLinkTextFlowContainer(Action defaultCreationParameters = null) : base(defaultCreationParameters) - { - } - - protected override SpriteText CreateSpriteText() => new T(); - - /// - /// The colour for normal text (links ignore this). Will only be used for new text elements. - /// Default is white. - /// - public ColourInfo? TextColour; - - public void AddLink(string text, string url, Action creationParameters = null) - { - // TODO: Remove this and get word wrapping working - text = text.Replace(' ', '_'); - - AddText(text, link => - { - creationParameters?.Invoke(link); - LoadComponentAsync(link, d => ((T)d).Url = url); - }); - } - - public IEnumerable AddText(string text, Action creationParameters = null) - { - return base.AddText(text, sprite => - { - if (TextColour.HasValue) - ((OsuLinkSpriteText)sprite).TextColour = TextColour.Value; - - creationParameters?.Invoke(sprite); - }); - } - } -} diff --git a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs b/osu.Game/Graphics/Sprites/OsuSpriteLink.cs similarity index 90% rename from osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs rename to osu.Game/Graphics/Sprites/OsuSpriteLink.cs index ccf7c80d07..e42337dff3 100644 --- a/osu.Game/Graphics/Sprites/OsuLinkSpriteText.cs +++ b/osu.Game/Graphics/Sprites/OsuSpriteLink.cs @@ -10,7 +10,7 @@ using osu.Game.Graphics.Containers; namespace osu.Game.Graphics.Sprites { - public class OsuLinkSpriteText : OsuSpriteText + public class OsuSpriteLink : OsuSpriteText { protected override IEnumerable FlowingChildren => Children; @@ -18,7 +18,7 @@ namespace osu.Game.Graphics.Sprites private readonly Container content; - public OsuLinkSpriteText() + public OsuSpriteLink() { AddInternal(content = new OsuHoverContainer { diff --git a/osu.Game/Online/Chat/ChatLink.cs b/osu.Game/Online/Chat/ChatLink.cs index ccf146286b..aa5a0e100b 100644 --- a/osu.Game/Online/Chat/ChatLink.cs +++ b/osu.Game/Online/Chat/ChatLink.cs @@ -12,7 +12,7 @@ using System.Text.RegularExpressions; namespace osu.Game.Online.Chat { - public class ChatLink : OsuLinkSpriteText, IHasTooltip + public class ChatLink : OsuSpriteLink, IHasTooltip { private APIAccess api; private BeatmapSetOverlay beatmapSetOverlay; @@ -20,9 +20,13 @@ namespace osu.Game.Online.Chat public override bool HandleInput => !string.IsNullOrEmpty(Url); + // 'protocol' -> 'https', 'http', 'osu', 'osump' etc. + // 'content' -> everything after '://' + private Match getUrlMatch() => Regex.Match(Url, @"^(?osu(?:mp)?|https?):\/\/(?.*)"); + protected override void OnLinkClicked() { - var urlMatch = Regex.Matches(Url, @"^(?osu(?:mp)?|https?):\/\/(?.*)")[0]; + var urlMatch = getUrlMatch(); if (urlMatch.Success) { var args = urlMatch.Groups["content"].Value.Split('/'); @@ -41,11 +45,8 @@ namespace osu.Game.Online.Chat case "chan": var foundChannel = chat.AvailableChannels.Find(channel => channel.Name == args[1]); - if (foundChannel == null) - throw new ArgumentException($"Unknown channel name ({args[1]})."); - else - chat.OpenChannel(foundChannel); - + // links should be filtered out by now if a channel doesn't exist + chat.OpenChannel(foundChannel ?? throw new ArgumentException($"Unknown channel name ({args[1]}).")); break; case "edit": chat.Game?.LoadEditorTimestamp(); @@ -130,9 +131,10 @@ namespace osu.Game.Online.Chat if (Url == Text) return null; - if (Url.StartsWith("osu://")) + var urlMatch = getUrlMatch(); + if (urlMatch.Success && urlMatch.Groups["protocol"].Value == "osu") { - var args = Url.Substring(6).Split('/'); + var args = urlMatch.Groups["content"].Value.Split('/'); if (args.Length < 2) return Url; @@ -140,7 +142,7 @@ namespace osu.Game.Online.Chat if (args[0] == "chan") return "Switch to channel " + args[1]; if (args[0] == "edit") - return "Go to " + args[1].Remove(9).TrimEnd(); + return "Go to " + args[1]; } return Url; diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 26759825ee..add0bb1fd8 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -83,7 +83,10 @@ namespace osu.Game.Overlays.Chat private Message message; private OsuSpriteText username; - private OsuLinkTextFlowContainer contentFlow; + private OsuLinkFlowContainer contentFlow; + + // this is only used for testing + public OsuTextFlowContainer ContentFlow => contentFlow; public Message Message { @@ -101,9 +104,6 @@ namespace osu.Game.Overlays.Chat } } - // this is only used for testing - public OsuTextFlowContainer ContentFlow => contentFlow; - [BackgroundDependencyLoader(true)] private void load(OsuColour colours, ChatOverlay chat) { @@ -193,7 +193,7 @@ namespace osu.Game.Overlays.Chat Padding = new MarginPadding { Left = message_padding + padding }, Children = new Drawable[] { - contentFlow = new OsuLinkTextFlowContainer(t => + contentFlow = new OsuLinkFlowContainer(t => { if (Message.IsAction) t.Font = "Exo2.0-MediumItalic"; diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 2a682aa654..f1efbbc54a 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.Profile public class ProfileHeader : Container { private readonly OsuTextFlowContainer infoTextLeft; - private readonly OsuLinkTextFlowContainer infoTextRight; + private readonly OsuLinkFlowContainer infoTextRight; private readonly FillFlowContainer scoreText, scoreNumberText; private readonly RankGraph rankGraph; @@ -142,7 +142,7 @@ namespace osu.Game.Overlays.Profile ParagraphSpacing = 0.8f, LineSpacing = 0.2f }, - infoTextRight = new OsuLinkTextFlowContainer(t => + infoTextRight = new OsuLinkFlowContainer(t => { t.TextSize = 14; t.Font = @"Exo2.0-RegularItalic"; @@ -473,7 +473,7 @@ namespace osu.Game.Overlays.Profile } } - private class ProfileLink : OsuLinkSpriteText, IHasTooltip + private class ProfileLink : OsuSpriteLink, IHasTooltip { public string TooltipText => "View Profile in Browser"; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index bc7df963da..d32c458ccf 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -270,8 +270,8 @@ - - + + From ae79be7b513ea30ce8eeb8385806b9e7b8a54ed2 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 28 Dec 2017 21:45:58 +0100 Subject: [PATCH 081/138] small style fixes plus new assert in test --- osu.Game.Tests/Visual/TestCaseChatLink.cs | 5 ++++- .../Graphics/Containers/OsuLinkFlowContainer.cs | 6 +++--- osu.Game/Graphics/Sprites/OsuSpriteLink.cs | 2 +- osu.Game/Overlays/Chat/ChatLine.cs | 14 +++++++------- 4 files changed, 15 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index 31544c9364..1ad297d1f9 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -53,7 +53,10 @@ namespace osu.Game.Tests.Visual textContainer.Add(newLine); AddAssert($"msg #{textContainer.Count} has {linkAmount} link(s)", () => newLine.Message.Links.Count == linkAmount); - AddAssert($"msg #{textContainer.Count} shows link(s)", () => newLine.ContentFlow.Any() && isShowingLinks(newLine.ContentFlow)); + AddAssert($"msg #{textContainer.Count} is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic(newLine.ContentFlow)); + AddAssert($"msg #{textContainer.Count} shows link(s)", () => isShowingLinks(newLine.ContentFlow)); + + bool isItalic(OsuTextFlowContainer c) => c.Cast().All(sprite => sprite.Font == @"Exo2.0-MediumItalic"); bool isShowingLinks(OsuTextFlowContainer c) => c.Cast().All(sprite => sprite.HandleInput && !sprite.TextColour.Equals((SRGBColour)Color4.White) || !sprite.HandleInput && sprite.TextColour.Equals((SRGBColour)Color4.White)); diff --git a/osu.Game/Graphics/Containers/OsuLinkFlowContainer.cs b/osu.Game/Graphics/Containers/OsuLinkFlowContainer.cs index 5f318668a2..48910a04ca 100644 --- a/osu.Game/Graphics/Containers/OsuLinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/OsuLinkFlowContainer.cs @@ -23,7 +23,8 @@ namespace osu.Game.Graphics.Containers { public override bool HandleInput => true; - public OsuLinkFlowContainer(Action defaultCreationParameters = null) : base(defaultCreationParameters) + public OsuLinkFlowContainer(Action defaultCreationParameters = null) + : base(defaultCreationParameters) { } @@ -41,8 +42,8 @@ namespace osu.Game.Graphics.Containers return AddText(text, link => { + ((OsuSpriteLink)link).Url = url; creationParameters?.Invoke(link); - ((T)link).Url = url; }); } @@ -51,7 +52,6 @@ namespace osu.Game.Graphics.Containers return base.AddText(text, sprite => { ((OsuSpriteLink)sprite).TextColour = TextColour; - creationParameters?.Invoke(sprite); }); } diff --git a/osu.Game/Graphics/Sprites/OsuSpriteLink.cs b/osu.Game/Graphics/Sprites/OsuSpriteLink.cs index e42337dff3..1c2a219b53 100644 --- a/osu.Game/Graphics/Sprites/OsuSpriteLink.cs +++ b/osu.Game/Graphics/Sprites/OsuSpriteLink.cs @@ -16,7 +16,7 @@ namespace osu.Game.Graphics.Sprites protected override Container Content => content; - private readonly Container content; + private readonly OsuHoverContainer content; public OsuSpriteLink() { diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index add0bb1fd8..e877a35a75 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -85,7 +85,6 @@ namespace osu.Game.Overlays.Chat private OsuSpriteText username; private OsuLinkFlowContainer contentFlow; - // this is only used for testing public OsuTextFlowContainer ContentFlow => contentFlow; public Message Message @@ -196,7 +195,7 @@ namespace osu.Game.Overlays.Chat contentFlow = new OsuLinkFlowContainer(t => { if (Message.IsAction) - t.Font = "Exo2.0-MediumItalic"; + t.Font = @"Exo2.0-MediumItalic"; t.TextSize = text_size; }) { @@ -230,18 +229,19 @@ namespace osu.Game.Overlays.Chat contentFlow.AddText(message.Content); else { - int prevIndex = 0; + int lastLinkEndIndex = 0; List linksToRemove = new List(); foreach (var link in message.Links) { - contentFlow.AddText(message.Content.Substring(prevIndex, link.Index - prevIndex)); - prevIndex = link.Index + link.Length; + contentFlow.AddText(message.Content.Substring(lastLinkEndIndex, link.Index - lastLinkEndIndex)); + lastLinkEndIndex = link.Index + link.Length; + const string channelPrefix = "osu://chan/"; // If a channel doesn't exist, add it as normal text instead - if (link.Url.StartsWith("osu://chan/")) + if (link.Url.StartsWith(channelPrefix)) { - var channelName = link.Url.Substring(11).Split('/')[0]; + var channelName = link.Url.Substring(channelPrefix.Length).Split('/')[0]; if (chat?.AvailableChannels.TrueForAll(c => c.Name != channelName) != false) { linksToRemove.Add(link); From d66d741af27be7978ab4a25d1fdffcaf524d6070 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 28 Dec 2017 22:11:10 +0100 Subject: [PATCH 082/138] fixed constant naming --- osu.Game/Overlays/Chat/ChatLine.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index e877a35a75..ebfdadd553 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -237,11 +237,11 @@ namespace osu.Game.Overlays.Chat contentFlow.AddText(message.Content.Substring(lastLinkEndIndex, link.Index - lastLinkEndIndex)); lastLinkEndIndex = link.Index + link.Length; - const string channelPrefix = "osu://chan/"; + const string channel_link_prefix = "osu://chan/"; // If a channel doesn't exist, add it as normal text instead - if (link.Url.StartsWith(channelPrefix)) + if (link.Url.StartsWith(channel_link_prefix)) { - var channelName = link.Url.Substring(channelPrefix.Length).Split('/')[0]; + var channelName = link.Url.Substring(channel_link_prefix.Length).Split('/')[0]; if (chat?.AvailableChannels.TrueForAll(c => c.Name != channelName) != false) { linksToRemove.Add(link); From e7721d71f3a650ebb06072bb9c7006d1553e8dc8 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Sun, 31 Dec 2017 00:51:47 +0100 Subject: [PATCH 083/138] Changed chat link implementation according to review --- osu.Game.Tests/Visual/TestCaseChatLink.cs | 57 +++--- .../Graphics/Containers/ChatFlowContainer.cs | 61 +++++++ .../Containers/OsuLinkFlowContainer.cs | 59 ------- osu.Game/Graphics/Sprites/OsuSpriteLink.cs | 2 + .../Online/API/Requests/GetBeatmapRequest.cs | 19 -- osu.Game/Online/Chat/ChatLink.cs | 163 +++++------------- osu.Game/Online/Chat/Message.cs | 2 +- osu.Game/Online/Chat/MessageFormatter.cs | 115 ++++++++++-- osu.Game/OsuGame.cs | 2 +- osu.Game/Overlays/BeatmapSetOverlay.cs | 14 -- osu.Game/Overlays/Chat/ChatLine.cs | 25 ++- osu.Game/Overlays/ChatOverlay.cs | 11 +- osu.Game/Overlays/Profile/ProfileHeader.cs | 18 +- osu.Game/osu.Game.csproj | 5 +- 14 files changed, 272 insertions(+), 281 deletions(-) create mode 100644 osu.Game/Graphics/Containers/ChatFlowContainer.cs delete mode 100644 osu.Game/Graphics/Containers/OsuLinkFlowContainer.cs delete mode 100644 osu.Game/Online/API/Requests/GetBeatmapRequest.cs diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index 1ad297d1f9..9f8c3ce146 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -6,6 +6,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; +using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Online.Chat; using osu.Game.Overlays; @@ -47,19 +48,32 @@ namespace osu.Game.Tests.Visual private void clear() => AddStep("clear messages", textContainer.Clear); - private void addMessageWithChecks(string text, int linkAmount = 0, bool isAction = false) + private void addMessageWithChecks(string text, int linkAmount = 0, bool isAction = false, bool isImportant = false) { - var newLine = new ChatLine(new DummyMessage(text, isAction)); + var newLine = new ChatLine(new DummyMessage(text, isAction, isImportant)); textContainer.Add(newLine); AddAssert($"msg #{textContainer.Count} has {linkAmount} link(s)", () => newLine.Message.Links.Count == linkAmount); AddAssert($"msg #{textContainer.Count} is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic(newLine.ContentFlow)); - AddAssert($"msg #{textContainer.Count} shows link(s)", () => isShowingLinks(newLine.ContentFlow)); + AddAssert($"msg #{textContainer.Count} shows link(s)", isShowingLinks); - bool isItalic(OsuTextFlowContainer c) => c.Cast().All(sprite => sprite.Font == @"Exo2.0-MediumItalic"); + bool isItalic(ChatFlowContainer c) => c.Cast().All(sprite => sprite.Font == @"Exo2.0-MediumItalic"); - bool isShowingLinks(OsuTextFlowContainer c) => c.Cast().All(sprite => sprite.HandleInput && !sprite.TextColour.Equals((SRGBColour)Color4.White) - || !sprite.HandleInput && sprite.TextColour.Equals((SRGBColour)Color4.White)); + bool isShowingLinks() + { + SRGBColour textColour = Color4.White; + bool hasBackground = !string.IsNullOrEmpty(newLine.Message.Sender.Colour); + + if (isAction && hasBackground) + 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) + // 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)); + } } private void testLinksGeneral() @@ -77,6 +91,9 @@ namespace osu.Game.Tests.Visual addMessageWithChecks("Let's (try)[https://osu.ppy.sh/home] [https://osu.ppy.sh/home multiple links] https://osu.ppy.sh/home", 3); // note that there's 0 links here (they get removed if a channel is not found) addMessageWithChecks("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present)."); + addMessageWithChecks("I am important!", 0, false, true); + addMessageWithChecks("feels important", 0, true, true); + addMessageWithChecks("likes to post this [https://osu.ppy.sh/home link].", 1, true, true); } private void testAddingLinks() @@ -132,37 +149,27 @@ namespace osu.Game.Tests.Visual private class DummyMessage : Message { private static long messageCounter; + internal static readonly User TEST_SENDER_BACKGROUND = new User + { + Username = @"i-am-important", + Id = 42, + Colour = "#250cc9", + }; + internal static readonly User TEST_SENDER = new User { Username = @"Somebody", Id = 1, - Country = new Country { FullName = @"Alien" }, - CoverUrl = @"https://osu.ppy.sh/images/headers/profile-covers/c1.jpg", - JoinDate = DateTimeOffset.Now.AddDays(-1), - LastVisit = DateTimeOffset.Now, - Age = 1, - ProfileOrder = new[] { "me" }, - CountryRank = 1, - Statistics = new UserStatistics - { - Rank = 2148, - PP = 4567.89m - }, - RankHistory = new User.RankHistoryData - { - Mode = @"osu", - Data = Enumerable.Range(2345, 45).Concat(Enumerable.Range(2109, 40)).ToArray(), - } }; public new DateTimeOffset Timestamp = DateTimeOffset.Now; - public DummyMessage(string text, bool isAction = false) + public DummyMessage(string text, bool isAction = false, bool isImportant = false) : base(messageCounter++) { Content = text; IsAction = isAction; - Sender = TEST_SENDER; + Sender = isImportant ? TEST_SENDER_BACKGROUND : TEST_SENDER; } } diff --git a/osu.Game/Graphics/Containers/ChatFlowContainer.cs b/osu.Game/Graphics/Containers/ChatFlowContainer.cs new file mode 100644 index 0000000000..6fd06aef7c --- /dev/null +++ b/osu.Game/Graphics/Containers/ChatFlowContainer.cs @@ -0,0 +1,61 @@ +// Copyright (c) 2007-2017 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/OsuLinkFlowContainer.cs b/osu.Game/Graphics/Containers/OsuLinkFlowContainer.cs deleted file mode 100644 index 48910a04ca..0000000000 --- a/osu.Game/Graphics/Containers/OsuLinkFlowContainer.cs +++ /dev/null @@ -1,59 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using OpenTK.Graphics; -using osu.Framework.Graphics.Colour; -using osu.Framework.Graphics.Sprites; -using osu.Game.Graphics.Sprites; -using System; -using System.Collections.Generic; - -namespace osu.Game.Graphics.Containers -{ - public class OsuLinkFlowContainer : OsuLinkFlowContainer - { - public OsuLinkFlowContainer(Action defaultCreationParameters = null) - : base(defaultCreationParameters) - { - } - } - - public class OsuLinkFlowContainer : OsuTextFlowContainer - where T : OsuSpriteLink, new() - { - public override bool HandleInput => true; - - public OsuLinkFlowContainer(Action defaultCreationParameters = null) - : base(defaultCreationParameters) - { - } - - protected override SpriteText CreateSpriteText() => new T(); - - /// - /// The colour for text (links override this). Will only be used for new text elements. - /// - public ColourInfo TextColour = Color4.White; - - public IEnumerable AddLink(string text, string url, Action creationParameters = null) - { - // TODO: Remove this and get word wrapping working - text = text.Replace(' ', '_'); - - return AddText(text, link => - { - ((OsuSpriteLink)link).Url = url; - creationParameters?.Invoke(link); - }); - } - - public new IEnumerable AddText(string text, Action creationParameters = null) - { - return base.AddText(text, sprite => - { - ((OsuSpriteLink)sprite).TextColour = TextColour; - creationParameters?.Invoke(sprite); - }); - } - } -} diff --git a/osu.Game/Graphics/Sprites/OsuSpriteLink.cs b/osu.Game/Graphics/Sprites/OsuSpriteLink.cs index 1c2a219b53..9fc24651db 100644 --- a/osu.Game/Graphics/Sprites/OsuSpriteLink.cs +++ b/osu.Game/Graphics/Sprites/OsuSpriteLink.cs @@ -12,6 +12,8 @@ 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; diff --git a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs b/osu.Game/Online/API/Requests/GetBeatmapRequest.cs deleted file mode 100644 index 76d67e7afc..0000000000 --- a/osu.Game/Online/API/Requests/GetBeatmapRequest.cs +++ /dev/null @@ -1,19 +0,0 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . -// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE - -using osu.Game.Beatmaps; - -namespace osu.Game.Online.API.Requests -{ - public class GetBeatmapRequest : APIRequest - { - private readonly int beatmapId; - - public GetBeatmapRequest(int beatmapId) - { - this.beatmapId = beatmapId; - } - - protected override string Target => $@"beatmaps/{beatmapId}"; - } -} diff --git a/osu.Game/Online/Chat/ChatLink.cs b/osu.Game/Online/Chat/ChatLink.cs index aa5a0e100b..f38eade682 100644 --- a/osu.Game/Online/Chat/ChatLink.cs +++ b/osu.Game/Online/Chat/ChatLink.cs @@ -4,123 +4,57 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Cursor; using osu.Game.Graphics.Sprites; -using osu.Game.Online.API; -using osu.Game.Online.API.Requests; using osu.Game.Overlays; using System; -using System.Text.RegularExpressions; namespace osu.Game.Online.Chat { public class ChatLink : OsuSpriteLink, IHasTooltip { - private APIAccess api; private BeatmapSetOverlay beatmapSetOverlay; private ChatOverlay chat; + private OsuGame game; - public override bool HandleInput => !string.IsNullOrEmpty(Url); + /// + /// The type of action executed on clicking this link. + /// + public LinkAction LinkAction { get; set; } - // 'protocol' -> 'https', 'http', 'osu', 'osump' etc. - // 'content' -> everything after '://' - private Match getUrlMatch() => Regex.Match(Url, @"^(?osu(?:mp)?|https?):\/\/(?.*)"); + /// + /// 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() { - var urlMatch = getUrlMatch(); - if (urlMatch.Success) + switch (LinkAction) { - var args = urlMatch.Groups["content"].Value.Split('/'); - - switch (urlMatch.Groups["protocol"].Value) - { - case "osu": - if (args.Length == 1) - { - base.OnLinkClicked(); - break; - } - - switch (args[0]) - { - case "chan": - var foundChannel = chat.AvailableChannels.Find(channel => channel.Name == args[1]); - - // links should be filtered out by now if a channel doesn't exist - chat.OpenChannel(foundChannel ?? throw new ArgumentException($"Unknown channel name ({args[1]}).")); - break; - case "edit": - chat.Game?.LoadEditorTimestamp(); - break; - case "b": - if (args.Length > 1 && int.TryParse(args[1], out int mapId)) - beatmapSetOverlay.ShowBeatmap(mapId); - - break; - case "s": - case "dl": - if (args.Length > 1 && int.TryParse(args[1], out int mapSetId)) - beatmapSetOverlay.ShowBeatmapSet(mapSetId); - - break; - case "spectate": - GetUserRequest req; - if (int.TryParse(args[1], out int userId)) - req = new GetUserRequest(userId); - else - return; - - req.Success += user => - { - chat.Game?.LoadSpectatorScreen(); - }; - api.Queue(req); - - break; - default: - throw new ArgumentException($"Unknown osu:// link at {nameof(ChatLink)} ({urlMatch.Groups["content"].Value})."); - } - - break; - case "osump": - if (args.Length > 1 && int.TryParse(args[1], out int multiId)) - chat.Game?.LoadMultiplayerLobby(multiId); - - break; - case "http": - case "https": - if (args[0] == "osu.ppy.sh" && args.Length > 2) - { - switch (args[1]) - { - case "b": - case "beatmaps": - beatmapSetOverlay.ShowBeatmap(getId(args[2])); - break; - case "s": - case "beatmapsets": - case "d": - beatmapSetOverlay.ShowBeatmapSet(getId(args[2])); - break; - default: - base.OnLinkClicked(); - break; - } - } - else - base.OnLinkClicked(); - break; - default: - base.OnLinkClicked(); - break; - } - } - else - base.OnLinkClicked(); - - int getId(string input) - { - var index = input.IndexOf('#'); - return int.Parse(index > 0 ? input.Remove(index) : input); + 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."); } } @@ -131,30 +65,25 @@ namespace osu.Game.Online.Chat if (Url == Text) return null; - var urlMatch = getUrlMatch(); - if (urlMatch.Success && urlMatch.Groups["protocol"].Value == "osu") + switch (LinkAction) { - var args = urlMatch.Groups["content"].Value.Split('/'); - - if (args.Length < 2) + case LinkAction.OpenChannel: + return "Switch to channel " + LinkArgument; + case LinkAction.OpenEditorTimestamp: + return "Go to " + LinkArgument; + default: return Url; - - if (args[0] == "chan") - return "Switch to channel " + args[1]; - if (args[0] == "edit") - return "Go to " + args[1]; } - - return Url; } } - [BackgroundDependencyLoader] - private void load(APIAccess api, BeatmapSetOverlay beatmapSetOverlay, ChatOverlay chat) + [BackgroundDependencyLoader(true)] + private void load(BeatmapSetOverlay beatmapSetOverlay, ChatOverlay chat, OsuGame game) { - this.api = api; this.beatmapSetOverlay = beatmapSetOverlay; this.chat = chat; + // this will be null in tests + this.game = game; } } } diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs index ac14d3a88f..cda55ad269 100644 --- a/osu.Game/Online/Chat/Message.cs +++ b/osu.Game/Online/Chat/Message.cs @@ -41,7 +41,7 @@ namespace osu.Game.Online.Chat { } - public List Links; + public List Links; public Message(long? id) { diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 81551b08f4..eadab8840f 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -39,7 +39,7 @@ namespace osu.Game.Online.Chat // Unicode emojis private static readonly Regex emoji_regex = new Regex(@"(\uD83D[\uDC00-\uDE4F])"); - private static void handleMatches(Regex regex, string display, string link, MessageFormatterResult result, int startIndex = 0) + private static void handleMatches(Regex regex, string display, string link, MessageFormatterResult result, int startIndex = 0, LinkAction? linkActionOverride = null) { int captureOffset = 0; foreach (Match m in regex.Matches(result.Text, startIndex)) @@ -66,7 +66,8 @@ namespace osu.Game.Online.Chat //since we just changed the line display text, offset any already processed links. result.Links.ForEach(l => l.Index -= l.Index > index ? m.Length - displayText.Length : 0); - result.Links.Add(new Link(linkText, index, displayText.Length)); + var details = getLinkDetails(link); + result.Links.Add(new Link(linkText, index, displayText.Length, linkActionOverride ?? details.linkType, details.linkArgument)); //adjust the offset for processing the current matches group. captureOffset += m.Length - displayText.Length; @@ -93,7 +94,70 @@ namespace osu.Game.Online.Chat } } - result.Links.Add(new Link(link, index, indexLength)); + var details = getLinkDetails(link); + result.Links.Add(new Link(link, index, indexLength, details.linkType, details.linkArgument)); + } + } + + private static (LinkAction linkType, string linkArgument) getLinkDetails(string url) + { + var args = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); + args[0] = args[0].TrimEnd(':'); + + switch (args[0]) + { + case "http": + case "https": + // length > 3 since all these links need another argument to work + if (args.Length > 3 && (args[1] == "osu.ppy.sh" || args[1] == "new.ppy.sh")) + { + switch (args[2]) + { + case "b": + case "beatmaps": + return (LinkAction.OpenBeatmap, args[3]); + case "s": + case "beatmapsets": + case "d": + return (LinkAction.External, args[3]); + } + } + + return (LinkAction.External, null); + case "osu": + // every internal link also needs some kind of argument + if (args.Length < 3) + return (LinkAction.External, null); + + LinkAction linkType; + switch (args[1]) + { + case "chan": + linkType = LinkAction.OpenChannel; + break; + case "edit": + linkType = LinkAction.OpenEditorTimestamp; + break; + case "b": + linkType = LinkAction.OpenBeatmap; + break; + case "s": + case "dl": + linkType = LinkAction.OpenBeatmapSet; + break; + case "spectate": + linkType = LinkAction.Spectate; + break; + default: + linkType = LinkAction.External; + break; + } + + return (linkType, args[2]); + case "osump": + return (LinkAction.JoinMultiplayerMatch, args[1]); + default: + return (LinkAction.External, null); } } @@ -114,10 +178,10 @@ namespace osu.Game.Online.Chat handleAdvanced(advanced_link_regex, result, startIndex); // handle editor times - handleMatches(time_regex, "{0}", "osu://edit/{0}", result, startIndex); + handleMatches(time_regex, "{0}", "osu://edit/{0}", result, startIndex, LinkAction.OpenEditorTimestamp); // handle channels - handleMatches(channel_regex, "{0}", "osu://chan/{0}", result, startIndex); + handleMatches(channel_regex, "{0}", "osu://chan/{0}", result, startIndex, LinkAction.OpenChannel); var empty = ""; while (space-- > 0) @@ -151,21 +215,36 @@ namespace osu.Game.Online.Chat OriginalText = Text = text; } } + } - public class Link : IComparable + public enum LinkAction + { + External, + OpenBeatmap, + OpenBeatmapSet, + OpenChannel, + OpenEditorTimestamp, + JoinMultiplayerMatch, + Spectate, + } + + public class Link : IComparable + { + public string Url; + public int Index; + public int Length; + public LinkAction Action; + public string Argument; + + public Link(string url, int startIndex, int length, LinkAction action, string argument) { - public string Url; - public int Index; - public int Length; - - public Link(string url, int startIndex, int length) - { - Url = url; - Index = startIndex; - Length = length; - } - - public int CompareTo(Link otherLink) => Index > otherLink.Index ? 1 : -1; + Url = url; + Index = startIndex; + Length = length; + Action = action; + Argument = argument; } + + public int CompareTo(Link otherLink) => Index > otherLink.Index ? 1 : -1; } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 66d2347f47..5c26e3acab 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -134,7 +134,7 @@ namespace osu.Game }); } - internal void LoadMultiplayerLobby(int lobbyId) + internal void JoinMultiplayerMatch(int matchId) { notifications.Post(new SimpleNotification { diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 1481523bc9..0a88f586b5 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -139,20 +139,6 @@ namespace osu.Game.Overlays return true; } - public void ShowBeatmap(int beatmapId) - { - var req = new GetBeatmapRequest(beatmapId); - req.Success += res => - { - if (!res.OnlineBeatmapSetID.HasValue) - return; - - ShowBeatmapSet(res.OnlineBeatmapSetID.Value); - }; - - api.Queue(req); - } - public void ShowBeatmapSet(int beatmapSetId) { // todo: display the overlay while we are loading here. we need to support setting BeatmapSet to null for this to work. diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index ebfdadd553..8d2c1895bb 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -83,13 +83,13 @@ namespace osu.Game.Overlays.Chat private Message message; private OsuSpriteText username; - private OsuLinkFlowContainer contentFlow; + private ChatFlowContainer contentFlow; - public OsuTextFlowContainer ContentFlow => contentFlow; + public ChatFlowContainer ContentFlow => contentFlow; public Message Message { - get { return message; } + get => message; set { if (message == value) return; @@ -107,7 +107,6 @@ namespace osu.Game.Overlays.Chat private void load(OsuColour colours, ChatOverlay chat) { this.chat = chat; - urlColour = colours.Blue; customUsernameColour = colours.ChatBlue; } @@ -192,10 +191,16 @@ namespace osu.Game.Overlays.Chat Padding = new MarginPadding { Left = message_padding + padding }, Children = new Drawable[] { - contentFlow = new OsuLinkFlowContainer(t => + contentFlow = new ChatFlowContainer(t => { if (Message.IsAction) + { t.Font = @"Exo2.0-MediumItalic"; + + if (senderHasBackground) + t.TextColour = OsuColour.FromHex(message.Sender.Colour); + } + t.TextSize = text_size; }) { @@ -205,15 +210,12 @@ namespace osu.Game.Overlays.Chat } } }; - if (message.IsAction && senderHasBackground) - contentFlow.TextColour = OsuColour.FromHex(message.Sender.Colour); updateMessageContent(); FinishTransforms(true); } private ChatOverlay chat; - private Color4 urlColour; private void updateMessageContent() { @@ -230,7 +232,7 @@ namespace osu.Game.Overlays.Chat else { int lastLinkEndIndex = 0; - List linksToRemove = new List(); + List linksToRemove = new List(); foreach (var link in message.Links) { @@ -250,10 +252,7 @@ namespace osu.Game.Overlays.Chat } } - contentFlow.AddLink(message.Content.Substring(link.Index, link.Length), link.Url, sprite => - { - ((ChatLink)sprite).TextColour = urlColour; - }); + contentFlow.AddLink(message.Content.Substring(link.Index, link.Length), link.Url, link.Action, link.Argument); } var lastLink = message.Links[message.Links.Count - 1]; diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 61e37b6e2f..86e4cba9bd 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -41,11 +41,6 @@ namespace osu.Game.Overlays private readonly FocusedTextBox textbox; - /// - /// The current OsuGame instance. Will be null for Tests. - /// - public OsuGame Game; - private APIAccess api; private const int transition_length = 500; @@ -275,11 +270,9 @@ namespace osu.Game.Overlays base.PopOut(); } - [BackgroundDependencyLoader(true)] - private void load(APIAccess api, OsuConfigManager config, OsuColour colours, OsuGame game) + [BackgroundDependencyLoader] + private void load(APIAccess api, OsuConfigManager config, OsuColour colours) { - // game will be null in testing, so some links will not work - Game = game; this.api = api; api.Register(this); diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index f1efbbc54a..a931df78af 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -23,7 +23,7 @@ namespace osu.Game.Overlays.Profile public class ProfileHeader : Container { private readonly OsuTextFlowContainer infoTextLeft; - private readonly OsuLinkFlowContainer infoTextRight; + private readonly LinkFlowContainer infoTextRight; private readonly FillFlowContainer scoreText, scoreNumberText; private readonly RankGraph rankGraph; @@ -142,7 +142,7 @@ namespace osu.Game.Overlays.Profile ParagraphSpacing = 0.8f, LineSpacing = 0.2f }, - infoTextRight = new OsuLinkFlowContainer(t => + infoTextRight = new LinkFlowContainer(t => { t.TextSize = 14; t.Font = @"Exo2.0-RegularItalic"; @@ -473,6 +473,20 @@ namespace osu.Game.Overlays.Profile } } + 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"; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d32c458ccf..37f94cf4f7 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -268,9 +268,10 @@ + + - @@ -293,7 +294,6 @@ 20171209034410_AddRulesetInfoShortName.cs - @@ -441,7 +441,6 @@ - From 1be0569743fc3590aae307c3ddb91f6050c9ba9b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2018 14:34:52 +0900 Subject: [PATCH 084/138] Update licence headers --- osu.Game.Tests/Visual/TestCaseChatLink.cs | 2 +- osu.Game/Graphics/Containers/ChatFlowContainer.cs | 2 +- osu.Game/Graphics/Sprites/OsuSpriteLink.cs | 2 +- osu.Game/Online/Chat/ChatLink.cs | 2 +- osu.Game/Online/Chat/MessageFormatter.cs | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index 9f8c3ce146..7984a67c36 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . +// Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using OpenTK.Graphics; diff --git a/osu.Game/Graphics/Containers/ChatFlowContainer.cs b/osu.Game/Graphics/Containers/ChatFlowContainer.cs index 6fd06aef7c..bf7d152a3b 100644 --- a/osu.Game/Graphics/Containers/ChatFlowContainer.cs +++ b/osu.Game/Graphics/Containers/ChatFlowContainer.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . +// Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Allocation; diff --git a/osu.Game/Graphics/Sprites/OsuSpriteLink.cs b/osu.Game/Graphics/Sprites/OsuSpriteLink.cs index 9fc24651db..f663f50624 100644 --- a/osu.Game/Graphics/Sprites/OsuSpriteLink.cs +++ b/osu.Game/Graphics/Sprites/OsuSpriteLink.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . +// Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Graphics; diff --git a/osu.Game/Online/Chat/ChatLink.cs b/osu.Game/Online/Chat/ChatLink.cs index f38eade682..e51f7d0828 100644 --- a/osu.Game/Online/Chat/ChatLink.cs +++ b/osu.Game/Online/Chat/ChatLink.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . +// Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Allocation; diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index eadab8840f..1bfb9b394e 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -1,4 +1,4 @@ -// Copyright (c) 2007-2017 ppy Pty Ltd . +// Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using System; From 72624aea186103e000e46cf6235f73a0ad100f5e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 9 Jan 2018 20:22:23 +0900 Subject: [PATCH 085/138] Use a better method of link compilation Adds word wrap back, simplifies a lot. --- osu.Game.Tests/Visual/TestCaseChatDisplay.cs | 19 +--- osu.Game.Tests/Visual/TestCaseChatLink.cs | 11 +-- .../Graphics/Containers/ChatFlowContainer.cs | 61 ------------- .../Graphics/Containers/LinkFlowContainer.cs | 75 ++++++++++++++++ .../Graphics/Containers/OsuHoverContainer.cs | 22 +++-- osu.Game/Graphics/Sprites/OsuSpriteLink.cs | 52 ----------- osu.Game/Online/Chat/ChatLink.cs | 89 ------------------- osu.Game/Online/Chat/DrawableLinkCompiler.cs | 43 +++++++++ osu.Game/Overlays/Chat/ChatLine.cs | 10 +-- osu.Game/Overlays/Profile/ProfileHeader.cs | 54 +++++------ osu.Game/osu.Game.csproj | 5 +- 11 files changed, 172 insertions(+), 269 deletions(-) delete mode 100644 osu.Game/Graphics/Containers/ChatFlowContainer.cs create mode 100644 osu.Game/Graphics/Containers/LinkFlowContainer.cs delete mode 100644 osu.Game/Graphics/Sprites/OsuSpriteLink.cs delete mode 100644 osu.Game/Online/Chat/ChatLink.cs create mode 100644 osu.Game/Online/Chat/DrawableLinkCompiler.cs 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 @@ - + From 3bf9901dd20d324ccd5398207b940765e7f8c85c Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Tue, 9 Jan 2018 16:11:45 +0100 Subject: [PATCH 086/138] Fixed bugs and added tests --- osu.Game.Tests/Visual/TestCaseChatLink.cs | 90 +++++++++---------- .../Graphics/Containers/LinkFlowContainer.cs | 35 ++++++-- osu.Game/Online/Chat/Message.cs | 9 ++ osu.Game/Online/Chat/MessageFormatter.cs | 6 +- osu.Game/OsuGame.cs | 9 +- osu.Game/Overlays/Chat/ChatLine.cs | 45 +++------- 6 files changed, 96 insertions(+), 98 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index ef36242f1f..722e3c30f3 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -4,12 +4,9 @@ using OpenTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Graphics; -using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Game.Graphics; -using osu.Game.Graphics.Containers; using osu.Game.Online.Chat; -using osu.Game.Overlays; using osu.Game.Overlays.Chat; using osu.Game.Users; using System; @@ -20,20 +17,11 @@ namespace osu.Game.Tests.Visual { public class TestCaseChatLink : OsuTestCase { - private readonly BeatmapSetOverlay beatmapSetOverlay; - private readonly ChatOverlay chat; - - private DependencyContainer dependencies; - private readonly TestChatLineContainer textContainer; - - protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); + private Color4 linkColour; public TestCaseChatLink() { - chat = new ChatOverlay(); - Add(beatmapSetOverlay = new BeatmapSetOverlay { Depth = float.MinValue }); - Add(textContainer = new TestChatLineContainer { Padding = new MarginPadding { Left = 20, Right = 20 }, @@ -43,37 +31,50 @@ namespace osu.Game.Tests.Visual }); testLinksGeneral(); - testAddingLinks(); testEcho(); } private void clear() => AddStep("clear messages", textContainer.Clear); - private void addMessageWithChecks(string text, int linkAmount = 0, bool isAction = false, bool isImportant = false) + private void addMessageWithChecks(string text, int linkAmount = 0, bool isAction = false, bool isImportant = false, params LinkAction[] expectedActions) { var newLine = new ChatLine(new DummyMessage(text, isAction, isImportant)); textContainer.Add(newLine); AddAssert($"msg #{textContainer.Count} has {linkAmount} link(s)", () => newLine.Message.Links.Count == linkAmount); - AddAssert($"msg #{textContainer.Count} is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic(newLine.ContentFlow)); - AddAssert($"msg #{textContainer.Count} shows link(s)", isShowingLinks); + AddAssert($"msg #{textContainer.Count} has the right action", hasExpectedActions); + AddAssert($"msg #{textContainer.Count} is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic()); + AddAssert($"msg #{textContainer.Count} shows {linkAmount} link(s)", isShowingLinks); - bool isItalic(LinkFlowContainer c) => c.Cast().All(sprite => sprite.Font == @"Exo2.0-MediumItalic"); + bool hasExpectedActions() + { + var expectedActionsList = expectedActions.ToList(); + + if (expectedActionsList.Count != newLine.Message.Links.Count) + return false; + + for (int i = 0; i < newLine.Message.Links.Count; i++) + { + var action = newLine.Message.Links[i].Action; + if (action != expectedActions[i]) return false; + } + + return true; + } + + bool isItalic() => newLine.ContentFlow.Where(d => d is OsuSpriteText).Cast().All(sprite => sprite.Font == "Exo2.0-MediumItalic"); bool isShowingLinks() { - SRGBColour textColour = Color4.White; bool hasBackground = !string.IsNullOrEmpty(newLine.Message.Sender.Colour); - if (isAction && hasBackground) - textColour = OsuColour.FromHex(newLine.Message.Sender.Colour); + Color4 textColour = isAction && hasBackground ? OsuColour.FromHex(newLine.Message.Sender.Colour) : Color4.White; - return newLine.ContentFlow - .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.Colour.Equals((ColourInfo)Color4.White)); + var linkCompilers = newLine.ContentFlow.Where(d => d is DrawableLinkCompiler).ToList(); + var linkSprites = linkCompilers.SelectMany(comp => ((DrawableLinkCompiler)comp).Parts); + + return linkSprites.All(d => d.Colour == linkColour) + && newLine.ContentFlow.Except(linkSprites.Concat(linkCompilers)).All(d => d.Colour == textColour); } } @@ -81,30 +82,20 @@ namespace osu.Game.Tests.Visual { addMessageWithChecks("test!"); addMessageWithChecks("osu.ppy.sh!"); - addMessageWithChecks("https://osu.ppy.sh!", 1); - addMessageWithChecks("00:12:345 (1,2) - Test?", 1); - addMessageWithChecks("Wiki link for tasty [[Performance Points]]", 1); - addMessageWithChecks("(osu forums)[https://osu.ppy.sh/forum] (old link format)", 1); - addMessageWithChecks("[https://osu.ppy.sh/home New site] (new link format)", 1); - addMessageWithChecks("[https://osu.ppy.sh/home This is only a link to the new osu webpage but this is supposed to test word wrap.]", 1); - addMessageWithChecks("is now listening to [https://osu.ppy.sh/s/93523 IMAGE -MATERIAL- ]", 1, true); - addMessageWithChecks("is now playing [https://osu.ppy.sh/b/252238 IMAGE -MATERIAL- ]", 1, true); - addMessageWithChecks("Let's (try)[https://osu.ppy.sh/home] [https://osu.ppy.sh/home multiple links] https://osu.ppy.sh/home", 3); + addMessageWithChecks("https://osu.ppy.sh!", 1, expectedActions: LinkAction.External); + addMessageWithChecks("00:12:345 (1,2) - Test?", 1, expectedActions: LinkAction.OpenEditorTimestamp); + addMessageWithChecks("Wiki link for tasty [[Performance Points]]", 1, expectedActions: LinkAction.External); + addMessageWithChecks("(osu forums)[https://osu.ppy.sh/forum] (old link format)", 1, expectedActions: LinkAction.External); + addMessageWithChecks("[https://osu.ppy.sh/home New site] (new link format)", 1, expectedActions: LinkAction.External); + addMessageWithChecks("[https://osu.ppy.sh/home This is only a link to the new osu webpage but this is supposed to test word wrap.]", 1, expectedActions: LinkAction.External); + addMessageWithChecks("is now listening to [https://osu.ppy.sh/s/93523 IMAGE -MATERIAL- ]", 1, true, expectedActions: LinkAction.OpenBeatmapSet); + addMessageWithChecks("is now playing [https://osu.ppy.sh/b/252238 IMAGE -MATERIAL- ]", 1, true, expectedActions: LinkAction.OpenBeatmap); + addMessageWithChecks("Let's (try)[https://osu.ppy.sh/home] [https://osu.ppy.sh/b/252238 multiple links] https://osu.ppy.sh/home", 3, expectedActions: new[] { LinkAction.External, LinkAction.OpenBeatmap, LinkAction.External }); // note that there's 0 links here (they get removed if a channel is not found) addMessageWithChecks("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present)."); addMessageWithChecks("I am important!", 0, false, true); addMessageWithChecks("feels important", 0, true, true); - addMessageWithChecks("likes to post this [https://osu.ppy.sh/home link].", 1, true, true); - } - - private void testAddingLinks() - { - const int count = 5; - - for (int i = 1; i <= count; i++) - AddStep($"add long msg #{i}", () => textContainer.Add(new ChatLine(new DummyMessage("alright let's just put a really long text here to see if it loads in correctly rather than adding the text sprites individually after the chat line appearing!")))); - - clear(); + addMessageWithChecks("likes to post this [https://osu.ppy.sh/home link].", 1, true, true, expectedActions: LinkAction.External); } private void testEcho() @@ -131,10 +122,9 @@ namespace osu.Game.Tests.Visual } [BackgroundDependencyLoader] - private void load() + private void load(OsuColour colours) { - dependencies.Cache(chat); - dependencies.Cache(beatmapSetOverlay); + linkColour = colours.Blue; } private class DummyEchoMessage : LocalEchoMessage diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 4485630e12..c091e2b5c9 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -7,7 +7,7 @@ using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; -using osu.Game.Overlays; +using System.Collections.Generic; namespace osu.Game.Graphics.Containers { @@ -20,19 +20,36 @@ namespace osu.Game.Graphics.Containers 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) + private void load(OsuGame game) { - this.beatmapSetOverlay = beatmapSetOverlay; - this.chat = chat; - // this will be null in tests + // will be null in tests this.game = game; } + public void AddLinks(string text, List links) + { + if (string.IsNullOrEmpty(text) || links == null) + return; + + if (links.Count == 0) + { + AddText(text); + return; + } + + int previousLinkEnd = 0; + foreach (var link in links) + { + AddText(text.Substring(previousLinkEnd, link.Index - previousLinkEnd)); + + AddLink(text.Substring(link.Index, link.Length), link.Url, link.Action, link.Argument); + previousLinkEnd = link.Index + link.Length; + } + } + public void AddLink(string text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null) { AddInternal(new DrawableLinkCompiler(AddText(text).ToList()) @@ -47,10 +64,10 @@ namespace osu.Game.Graphics.Containers break; case LinkAction.OpenBeatmapSet: if (int.TryParse(linkArgument, out int setId)) - beatmapSetOverlay.ShowBeatmapSet(setId); + game?.ShowBeatmapSet(setId); break; case LinkAction.OpenChannel: - chat.OpenChannel(chat.AvailableChannels.Find(c => c.Name == linkArgument)); + game?.OpenChannel(linkArgument); break; case LinkAction.OpenEditorTimestamp: game?.LoadEditorTimestamp(); diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs index aa1020c2de..374a6e3159 100644 --- a/osu.Game/Online/Chat/Message.cs +++ b/osu.Game/Online/Chat/Message.cs @@ -41,6 +41,15 @@ namespace osu.Game.Online.Chat { } + /// + /// The text that is displayed in chat. + /// + public string DisplayContent { get; set; } + + /// + /// The links found in this message. + /// + /// The links' positions are according to public List Links; public Message(long? id) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 1bfb9b394e..3a396a04c0 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -66,7 +66,7 @@ namespace osu.Game.Online.Chat //since we just changed the line display text, offset any already processed links. result.Links.ForEach(l => l.Index -= l.Index > index ? m.Length - displayText.Length : 0); - var details = getLinkDetails(link); + var details = getLinkDetails(linkText); result.Links.Add(new Link(linkText, index, displayText.Length, linkActionOverride ?? details.linkType, details.linkArgument)); //adjust the offset for processing the current matches group. @@ -119,7 +119,7 @@ namespace osu.Game.Online.Chat case "s": case "beatmapsets": case "d": - return (LinkAction.External, args[3]); + return (LinkAction.OpenBeatmapSet, args[3]); } } @@ -196,7 +196,7 @@ namespace osu.Game.Online.Chat { var result = format(inputMessage.Content); - inputMessage.Content = result.Text; + inputMessage.DisplayContent = result.Text; // Sometimes, regex matches are not in order result.Links.Sort(); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 847d93e12e..2cf8417d7f 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -119,7 +119,12 @@ namespace osu.Game private ScheduledDelegate scoreLoad; - // TODO: Implement this properly as soon as the Editor is done + #region chat link actions + + internal void OpenChannel(string channelName) => chat.OpenChannel(chat.AvailableChannels.Find(c => c.Name == channelName)); + + internal void ShowBeatmapSet(int setId) => beatmapSetOverlay.ShowBeatmapSet(setId); + internal void LoadEditorTimestamp() { notifications.Post(new SimpleNotification @@ -147,6 +152,8 @@ namespace osu.Game }); } + #endregion + protected void LoadScore(Score s) { scoreLoad?.Cancel(); diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 6f847d6f01..2382a315f5 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -225,42 +225,17 @@ namespace osu.Game.Overlays.Chat timestamp.Text = $@"{message.Timestamp.LocalDateTime:HH:mm:ss}"; username.Text = $@"{message.Sender.Username}" + (senderHasBackground || message.IsAction ? "" : ":"); + // remove any non-existent channels from the link list + var linksToRemove = new List(); + foreach (var link in message.Links) + if (link.Action == LinkAction.OpenChannel && chat?.AvailableChannels.TrueForAll(c => c.Name != link.Argument) != false) + linksToRemove.Add(link); + + foreach (var link in linksToRemove) + message.Links.Remove(link); + contentFlow.Clear(); - - if (message.Links == null || message.Links.Count == 0) - contentFlow.AddText(message.Content); - else - { - int lastLinkEndIndex = 0; - List linksToRemove = new List(); - - foreach (var link in message.Links) - { - contentFlow.AddText(message.Content.Substring(lastLinkEndIndex, link.Index - lastLinkEndIndex)); - lastLinkEndIndex = link.Index + link.Length; - - const string channel_link_prefix = "osu://chan/"; - // If a channel doesn't exist, add it as normal text instead - if (link.Url.StartsWith(channel_link_prefix)) - { - var channelName = link.Url.Substring(channel_link_prefix.Length).Split('/')[0]; - if (chat?.AvailableChannels.TrueForAll(c => c.Name != channelName) != false) - { - linksToRemove.Add(link); - contentFlow.AddText(message.Content.Substring(link.Index, link.Length)); - continue; - } - } - - contentFlow.AddLink(message.Content.Substring(link.Index, link.Length), link.Url); - } - - var lastLink = message.Links[message.Links.Count - 1]; - contentFlow.AddText(message.Content.Substring(lastLink.Index + lastLink.Length)); - - foreach (var link in linksToRemove) - message.Links.Remove(link); - } + contentFlow.AddLinks(message.DisplayContent, message.Links); } private class MessageSender : OsuClickableContainer, IHasContextMenu From 174fdf5037f637215b4c3104e486a1ceb2657297 Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 11 Jan 2018 18:51:20 +0100 Subject: [PATCH 087/138] Nicer code to remove non-existent channels from links --- osu.Game/Overlays/Chat/ChatLine.cs | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 2382a315f5..d87f60a350 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -15,7 +15,6 @@ using osu.Game.Users; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; -using System.Collections.Generic; namespace osu.Game.Overlays.Chat { @@ -225,14 +224,8 @@ namespace osu.Game.Overlays.Chat timestamp.Text = $@"{message.Timestamp.LocalDateTime:HH:mm:ss}"; username.Text = $@"{message.Sender.Username}" + (senderHasBackground || message.IsAction ? "" : ":"); - // remove any non-existent channels from the link list - var linksToRemove = new List(); - foreach (var link in message.Links) - if (link.Action == LinkAction.OpenChannel && chat?.AvailableChannels.TrueForAll(c => c.Name != link.Argument) != false) - linksToRemove.Add(link); - - foreach (var link in linksToRemove) - message.Links.Remove(link); + // remove non-existent channels from the link list + message.Links.RemoveAll(link => link.Action == LinkAction.OpenChannel && chat?.AvailableChannels.TrueForAll(c => c.Name != link.Argument) != false); contentFlow.Clear(); contentFlow.AddLinks(message.DisplayContent, message.Links); From 2c67ff75edd699604fa612c7fd7882dcae6c782c Mon Sep 17 00:00:00 2001 From: FreezyLemon Date: Thu, 11 Jan 2018 18:52:50 +0100 Subject: [PATCH 088/138] added custom hoverclicksounds so links make sounds on hover&click --- .../Graphics/Containers/OsuClickableContainer.cs | 4 +++- osu.Game/Online/Chat/DrawableLinkCompiler.cs | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/OsuClickableContainer.cs b/osu.Game/Graphics/Containers/OsuClickableContainer.cs index 4e95050bda..b9ee1f4463 100644 --- a/osu.Game/Graphics/Containers/OsuClickableContainer.cs +++ b/osu.Game/Graphics/Containers/OsuClickableContainer.cs @@ -16,6 +16,8 @@ namespace osu.Game.Graphics.Containers protected override Container Content => content; + protected virtual HoverClickSounds CreateHoverClickSounds(HoverSampleSet sampleSet) => new HoverClickSounds(sampleSet); + public OsuClickableContainer(HoverSampleSet sampleSet = HoverSampleSet.Normal) { this.sampleSet = sampleSet; @@ -33,7 +35,7 @@ namespace osu.Game.Graphics.Containers InternalChildren = new Drawable[] { content, - new HoverClickSounds(sampleSet) + CreateHoverClickSounds(sampleSet) }; } } diff --git a/osu.Game/Online/Chat/DrawableLinkCompiler.cs b/osu.Game/Online/Chat/DrawableLinkCompiler.cs index f1249031c1..234781fb52 100644 --- a/osu.Game/Online/Chat/DrawableLinkCompiler.cs +++ b/osu.Game/Online/Chat/DrawableLinkCompiler.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Sprites; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; using OpenTK; namespace osu.Game.Online.Chat @@ -25,6 +26,8 @@ namespace osu.Game.Online.Chat public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => Parts.Any(d => d.ReceiveMouseInputAt(screenSpacePos)); + protected override HoverClickSounds CreateHoverClickSounds(HoverSampleSet sampleSet) => new LinkHoverSounds(sampleSet, Parts); + public DrawableLinkCompiler(IEnumerable parts) { Parts = parts.ToList(); @@ -39,5 +42,18 @@ namespace osu.Game.Online.Chat protected override IEnumerable EffectTargets => Parts; public string TooltipText { get; set; } + + private class LinkHoverSounds : HoverClickSounds + { + private readonly List parts; + + public LinkHoverSounds(HoverSampleSet sampleSet, List parts) + : base(sampleSet) + { + this.parts = parts; + } + + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => parts.Any(d => d.ReceiveMouseInputAt(screenSpacePos)); + } } } From 9277586907600f6ae9ace884fea751d431359bbf Mon Sep 17 00:00:00 2001 From: aQaTL Date: Tue, 16 Jan 2018 17:46:54 +0100 Subject: [PATCH 089/138] Toggle mute/unmute keyboard shortcut --- .../UserInterface/Volume/VolumeControl.cs | 4 ++++ .../Graphics/UserInterface/Volume/VolumeMeter.cs | 16 +++++++++++++++- .../Bindings/GlobalKeyBindingInputManager.cs | 3 +++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs index 33888e57e0..63b14713e2 100644 --- a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs +++ b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs @@ -76,6 +76,10 @@ namespace osu.Game.Graphics.UserInterface.Volume else volumeMeterMaster.Increase(); return true; + case GlobalAction.ToggleMute: + Show(); + volumeMeterMaster.ToogleMute(); + return true; } return false; diff --git a/osu.Game/Graphics/UserInterface/Volume/VolumeMeter.cs b/osu.Game/Graphics/UserInterface/Volume/VolumeMeter.cs index 8323dade44..dc1b2e85eb 100644 --- a/osu.Game/Graphics/UserInterface/Volume/VolumeMeter.cs +++ b/osu.Game/Graphics/UserInterface/Volume/VolumeMeter.cs @@ -18,6 +18,9 @@ namespace osu.Game.Graphics.UserInterface.Volume private readonly Box meterFill; public BindableDouble Bindable { get; } = new BindableDouble(); + private double lastVolume; + public bool IsMuted { get; private set; } + public VolumeMeter(string meterName) { Size = new Vector2(40, 180); @@ -70,16 +73,19 @@ namespace osu.Game.Graphics.UserInterface.Volume public double Volume { - get { return Bindable.Value; } + get => Bindable.Value; private set { Bindable.Value = value; + if (value > 0) + IsMuted = false; } } public void Increase() { Volume += 0.05f; + IsMuted = false; } public void Decrease() @@ -87,6 +93,14 @@ namespace osu.Game.Graphics.UserInterface.Volume Volume -= 0.05f; } + public void ToogleMute() + { + IsMuted = !IsMuted; + if (IsMuted) + lastVolume = Volume; + Volume = IsMuted ? 0.0 : lastVolume; + } + private void updateFill() => meterFill.ScaleTo(new Vector2(1, (float)Volume), 300, Easing.OutQuint); public bool OnPressed(GlobalAction action) diff --git a/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs b/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs index f5e54775fb..6c317890af 100644 --- a/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs +++ b/osu.Game/Input/Bindings/GlobalKeyBindingInputManager.cs @@ -33,6 +33,7 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.MouseWheelUp }, GlobalAction.IncreaseVolume), new KeyBinding(new[] { InputKey.Down }, GlobalAction.DecreaseVolume), new KeyBinding(new[] { InputKey.MouseWheelDown }, GlobalAction.DecreaseVolume), + new KeyBinding(InputKey.F4, GlobalAction.ToggleMute), }; public IEnumerable InGameKeyBindings => new[] @@ -62,6 +63,8 @@ namespace osu.Game.Input.Bindings IncreaseVolume, [Description("Decrease Volume")] DecreaseVolume, + [Description("Toggle mute")] + ToggleMute, // In-Game Keybindings [Description("Skip Cutscene")] From 0340e4f8dcccbf6f5d09189f95aea5543bdca92a Mon Sep 17 00:00:00 2001 From: aQaTL Date: Tue, 16 Jan 2018 20:33:30 +0100 Subject: [PATCH 090/138] Option in settings to toggle mute/unmute when losing/gaining window focus --- osu.Game/Configuration/OsuConfigManager.cs | 3 +++ .../UserInterface/Volume/VolumeControl.cs | 18 ++++++++++++++ osu.Game/OsuGame.cs | 24 +++++++++++++++++++ .../Settings/Sections/Audio/VolumeSettings.cs | 4 +++- 4 files changed, 48 insertions(+), 1 deletion(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 23f7fd6ac1..0c90b2c2ec 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -39,6 +39,8 @@ namespace osu.Game.Configuration }; // Audio + Set(OsuSetting.MuteWhenInactive, false); + Set(OsuSetting.MenuVoice, true); Set(OsuSetting.MenuMusic, true); @@ -101,6 +103,7 @@ namespace osu.Game.Configuration MouseDisableButtons, MouseDisableWheel, AudioOffset, + MuteWhenInactive, MenuMusic, MenuVoice, CursorRotation, diff --git a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs index 63b14713e2..7a1a2de50d 100644 --- a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs +++ b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs @@ -91,6 +91,24 @@ namespace osu.Game.Graphics.UserInterface.Volume schedulePopOut(); } + public bool IsMuted => volumeMeterMaster.IsMuted; + + public void Mute() + { + if (!IsMuted) + { + volumeMeterMaster.ToogleMute(); + } + } + + public void Unmute() + { + if (IsMuted) + { + volumeMeterMaster.ToogleMute(); + } + } + [BackgroundDependencyLoader] private void load(AudioManager audio) { diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 124b9364b3..56cfffdeae 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -115,6 +115,8 @@ namespace osu.Game configRuleset = LocalConfig.GetBindable(OsuSetting.Ruleset); Ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value) ?? RulesetStore.AvailableRulesets.First(); Ruleset.ValueChanged += r => configRuleset.Value = r.ID ?? 0; + + muteWhenInactive = LocalConfig.GetBindable(OsuSetting.MuteWhenInactive); } private ScheduledDelegate scoreLoad; @@ -386,6 +388,28 @@ namespace osu.Game return false; } + private Bindable muteWhenInactive = new Bindable(); + private bool wasMuted; + + protected override void OnDeactivated() + { + base.OnDeactivated(); + if (muteWhenInactive) + { + wasMuted = volume.IsMuted; + volume.Mute(); + } + } + + protected override void OnActivated() + { + base.OnActivated(); + if (IsLoaded && muteWhenInactive && !wasMuted) + { + volume.Unmute(); + } + } + public bool OnReleased(GlobalAction action) => false; private Container mainContent; diff --git a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs index 40b9ff069b..01bcf989dc 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs @@ -4,6 +4,7 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Graphics; +using osu.Game.Configuration; namespace osu.Game.Overlays.Settings.Sections.Audio { @@ -12,13 +13,14 @@ namespace osu.Game.Overlays.Settings.Sections.Audio protected override string Header => "Volume"; [BackgroundDependencyLoader] - private void load(AudioManager audio) + private void load(AudioManager audio, OsuConfigManager config) { Children = new Drawable[] { new SettingsSlider { LabelText = "Master", Bindable = audio.Volume, KeyboardStep = 0.1f }, new SettingsSlider { LabelText = "Effect", Bindable = audio.VolumeSample, KeyboardStep = 0.1f }, new SettingsSlider { LabelText = "Music", Bindable = audio.VolumeTrack, KeyboardStep = 0.1f }, + new SettingsCheckbox { LabelText = "Mute osu! when inactive", Bindable = config.GetBindable(OsuSetting.MuteWhenInactive) } }; } } From 538c20a947d0e231ff9b84ba9ce36bac78c52c70 Mon Sep 17 00:00:00 2001 From: aQaTL Date: Tue, 16 Jan 2018 21:30:48 +0100 Subject: [PATCH 091/138] Prevent not saving audio levels when user alt tabs before the window closes --- osu.Game/OsuGame.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 56cfffdeae..de923b3451 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -79,6 +79,8 @@ namespace osu.Game private SettingsOverlay settings; + private bool exiting; + public OsuGame(string[] args = null) { this.args = args; @@ -117,6 +119,13 @@ namespace osu.Game Ruleset.ValueChanged += r => configRuleset.Value = r.ID ?? 0; muteWhenInactive = LocalConfig.GetBindable(OsuSetting.MuteWhenInactive); + Host.Window.Exited += () => + { + //Prevent not saving audio levels when user alt tabs before the window closes + if (volume.IsMuted) + volume.Unmute(); + exiting = true; + }; } private ScheduledDelegate scoreLoad; @@ -394,7 +403,7 @@ namespace osu.Game protected override void OnDeactivated() { base.OnDeactivated(); - if (muteWhenInactive) + if (muteWhenInactive && !exiting) { wasMuted = volume.IsMuted; volume.Mute(); @@ -404,7 +413,7 @@ namespace osu.Game protected override void OnActivated() { base.OnActivated(); - if (IsLoaded && muteWhenInactive && !wasMuted) + if (IsLoaded && muteWhenInactive && !wasMuted && !exiting) { volume.Unmute(); } From 2f7d9036bdd13ace2dca123374b87c0f5463cf2a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jan 2018 16:20:40 +0900 Subject: [PATCH 092/138] Update framework --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index 49b563e2cf..cbdfad5d7e 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 49b563e2cf170eb19006b98dd5b69c2398362d9e +Subproject commit cbdfad5d7efdb9552a7d28523bd118f35d8542ea From 783ca40c642a6a7e107aff1f2a704ae0d9d6bb30 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jan 2018 16:51:47 +0900 Subject: [PATCH 093/138] Update with upstream changes --- osu.Game/Graphics/Containers/LinkFlowContainer.cs | 2 +- osu.Game/Overlays/Profile/ProfileHeader.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index c091e2b5c9..3c81b92168 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -18,7 +18,7 @@ namespace osu.Game.Graphics.Containers { } - public override bool HandleInput => true; + public override bool HandleMouseInput => true; private OsuGame game; diff --git a/osu.Game/Overlays/Profile/ProfileHeader.cs b/osu.Game/Overlays/Profile/ProfileHeader.cs index 035aba7dd7..937b4efcad 100644 --- a/osu.Game/Overlays/Profile/ProfileHeader.cs +++ b/osu.Game/Overlays/Profile/ProfileHeader.cs @@ -440,7 +440,7 @@ namespace osu.Game.Overlays.Profile { public string TooltipText => "View Profile in Browser"; - public override bool HandleInput => true; + public override bool HandleMouseInput => true; public ProfileLink(User user) { From 3eccface7274c4aa1ee9a951555421ae1d484987 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jan 2018 19:21:32 +0900 Subject: [PATCH 094/138] Update framework --- osu-framework | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-framework b/osu-framework index cbdfad5d7e..aa111a4362 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit cbdfad5d7efdb9552a7d28523bd118f35d8542ea +Subproject commit aa111a4362acc50d5684be62675685041948894f From b27577e242c6d4a66ca201c05006d370b0fac4e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jan 2018 19:37:20 +0900 Subject: [PATCH 095/138] Add temporary browser handling of beatmap difficulty loading until we have an api method to support this. --- osu.Game/Graphics/Containers/LinkFlowContainer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 3c81b92168..ea11f065b4 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -60,7 +60,9 @@ namespace osu.Game.Graphics.Containers switch (linkType) { case LinkAction.OpenBeatmap: - // todo: implement this when overlay.ShowBeatmap(id) exists + // todo: replace this with overlay.ShowBeatmap(id) once an appropriate API call is implemented. + if (int.TryParse(linkArgument, out int beatmapId)) + Process.Start($"https://osu.ppy.sh/b/{beatmapId}"); break; case LinkAction.OpenBeatmapSet: if (int.TryParse(linkArgument, out int setId)) From 5a80c4964004925647c07ee0663ab3da8886ba5a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jan 2018 19:44:15 +0900 Subject: [PATCH 096/138] Improve reference xmldoc --- osu.Game/Online/Chat/Message.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs index 374a6e3159..99735c4d65 100644 --- a/osu.Game/Online/Chat/Message.cs +++ b/osu.Game/Online/Chat/Message.cs @@ -49,7 +49,7 @@ namespace osu.Game.Online.Chat /// /// The links found in this message. /// - /// The links' positions are according to + /// The s' and s are according to public List Links; public Message(long? id) From 38929658947fd9dd6b2ed6941b1211baaf26095b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jan 2018 19:45:10 +0900 Subject: [PATCH 097/138] Remove unnecessary region and make methods public --- osu.Game/OsuGame.cs | 14 +++++--------- 1 file changed, 5 insertions(+), 9 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 82271090f1..40848d2507 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -119,13 +119,11 @@ namespace osu.Game private ScheduledDelegate scoreLoad; - #region chat link actions + public void OpenChannel(string channelName) => chat.OpenChannel(chat.AvailableChannels.Find(c => c.Name == channelName)); - internal void OpenChannel(string channelName) => chat.OpenChannel(chat.AvailableChannels.Find(c => c.Name == channelName)); + public void ShowBeatmapSet(int setId) => beatmapSetOverlay.ShowBeatmapSet(setId); - internal void ShowBeatmapSet(int setId) => beatmapSetOverlay.ShowBeatmapSet(setId); - - internal void LoadEditorTimestamp() + public void LoadEditorTimestamp() { notifications.Post(new SimpleNotification { @@ -134,7 +132,7 @@ namespace osu.Game }); } - internal void LoadSpectatorScreen() + public void LoadSpectatorScreen() { notifications.Post(new SimpleNotification { @@ -143,7 +141,7 @@ namespace osu.Game }); } - internal void JoinMultiplayerMatch(int matchId) + public void JoinMultiplayerMatch(int matchId) { notifications.Post(new SimpleNotification { @@ -152,8 +150,6 @@ namespace osu.Game }); } - #endregion - protected void LoadScore(Score s) { scoreLoad?.Cancel(); From 38c5434b825724fbf3c7b6d6c92753fd0b1a60c3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jan 2018 19:52:04 +0900 Subject: [PATCH 098/138] Remove placeholder methods in OsuGame Let's add these when they can actually be implemented. --- .../Graphics/Containers/LinkFlowContainer.cs | 21 +++++++++------ osu.Game/OsuGame.cs | 27 ------------------- 2 files changed, 13 insertions(+), 35 deletions(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index ea11f065b4..f35282fc93 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -8,6 +8,8 @@ using System.Linq; using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using System.Collections.Generic; +using osu.Game.Overlays; +using osu.Game.Overlays.Notifications; namespace osu.Game.Graphics.Containers { @@ -22,11 +24,19 @@ namespace osu.Game.Graphics.Containers private OsuGame game; + private Action showNotImplementedError; + [BackgroundDependencyLoader(true)] - private void load(OsuGame game) + private void load(OsuGame game, NotificationOverlay notifications) { // will be null in tests this.game = game; + + showNotImplementedError = () => notifications?.Post(new SimpleNotification + { + Text = @"This link type is not yet supported!", + Icon = FontAwesome.fa_life_saver, + }); } public void AddLinks(string text, List links) @@ -72,14 +82,9 @@ namespace osu.Game.Graphics.Containers game?.OpenChannel(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 + showNotImplementedError?.Invoke(); break; case LinkAction.External: Process.Start(url); @@ -87,7 +92,7 @@ namespace osu.Game.Graphics.Containers default: throw new NotImplementedException($"This {nameof(LinkAction)} ({linkType.ToString()}) is missing an associated action."); } - } + }, }); } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 40848d2507..56fe436210 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -123,33 +123,6 @@ namespace osu.Game public void ShowBeatmapSet(int setId) => beatmapSetOverlay.ShowBeatmapSet(setId); - public void LoadEditorTimestamp() - { - notifications.Post(new SimpleNotification - { - Text = @"Sorry, but this is not fully implemented yet!", - Icon = FontAwesome.fa_life_saver, - }); - } - - public void LoadSpectatorScreen() - { - notifications.Post(new SimpleNotification - { - Text = @"Sorry, but spectating is not implemented yet!", - Icon = FontAwesome.fa_life_saver, - }); - } - - public void JoinMultiplayerMatch(int matchId) - { - notifications.Post(new SimpleNotification - { - Text = @"Sorry, but the multiplayer lobby is not implemented yet!", - Icon = FontAwesome.fa_life_saver, - }); - } - protected void LoadScore(Score s) { scoreLoad?.Cancel(); From 73d69e2fd964c7eff983e3ca75bd3ffcf961203a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 17 Jan 2018 20:32:26 +0900 Subject: [PATCH 099/138] Add more xmldoc --- osu.Game/OsuGame.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 56fe436210..dd020e435d 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -119,8 +119,16 @@ namespace osu.Game private ScheduledDelegate scoreLoad; + /// + /// Open chat to a channel matching the provided name, if present. + /// + /// The name of the channel. public void OpenChannel(string channelName) => chat.OpenChannel(chat.AvailableChannels.Find(c => c.Name == channelName)); + /// + /// Show a beatmap set as an overlay. + /// + /// The set to display. public void ShowBeatmapSet(int setId) => beatmapSetOverlay.ShowBeatmapSet(setId); protected void LoadScore(Score s) From ac41cb59eabaed0034fa34ab6a5a0a5938673cb1 Mon Sep 17 00:00:00 2001 From: aQaTL Date: Wed, 17 Jan 2018 14:36:33 +0100 Subject: [PATCH 100/138] Typo fix, removed unnecessary braces --- .../Graphics/UserInterface/Volume/VolumeControl.cs | 10 +++------- osu.Game/Graphics/UserInterface/Volume/VolumeMeter.cs | 2 +- 2 files changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs index 7a1a2de50d..04c48c629d 100644 --- a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs +++ b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs @@ -78,7 +78,7 @@ namespace osu.Game.Graphics.UserInterface.Volume return true; case GlobalAction.ToggleMute: Show(); - volumeMeterMaster.ToogleMute(); + volumeMeterMaster.ToggleMute(); return true; } @@ -96,17 +96,13 @@ namespace osu.Game.Graphics.UserInterface.Volume public void Mute() { if (!IsMuted) - { - volumeMeterMaster.ToogleMute(); - } + volumeMeterMaster.ToggleMute(); } public void Unmute() { if (IsMuted) - { - volumeMeterMaster.ToogleMute(); - } + volumeMeterMaster.ToggleMute(); } [BackgroundDependencyLoader] diff --git a/osu.Game/Graphics/UserInterface/Volume/VolumeMeter.cs b/osu.Game/Graphics/UserInterface/Volume/VolumeMeter.cs index dc1b2e85eb..aac969b84f 100644 --- a/osu.Game/Graphics/UserInterface/Volume/VolumeMeter.cs +++ b/osu.Game/Graphics/UserInterface/Volume/VolumeMeter.cs @@ -93,7 +93,7 @@ namespace osu.Game.Graphics.UserInterface.Volume Volume -= 0.05f; } - public void ToogleMute() + public void ToggleMute() { IsMuted = !IsMuted; if (IsMuted) From 1440edbf8b14bf3577461247f51758abf72ab92e Mon Sep 17 00:00:00 2001 From: aQaTL Date: Wed, 17 Jan 2018 17:15:13 +0100 Subject: [PATCH 101/138] Use AudioManager adjustments to mute volume --- .../UserInterface/Volume/VolumeControl.cs | 28 ++++++++++++++----- .../UserInterface/Volume/VolumeMeter.cs | 17 +---------- 2 files changed, 22 insertions(+), 23 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs index 04c48c629d..d5645d6f24 100644 --- a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs +++ b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs @@ -7,12 +7,15 @@ using osu.Framework.Threading; using OpenTK; using osu.Framework.Audio; using osu.Framework.Allocation; +using osu.Framework.Configuration; using osu.Game.Input.Bindings; namespace osu.Game.Graphics.UserInterface.Volume { public class VolumeControl : OverlayContainer { + private AudioManager audio; + private readonly VolumeMeter volumeMeterMaster; protected override bool BlockPassThroughMouse => false; @@ -77,8 +80,10 @@ namespace osu.Game.Graphics.UserInterface.Volume volumeMeterMaster.Increase(); return true; case GlobalAction.ToggleMute: - Show(); - volumeMeterMaster.ToggleMute(); + if (IsMuted) + Unmute(); + else + Mute(); return true; } @@ -91,23 +96,32 @@ namespace osu.Game.Graphics.UserInterface.Volume schedulePopOut(); } - public bool IsMuted => volumeMeterMaster.IsMuted; + private readonly BindableDouble muteBindable = new BindableDouble(); + + public bool IsMuted { get; private set; } public void Mute() { - if (!IsMuted) - volumeMeterMaster.ToggleMute(); + if (IsMuted) + return; + + audio.AddAdjustment(AdjustableProperty.Volume, muteBindable); + IsMuted = true; } public void Unmute() { - if (IsMuted) - volumeMeterMaster.ToggleMute(); + if (!IsMuted) + return; + + audio.RemoveAdjustment(AdjustableProperty.Volume, muteBindable); + IsMuted = false; } [BackgroundDependencyLoader] private void load(AudioManager audio) { + this.audio = audio; volumeMeterMaster.Bindable.BindTo(audio.Volume); volumeMeterEffect.Bindable.BindTo(audio.VolumeSample); volumeMeterMusic.Bindable.BindTo(audio.VolumeTrack); diff --git a/osu.Game/Graphics/UserInterface/Volume/VolumeMeter.cs b/osu.Game/Graphics/UserInterface/Volume/VolumeMeter.cs index aac969b84f..a1acb5d2ff 100644 --- a/osu.Game/Graphics/UserInterface/Volume/VolumeMeter.cs +++ b/osu.Game/Graphics/UserInterface/Volume/VolumeMeter.cs @@ -19,7 +19,6 @@ namespace osu.Game.Graphics.UserInterface.Volume public BindableDouble Bindable { get; } = new BindableDouble(); private double lastVolume; - public bool IsMuted { get; private set; } public VolumeMeter(string meterName) { @@ -74,18 +73,12 @@ namespace osu.Game.Graphics.UserInterface.Volume public double Volume { get => Bindable.Value; - private set - { - Bindable.Value = value; - if (value > 0) - IsMuted = false; - } + private set => Bindable.Value = value; } public void Increase() { Volume += 0.05f; - IsMuted = false; } public void Decrease() @@ -93,14 +86,6 @@ namespace osu.Game.Graphics.UserInterface.Volume Volume -= 0.05f; } - public void ToggleMute() - { - IsMuted = !IsMuted; - if (IsMuted) - lastVolume = Volume; - Volume = IsMuted ? 0.0 : lastVolume; - } - private void updateFill() => meterFill.ScaleTo(new Vector2(1, (float)Volume), 300, Easing.OutQuint); public bool OnPressed(GlobalAction action) From a8fb732256d35c18c99cbf902d8b5fe8efe453de Mon Sep 17 00:00:00 2001 From: aQaTL Date: Wed, 17 Jan 2018 20:43:08 +0100 Subject: [PATCH 102/138] Added muted/unmuted icon --- .../UserInterface/Volume/VolumeControl.cs | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs index d5645d6f24..2d350a3474 100644 --- a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs +++ b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs @@ -17,6 +17,7 @@ namespace osu.Game.Graphics.UserInterface.Volume private AudioManager audio; private readonly VolumeMeter volumeMeterMaster; + private readonly IconButton muteIcon; protected override bool BlockPassThroughMouse => false; @@ -37,12 +38,23 @@ namespace osu.Game.Graphics.UserInterface.Volume Spacing = new Vector2(15, 0), Children = new Drawable[] { + muteIcon = new IconButton(), volumeMeterMaster = new VolumeMeter("Master"), volumeMeterEffect = new VolumeMeter("Effects"), volumeMeterMusic = new VolumeMeter("Music") } } }; + + muteIcon.Icon = FontAwesome.fa_volume_up; + muteIcon.Scale = new Vector2(2.0f); + muteIcon.Action = () => + { + if (IsMuted) + Unmute(); + else + Mute(); + }; } protected override void LoadComplete() @@ -80,6 +92,8 @@ namespace osu.Game.Graphics.UserInterface.Volume volumeMeterMaster.Increase(); return true; case GlobalAction.ToggleMute: + if (State == Visibility.Hidden) + Show(); if (IsMuted) Unmute(); else @@ -107,6 +121,7 @@ namespace osu.Game.Graphics.UserInterface.Volume audio.AddAdjustment(AdjustableProperty.Volume, muteBindable); IsMuted = true; + muteIcon.Icon = FontAwesome.fa_volume_off; } public void Unmute() @@ -116,6 +131,7 @@ namespace osu.Game.Graphics.UserInterface.Volume audio.RemoveAdjustment(AdjustableProperty.Volume, muteBindable); IsMuted = false; + muteIcon.Icon = FontAwesome.fa_volume_up; } [BackgroundDependencyLoader] From 8471a579e056db875a198921ba3dab1042c4cae0 Mon Sep 17 00:00:00 2001 From: aQaTL Date: Wed, 17 Jan 2018 20:56:44 +0100 Subject: [PATCH 103/138] Removed no longer neccessary "exiting" flag --- osu.Game/OsuGame.cs | 13 ++----------- 1 file changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index de923b3451..56cfffdeae 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -79,8 +79,6 @@ namespace osu.Game private SettingsOverlay settings; - private bool exiting; - public OsuGame(string[] args = null) { this.args = args; @@ -119,13 +117,6 @@ namespace osu.Game Ruleset.ValueChanged += r => configRuleset.Value = r.ID ?? 0; muteWhenInactive = LocalConfig.GetBindable(OsuSetting.MuteWhenInactive); - Host.Window.Exited += () => - { - //Prevent not saving audio levels when user alt tabs before the window closes - if (volume.IsMuted) - volume.Unmute(); - exiting = true; - }; } private ScheduledDelegate scoreLoad; @@ -403,7 +394,7 @@ namespace osu.Game protected override void OnDeactivated() { base.OnDeactivated(); - if (muteWhenInactive && !exiting) + if (muteWhenInactive) { wasMuted = volume.IsMuted; volume.Mute(); @@ -413,7 +404,7 @@ namespace osu.Game protected override void OnActivated() { base.OnActivated(); - if (IsLoaded && muteWhenInactive && !wasMuted && !exiting) + if (IsLoaded && muteWhenInactive && !wasMuted) { volume.Unmute(); } From 18ff57fdf9589a8d2cfffa943ae7bd4fb1733960 Mon Sep 17 00:00:00 2001 From: aQaTL Date: Wed, 17 Jan 2018 21:09:46 +0100 Subject: [PATCH 104/138] Inline changing mute icon properties with object creation --- .../UserInterface/Volume/VolumeControl.cs | 23 ++++++++++--------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs index 2d350a3474..2f565eba22 100644 --- a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs +++ b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs @@ -38,23 +38,24 @@ namespace osu.Game.Graphics.UserInterface.Volume Spacing = new Vector2(15, 0), Children = new Drawable[] { - muteIcon = new IconButton(), + muteIcon = new IconButton + { + Icon = FontAwesome.fa_volume_up, + Scale = new Vector2(2.0f), + Action = () => + { + if (IsMuted) + Unmute(); + else + Mute(); + }, + }, volumeMeterMaster = new VolumeMeter("Master"), volumeMeterEffect = new VolumeMeter("Effects"), volumeMeterMusic = new VolumeMeter("Music") } } }; - - muteIcon.Icon = FontAwesome.fa_volume_up; - muteIcon.Scale = new Vector2(2.0f); - muteIcon.Action = () => - { - if (IsMuted) - Unmute(); - else - Mute(); - }; } protected override void LoadComplete() From 9c09b33e4e75b49090fa7bf0679865c19cbcac02 Mon Sep 17 00:00:00 2001 From: aQaTL Date: Wed, 17 Jan 2018 23:17:59 +0100 Subject: [PATCH 105/138] Removed no longer used "lastVolume" field --- osu.Game/Graphics/UserInterface/Volume/VolumeMeter.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/Volume/VolumeMeter.cs b/osu.Game/Graphics/UserInterface/Volume/VolumeMeter.cs index a1acb5d2ff..ef3702fdf3 100644 --- a/osu.Game/Graphics/UserInterface/Volume/VolumeMeter.cs +++ b/osu.Game/Graphics/UserInterface/Volume/VolumeMeter.cs @@ -18,8 +18,6 @@ namespace osu.Game.Graphics.UserInterface.Volume private readonly Box meterFill; public BindableDouble Bindable { get; } = new BindableDouble(); - private double lastVolume; - public VolumeMeter(string meterName) { Size = new Vector2(40, 180); From 4a85266fca02c4375fafc572fc2c524b08aa9643 Mon Sep 17 00:00:00 2001 From: aQaTL Date: Thu, 18 Jan 2018 17:23:02 +0100 Subject: [PATCH 106/138] Using BindableBool to mute the volume when it's value changes --- .../UserInterface/Volume/VolumeControl.cs | 39 ++++++++----------- osu.Game/OsuGame.cs | 2 - 2 files changed, 16 insertions(+), 25 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs index 2f565eba22..e611c0dfd0 100644 --- a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs +++ b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs @@ -14,8 +14,6 @@ namespace osu.Game.Graphics.UserInterface.Volume { public class VolumeControl : OverlayContainer { - private AudioManager audio; - private readonly VolumeMeter volumeMeterMaster; private readonly IconButton muteIcon; @@ -42,13 +40,7 @@ namespace osu.Game.Graphics.UserInterface.Volume { Icon = FontAwesome.fa_volume_up, Scale = new Vector2(2.0f), - Action = () => - { - if (IsMuted) - Unmute(); - else - Mute(); - }, + Action = () => Adjust(GlobalAction.ToggleMute), }, volumeMeterMaster = new VolumeMeter("Master"), volumeMeterEffect = new VolumeMeter("Effects"), @@ -113,35 +105,36 @@ namespace osu.Game.Graphics.UserInterface.Volume private readonly BindableDouble muteBindable = new BindableDouble(); - public bool IsMuted { get; private set; } + private readonly BindableBool muted = new BindableBool(); + + public bool IsMuted => muted.Value; public void Mute() { - if (IsMuted) - return; - - audio.AddAdjustment(AdjustableProperty.Volume, muteBindable); - IsMuted = true; - muteIcon.Icon = FontAwesome.fa_volume_off; + muted.Value = true; } public void Unmute() { - if (!IsMuted) - return; - - audio.RemoveAdjustment(AdjustableProperty.Volume, muteBindable); - IsMuted = false; - muteIcon.Icon = FontAwesome.fa_volume_up; + muted.Value = false; } [BackgroundDependencyLoader] private void load(AudioManager audio) { - this.audio = audio; volumeMeterMaster.Bindable.BindTo(audio.Volume); volumeMeterEffect.Bindable.BindTo(audio.VolumeSample); volumeMeterMusic.Bindable.BindTo(audio.VolumeTrack); + + muted.ValueChanged += mute => + { + if (mute) + audio.AddAdjustment(AdjustableProperty.Volume, muteBindable); + else + audio.RemoveAdjustment(AdjustableProperty.Volume, muteBindable); + + muteIcon.Icon = mute ? FontAwesome.fa_volume_off : FontAwesome.fa_volume_up; + }; } private ScheduledDelegate popOutDelegate; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 56cfffdeae..f1fb2a4f0a 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -405,9 +405,7 @@ namespace osu.Game { base.OnActivated(); if (IsLoaded && muteWhenInactive && !wasMuted) - { volume.Unmute(); - } } public bool OnReleased(GlobalAction action) => false; From c4feb67bceede170fa341a2fe9f56fc7f43c83ca Mon Sep 17 00:00:00 2001 From: aQaTL Date: Sat, 20 Jan 2018 11:45:04 +0100 Subject: [PATCH 107/138] Using field properties to set mute / unmute instead of separate methods --- .../UserInterface/Volume/VolumeControl.cs | 28 ++++++++----------- osu.Game/OsuGame.cs | 6 ++-- 2 files changed, 14 insertions(+), 20 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs index e611c0dfd0..60242393ab 100644 --- a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs +++ b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs @@ -85,12 +85,8 @@ namespace osu.Game.Graphics.UserInterface.Volume volumeMeterMaster.Increase(); return true; case GlobalAction.ToggleMute: - if (State == Visibility.Hidden) - Show(); - if (IsMuted) - Unmute(); - else - Mute(); + Show(); + Muted = !Muted; return true; } @@ -107,16 +103,10 @@ namespace osu.Game.Graphics.UserInterface.Volume private readonly BindableBool muted = new BindableBool(); - public bool IsMuted => muted.Value; - - public void Mute() + public bool Muted { - muted.Value = true; - } - - public void Unmute() - { - muted.Value = false; + get => muted.Value; + set => muted.Value = value; } [BackgroundDependencyLoader] @@ -129,11 +119,15 @@ namespace osu.Game.Graphics.UserInterface.Volume muted.ValueChanged += mute => { if (mute) + { audio.AddAdjustment(AdjustableProperty.Volume, muteBindable); + muteIcon.Icon = FontAwesome.fa_volume_off; + } else + { audio.RemoveAdjustment(AdjustableProperty.Volume, muteBindable); - - muteIcon.Icon = mute ? FontAwesome.fa_volume_off : FontAwesome.fa_volume_up; + muteIcon.Icon = FontAwesome.fa_volume_up; + } }; } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index f1fb2a4f0a..b938595d57 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -396,8 +396,8 @@ namespace osu.Game base.OnDeactivated(); if (muteWhenInactive) { - wasMuted = volume.IsMuted; - volume.Mute(); + wasMuted = volume.Muted; + volume.Muted = true; } } @@ -405,7 +405,7 @@ namespace osu.Game { base.OnActivated(); if (IsLoaded && muteWhenInactive && !wasMuted) - volume.Unmute(); + volume.Muted = false; } public bool OnReleased(GlobalAction action) => false; From 57cd50c45ee2d45f2ba9fb6804cce7acdc219066 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Jan 2018 17:39:10 +0900 Subject: [PATCH 108/138] Reorder the way input is handled for replays Fixes https://github.com/ppy/osu/issues/1625 . --- .../Objects/Drawables/DrawableHitObject.cs | 2 ++ osu.Game/Rulesets/UI/RulesetInputManager.cs | 18 ++++++++++++++++-- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index 2db02724ed..e4f8124bce 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -71,6 +71,8 @@ namespace osu.Game.Rulesets.Objects.Drawables public override bool HandleKeyboardInput => Interactive; public override bool HandleMouseInput => Interactive; + public override bool MaskingAffectsInput => false; + public override bool RemoveWhenNotAlive => false; public override bool RemoveCompletedTransforms => false; protected override bool RequiresChildrenUpdate => true; diff --git a/osu.Game/Rulesets/UI/RulesetInputManager.cs b/osu.Game/Rulesets/UI/RulesetInputManager.cs index 223586a959..e32b38a013 100644 --- a/osu.Game/Rulesets/UI/RulesetInputManager.cs +++ b/osu.Game/Rulesets/UI/RulesetInputManager.cs @@ -136,9 +136,20 @@ namespace osu.Game.Rulesets.UI int loops = 0; while (validState && requireMoreUpdateLoops && loops++ < max_catch_up_updates_per_frame) + { if (!base.UpdateSubTree()) return false; + if (isAttached) + { + // When handling replay input, we need to consider the possibility of fast-forwarding, which may cause the clock to be updated + // to a point very far into the future, then playing a frame at that time. In such a case, lifetime MUST be updated before + // input is handled. This is why base.Update is not called from the derived Update when handling replay input, and is instead + // called manually at the correct time here. + base.Update(); + } + } + return true; } @@ -173,8 +184,11 @@ namespace osu.Game.Rulesets.UI // to ensure that the its time is valid for our children before input is processed Clock.ProcessFrame(); - // Process input - base.Update(); + if (!isAttached) + { + // For non-replay input handling, this provides equivalent input ordering as if Update was not overridden + base.Update(); + } } #endregion From 92df704fbd235f4637f6f31888f8bba1c9faebe8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 25 Jan 2018 21:37:38 +0900 Subject: [PATCH 109/138] Fix up taiko not having any important frames --- .../Replays/TaikoAutoGenerator.cs | 14 +++++++------- .../Replays/TaikoReplayFrame.cs | 17 +++++++++++++++++ .../osu.Game.Rulesets.Taiko.csproj | 1 + 3 files changed, 25 insertions(+), 7 deletions(-) create mode 100644 osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs index c1fe2c13a8..002159439d 100644 --- a/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoAutoGenerator.cs @@ -35,8 +35,8 @@ namespace osu.Game.Rulesets.Taiko.Replays { bool hitButton = true; - Frames.Add(new ReplayFrame(-100000, null, null, ReplayButtonState.None)); - Frames.Add(new ReplayFrame(Beatmap.HitObjects[0].StartTime - 1000, null, null, ReplayButtonState.None)); + Frames.Add(new TaikoReplayFrame(-100000, ReplayButtonState.None)); + Frames.Add(new TaikoReplayFrame(Beatmap.HitObjects[0].StartTime - 1000, ReplayButtonState.None)); for (int i = 0; i < Beatmap.HitObjects.Count; i++) { @@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Taiko.Replays break; } - Frames.Add(new ReplayFrame(j, null, null, button)); + Frames.Add(new TaikoReplayFrame(j, button)); d = (d + 1) % 4; if (++count == req) break; @@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Taiko.Replays { foreach (var tick in drumRoll.NestedHitObjects.OfType()) { - Frames.Add(new ReplayFrame(tick.StartTime, null, null, hitButton ? ReplayButtonState.Right1 : ReplayButtonState.Right2)); + Frames.Add(new TaikoReplayFrame(tick.StartTime, hitButton ? ReplayButtonState.Right1 : ReplayButtonState.Right2)); hitButton = !hitButton; } } @@ -107,18 +107,18 @@ namespace osu.Game.Rulesets.Taiko.Replays button = hitButton ? ReplayButtonState.Left1 : ReplayButtonState.Left2; } - Frames.Add(new ReplayFrame(h.StartTime, null, null, button)); + Frames.Add(new TaikoReplayFrame(h.StartTime, button)); } else throw new InvalidOperationException("Unknown hit object type."); - Frames.Add(new ReplayFrame(endTime + KEY_UP_DELAY, null, null, ReplayButtonState.None)); + Frames.Add(new TaikoReplayFrame(endTime + KEY_UP_DELAY, ReplayButtonState.None)); if (i < Beatmap.HitObjects.Count - 1) { double waitTime = Beatmap.HitObjects[i + 1].StartTime - 1000; if (waitTime > endTime) - Frames.Add(new ReplayFrame(waitTime, null, null, ReplayButtonState.None)); + Frames.Add(new TaikoReplayFrame(waitTime, ReplayButtonState.None)); } hitButton = !hitButton; diff --git a/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs new file mode 100644 index 0000000000..0c60cdc109 --- /dev/null +++ b/osu.Game.Rulesets.Taiko/Replays/TaikoReplayFrame.cs @@ -0,0 +1,17 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Replays; + +namespace osu.Game.Rulesets.Taiko.Replays +{ + public class TaikoReplayFrame : ReplayFrame + { + public override bool IsImportant => MouseLeft || MouseRight; + + public TaikoReplayFrame(double time, ReplayButtonState buttons) + : base(time, null, null, buttons) + { + } + } +} diff --git a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj index 36ac9384cf..7fb764fd0b 100644 --- a/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj +++ b/osu.Game.Rulesets.Taiko/osu.Game.Rulesets.Taiko.csproj @@ -91,6 +91,7 @@ + From 0a505dde2e73b89ffeb259090e6e2d577a3c80d4 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 26 Jan 2018 14:47:16 +0900 Subject: [PATCH 110/138] Remove MaskingAffectsInput override --- osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs index e4f8124bce..2db02724ed 100644 --- a/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs +++ b/osu.Game/Rulesets/Objects/Drawables/DrawableHitObject.cs @@ -71,8 +71,6 @@ namespace osu.Game.Rulesets.Objects.Drawables public override bool HandleKeyboardInput => Interactive; public override bool HandleMouseInput => Interactive; - public override bool MaskingAffectsInput => false; - public override bool RemoveWhenNotAlive => false; public override bool RemoveCompletedTransforms => false; protected override bool RequiresChildrenUpdate => true; From df221b6786f04df96b57d35b0910b9ebfaa83309 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jan 2018 17:45:23 +0900 Subject: [PATCH 111/138] Remove usage of ValueTuple to allow for dynamic recompilation --- osu.Game.Tests/Visual/TestCaseChatLink.cs | 12 +++++++++ osu.Game/Online/Chat/MessageFormatter.cs | 32 ++++++++++++++++------- 2 files changed, 34 insertions(+), 10 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index 722e3c30f3..136a2fae89 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -10,7 +10,9 @@ using osu.Game.Online.Chat; using osu.Game.Overlays.Chat; using osu.Game.Users; using System; +using System.Collections.Generic; using System.Linq; +using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; namespace osu.Game.Tests.Visual @@ -20,6 +22,16 @@ namespace osu.Game.Tests.Visual private readonly TestChatLineContainer textContainer; private Color4 linkColour; + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ChatLine), + typeof(Message), + typeof(LinkFlowContainer), + typeof(DummyEchoMessage), + typeof(LocalEchoMessage), + typeof(MessageFormatter) + }; + public TestCaseChatLink() { Add(textContainer = new TestChatLineContainer diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 3a396a04c0..2c15f48f95 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -67,7 +67,7 @@ namespace osu.Game.Online.Chat result.Links.ForEach(l => l.Index -= l.Index > index ? m.Length - displayText.Length : 0); var details = getLinkDetails(linkText); - result.Links.Add(new Link(linkText, index, displayText.Length, linkActionOverride ?? details.linkType, details.linkArgument)); + result.Links.Add(new Link(linkText, index, displayText.Length, linkActionOverride ?? details.Action, details.Argument)); //adjust the offset for processing the current matches group. captureOffset += m.Length - displayText.Length; @@ -95,11 +95,11 @@ namespace osu.Game.Online.Chat } var details = getLinkDetails(link); - result.Links.Add(new Link(link, index, indexLength, details.linkType, details.linkArgument)); + result.Links.Add(new Link(link, index, indexLength, details.Action, details.Argument)); } } - private static (LinkAction linkType, string linkArgument) getLinkDetails(string url) + private static LinkDetails getLinkDetails(string url) { var args = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); args[0] = args[0].TrimEnd(':'); @@ -115,19 +115,19 @@ namespace osu.Game.Online.Chat { case "b": case "beatmaps": - return (LinkAction.OpenBeatmap, args[3]); + return new LinkDetails(LinkAction.OpenBeatmap, args[3]); case "s": case "beatmapsets": case "d": - return (LinkAction.OpenBeatmapSet, args[3]); + return new LinkDetails(LinkAction.OpenBeatmapSet, args[3]); } } - return (LinkAction.External, null); + return new LinkDetails(LinkAction.External, null); case "osu": // every internal link also needs some kind of argument if (args.Length < 3) - return (LinkAction.External, null); + return new LinkDetails(LinkAction.External, null); LinkAction linkType; switch (args[1]) @@ -153,11 +153,11 @@ namespace osu.Game.Online.Chat break; } - return (linkType, args[2]); + return new LinkDetails(linkType, args[2]); case "osump": - return (LinkAction.JoinMultiplayerMatch, args[1]); + return new LinkDetails(LinkAction.JoinMultiplayerMatch, args[1]); default: - return (LinkAction.External, null); + return new LinkDetails(LinkAction.External, null); } } @@ -215,6 +215,18 @@ namespace osu.Game.Online.Chat OriginalText = Text = text; } } + + public class LinkDetails + { + public LinkAction Action; + public string Argument; + + public LinkDetails(LinkAction action, string argument) + { + Action = action; + Argument = argument; + } + } } public enum LinkAction From c07a31a48495a3a832bbc07e0d98e2d4fefcf7ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jan 2018 17:45:39 +0900 Subject: [PATCH 112/138] Add new (failing) tests --- osu.Game.Tests/Visual/TestCaseChatLink.cs | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index 136a2fae89..74faa3f48f 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -86,7 +86,7 @@ namespace osu.Game.Tests.Visual var linkSprites = linkCompilers.SelectMany(comp => ((DrawableLinkCompiler)comp).Parts); return linkSprites.All(d => d.Colour == linkColour) - && newLine.ContentFlow.Except(linkSprites.Concat(linkCompilers)).All(d => d.Colour == textColour); + && newLine.ContentFlow.Except(linkSprites.Concat(linkCompilers)).All(d => d.Colour == textColour); } } @@ -102,9 +102,15 @@ namespace osu.Game.Tests.Visual addMessageWithChecks("[https://osu.ppy.sh/home This is only a link to the new osu webpage but this is supposed to test word wrap.]", 1, expectedActions: LinkAction.External); addMessageWithChecks("is now listening to [https://osu.ppy.sh/s/93523 IMAGE -MATERIAL- ]", 1, true, expectedActions: LinkAction.OpenBeatmapSet); addMessageWithChecks("is now playing [https://osu.ppy.sh/b/252238 IMAGE -MATERIAL- ]", 1, true, expectedActions: LinkAction.OpenBeatmap); - addMessageWithChecks("Let's (try)[https://osu.ppy.sh/home] [https://osu.ppy.sh/b/252238 multiple links] https://osu.ppy.sh/home", 3, expectedActions: new[] { LinkAction.External, LinkAction.OpenBeatmap, LinkAction.External }); + addMessageWithChecks("Let's (try)[https://osu.ppy.sh/home] [https://osu.ppy.sh/b/252238 multiple links] https://osu.ppy.sh/home", 3, + expectedActions: new[] { LinkAction.External, LinkAction.OpenBeatmap, LinkAction.External }); // note that there's 0 links here (they get removed if a channel is not found) addMessageWithChecks("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present)."); + addMessageWithChecks("Join my multiplayer game osump://12346.", expectedActions: LinkAction.JoinMultiplayerMatch); + addMessageWithChecks("Join my [multiplayer game](osump://12346).", expectedActions: LinkAction.JoinMultiplayerMatch); + addMessageWithChecks("Join my [#english](osu://chan/english).", expectedActions: LinkAction.OpenChannel); + addMessageWithChecks("Join my osu://chan/english.", expectedActions: LinkAction.OpenChannel); + addMessageWithChecks("Join my #english.", expectedActions: LinkAction.OpenChannel); addMessageWithChecks("I am important!", 0, false, true); addMessageWithChecks("feels important", 0, true, true); addMessageWithChecks("likes to post this [https://osu.ppy.sh/home link].", 1, true, true, expectedActions: LinkAction.External); From f996acd7e6b55dc0493f2c899dba16f49d651883 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jan 2018 17:56:43 +0900 Subject: [PATCH 113/138] Add back old non-visual tests --- osu.Game.Tests/Chat/MessageFormatterTests.cs | 189 +++++++++++++++++++ osu.Game.Tests/osu.Game.Tests.csproj | 1 + 2 files changed, 190 insertions(+) create mode 100644 osu.Game.Tests/Chat/MessageFormatterTests.cs diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs new file mode 100644 index 0000000000..0fc3054f44 --- /dev/null +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -0,0 +1,189 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using NUnit.Framework; +using osu.Game.Online.Chat; + +namespace osu.Game.Tests.Chat +{ + [TestFixture] + public class MessageFormatterTests + { + [Test] + public void TestBareLink() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a http://www.basic-link.com/?test=test." }); + + Assert.AreEqual("This is a http://www.basic-link.com/?test=test.", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("http://www.basic-link.com/?test=test", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(36, result.Links[0].Length); + } + + [Test] + public void TestMultipleComplexLinks() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a http://test.io/link#fragment. (see https://twitter.com). Also, This string should not be altered. http://example.com/" }); + + Assert.AreEqual("This is a http://test.io/link#fragment. (see https://twitter.com). Also, This string should not be altered. http://example.com/", result.DisplayContent); + Assert.AreEqual(3, result.Links.Count); + + Assert.AreEqual("http://test.io/link#fragment", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(28, result.Links[0].Length); + + Assert.AreEqual("https://twitter.com", result.Links[1].Url); + Assert.AreEqual(45, result.Links[1].Index); + Assert.AreEqual(19, result.Links[1].Length); + + Assert.AreEqual("http://example.com/", result.Links[2].Url); + Assert.AreEqual(108, result.Links[2].Index); + Assert.AreEqual(19, result.Links[2].Length); + } + + [Test] + public void TestAjaxLinks() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "https://twitter.com/#!/hashbanglinks" }); + + Assert.AreEqual("https://twitter.com/#!/hashbanglinks", result.DisplayContent); + Assert.AreEqual(0, result.Links[0].Index); + Assert.AreEqual(36, result.Links[0].Length); + } + + [Test] + public void TestUnixHomeLinks() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "http://www.chiark.greenend.org.uk/~sgtatham/putty/" }); + + Assert.AreEqual("http://www.chiark.greenend.org.uk/~sgtatham/putty/", result.DisplayContent); + Assert.AreEqual(0, result.Links[0].Index); + Assert.AreEqual(50, result.Links[0].Length); + } + + [Test] + public void TestCaseInsensitiveLinks() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "look: http://puu.sh/7Ggh8xcC6/asf0asd9876.NEF" }); + + Assert.AreEqual("look: http://puu.sh/7Ggh8xcC6/asf0asd9876.NEF", result.DisplayContent); + Assert.AreEqual(6, result.Links[0].Index); + Assert.AreEqual(39, result.Links[0].Length); + } + + [Test] + public void TestWikiLink() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [[Wiki Link]]." }); + + Assert.AreEqual("This is a Wiki Link.", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki Link", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(9, result.Links[0].Length); + } + + [Test] + public void TestMultiWikiLink() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [[Wiki Link]] [[Wiki:Link]][[Wiki.Link]]." }); + + Assert.AreEqual("This is a Wiki Link Wiki:LinkWiki.Link.", result.DisplayContent); + Assert.AreEqual(3, result.Links.Count); + + Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki Link", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(9, result.Links[0].Length); + + Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki:Link", result.Links[1].Url); + Assert.AreEqual(20, result.Links[1].Index); + Assert.AreEqual(9, result.Links[1].Length); + + Assert.AreEqual("https://osu.ppy.sh/wiki/Wiki.Link", result.Links[2].Url); + Assert.AreEqual(29, result.Links[2].Index); + Assert.AreEqual(9, result.Links[2].Length); + } + + [Test] + public void TestOldFormatLink() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a (simple test)[https://osu.ppy.sh]." }); + + Assert.AreEqual("This is a simple test.", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(11, result.Links[0].Length); + } + + [Test] + public void TestNewFormatLink() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh simple test]." }); + + Assert.AreEqual("This is a simple test.", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(11, result.Links[0].Length); + } + + [Test] + public void TestRecursiveBreaking() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [https://osu.ppy.sh [[simple test]]]." }); + + Assert.AreEqual("This is a [[simple test]].", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(15, result.Links[0].Length); + } + + [Test] + public void TestLinkComplex() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [http://www.simple-test.com simple test] with some [traps] and [[wiki links]]. Don't forget to visit https://osu.ppy.sh (now!)[http://google.com]\uD83D\uDE12" }); + + Assert.AreEqual("This is a simple test with some [traps] and wiki links. Don't forget to visit https://osu.ppy.sh now!\0\0\0", result.DisplayContent); + Assert.AreEqual(5, result.Links.Count); + + Link f = result.Links.Find(l => l.Url == "https://osu.ppy.sh/wiki/wiki links"); + Assert.AreEqual(44, f.Index); + Assert.AreEqual(10, f.Length); + + f = result.Links.Find(l => l.Url == "http://www.simple-test.com"); + Assert.AreEqual(10, f.Index); + Assert.AreEqual(11, f.Length); + + f = result.Links.Find(l => l.Url == "http://google.com"); + Assert.AreEqual(97, f.Index); + Assert.AreEqual(4, f.Length); + + f = result.Links.Find(l => l.Url == "https://osu.ppy.sh"); + Assert.AreEqual(78, f.Index); + Assert.AreEqual(18, f.Length); + + f = result.Links.Find(l => l.Url == "\uD83D\uDE12"); + Assert.AreEqual(101, f.Index); + Assert.AreEqual(3, f.Length); + } + + [Test] + public void TestEmoji() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "Hello world\uD83D\uDE12<--This is an emoji,There are more:\uD83D\uDE10\uD83D\uDE00,\uD83D\uDE20" }); + Assert.AreEqual("Hello world\0\0\0<--This is an emoji,There are more:\0\0\0\0\0\0,\0\0\0", result.DisplayContent); + Assert.AreEqual(result.Links.Count, 4); + Assert.AreEqual(result.Links[0].Index, 11); + Assert.AreEqual(result.Links[1].Index, 49); + Assert.AreEqual(result.Links[2].Index, 52); + Assert.AreEqual(result.Links[3].Index, 56); + Assert.AreEqual(result.Links[0].Url, "\uD83D\uDE12"); + Assert.AreEqual(result.Links[1].Url, "\uD83D\uDE10"); + Assert.AreEqual(result.Links[2].Url, "\uD83D\uDE00"); + Assert.AreEqual(result.Links[3].Url, "\uD83D\uDE20"); + } + } +} diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index f308100d2c..8f5fd3587a 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -90,6 +90,7 @@ + From 1920a4e029a4641b65ba2c14817fb9e297b3a5d6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 29 Jan 2018 18:47:14 +0900 Subject: [PATCH 114/138] Prepare tests for fixing --- osu.Game.Tests/Visual/TestCaseChatLink.cs | 30 ++++++++++++++--------- 1 file changed, 18 insertions(+), 12 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index 74faa3f48f..eeb4889f94 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -50,13 +50,14 @@ namespace osu.Game.Tests.Visual private void addMessageWithChecks(string text, int linkAmount = 0, bool isAction = false, bool isImportant = false, params LinkAction[] expectedActions) { - var newLine = new ChatLine(new DummyMessage(text, isAction, isImportant)); + int index = textContainer.Count + 1; + var newLine = new ChatLine(new DummyMessage(text, isAction, isImportant, index)); textContainer.Add(newLine); - AddAssert($"msg #{textContainer.Count} has {linkAmount} link(s)", () => newLine.Message.Links.Count == linkAmount); - AddAssert($"msg #{textContainer.Count} has the right action", hasExpectedActions); - AddAssert($"msg #{textContainer.Count} is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic()); - AddAssert($"msg #{textContainer.Count} shows {linkAmount} link(s)", isShowingLinks); + AddAssert($"msg #{index} has {linkAmount} link(s)", () => newLine.Message.Links.Count == linkAmount); + AddAssert($"msg #{index} has the right action", hasExpectedActions); + AddAssert($"msg #{index} is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic()); + AddAssert($"msg #{index} shows {linkAmount} link(s)", isShowingLinks); bool hasExpectedActions() { @@ -106,14 +107,14 @@ namespace osu.Game.Tests.Visual expectedActions: new[] { LinkAction.External, LinkAction.OpenBeatmap, LinkAction.External }); // note that there's 0 links here (they get removed if a channel is not found) addMessageWithChecks("#lobby or #osu would be blue (and work) in the ChatDisplay test (when a proper ChatOverlay is present)."); - addMessageWithChecks("Join my multiplayer game osump://12346.", expectedActions: LinkAction.JoinMultiplayerMatch); - addMessageWithChecks("Join my [multiplayer game](osump://12346).", expectedActions: LinkAction.JoinMultiplayerMatch); - addMessageWithChecks("Join my [#english](osu://chan/english).", expectedActions: LinkAction.OpenChannel); - addMessageWithChecks("Join my osu://chan/english.", expectedActions: LinkAction.OpenChannel); - addMessageWithChecks("Join my #english.", expectedActions: LinkAction.OpenChannel); addMessageWithChecks("I am important!", 0, false, true); addMessageWithChecks("feels important", 0, true, true); addMessageWithChecks("likes to post this [https://osu.ppy.sh/home link].", 1, true, true, expectedActions: LinkAction.External); + addMessageWithChecks("Join my multiplayer game osump://12346.",1, expectedActions: LinkAction.JoinMultiplayerMatch); + addMessageWithChecks("Join my [multiplayer game](osump://12346).", 1, expectedActions: LinkAction.JoinMultiplayerMatch); + addMessageWithChecks("Join my [#english](osu://chan/english).", 1, expectedActions: LinkAction.OpenChannel); + addMessageWithChecks("Join my osu://chan/english.", 1, expectedActions: LinkAction.OpenChannel); + addMessageWithChecks("Join my #english.", 1, expectedActions: LinkAction.OpenChannel); } private void testEcho() @@ -173,12 +174,17 @@ namespace osu.Game.Tests.Visual public new DateTimeOffset Timestamp = DateTimeOffset.Now; - public DummyMessage(string text, bool isAction = false, bool isImportant = false) + public DummyMessage(string text, bool isAction = false, bool isImportant = false, int number = 0) : base(messageCounter++) { Content = text; IsAction = isAction; - Sender = isImportant ? TEST_SENDER_BACKGROUND : TEST_SENDER; + Sender = new User + { + Username = $"User {number}", + Id = number, + Colour = isImportant ? "#250cc9" : null, + }; } } From c97ea3ed606a60d50597198aa4a545f3a59477c7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 30 Jan 2018 15:49:55 +0900 Subject: [PATCH 115/138] Post-process beatmap before applying defaults --- osu.Game/Rulesets/UI/RulesetContainer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Rulesets/UI/RulesetContainer.cs b/osu.Game/Rulesets/UI/RulesetContainer.cs index 8f72644b28..231250e858 100644 --- a/osu.Game/Rulesets/UI/RulesetContainer.cs +++ b/osu.Game/Rulesets/UI/RulesetContainer.cs @@ -240,13 +240,13 @@ namespace osu.Game.Rulesets.UI foreach (var mod in Mods.OfType()) mod.ApplyToDifficulty(Beatmap.BeatmapInfo.BaseDifficulty); + // Post-process the beatmap + processor.PostProcess(Beatmap); + // Apply defaults foreach (var h in Beatmap.HitObjects) h.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.BaseDifficulty); - // Post-process the beatmap - processor.PostProcess(Beatmap); - KeyBindingInputManager = CreateInputManager(); KeyBindingInputManager.RelativeSizeAxes = Axes.Both; From b293408147a3fbb84bcfe7c8b704755dcf4afeaa Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 30 Jan 2018 15:50:48 +0900 Subject: [PATCH 116/138] Construct the head of sliders from Slider --- .../Objects/Drawables/DrawableSlider.cs | 32 ++++++------------- osu.Game.Rulesets.Osu/Objects/Slider.cs | 14 ++++++++ 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index af947817c0..7462478408 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -19,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { private readonly Slider slider; - public readonly DrawableHitCircle InitialCircle; + public readonly DrawableHitCircle HeadCircle; private readonly List components = new List(); @@ -51,27 +51,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables AlwaysPresent = true, Alpha = 0 }, - InitialCircle = new DrawableHitCircle(new HitCircle - { - StartTime = s.StartTime, - Position = s.StackedPosition, - IndexInCurrentCombo = s.IndexInCurrentCombo, - Scale = s.Scale, - ComboColour = s.ComboColour, - Samples = s.Samples, - SampleControlPoint = s.SampleControlPoint, - TimePreempt = s.TimePreempt, - TimeFadein = s.TimeFadein, - HitWindow300 = s.HitWindow300, - HitWindow100 = s.HitWindow100, - HitWindow50 = s.HitWindow50 - }) + HeadCircle = new DrawableHitCircle(s.HeadCircle) }; components.Add(Body); components.Add(Ball); - AddNested(InitialCircle); + AddNested(HeadCircle); foreach (var tick in s.NestedHitObjects.OfType()) { @@ -121,8 +107,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables currentSpan = span; //todo: we probably want to reconsider this before adding scoring, but it looks and feels nice. - if (!InitialCircle.Judgements.Any(j => j.IsHit)) - InitialCircle.Position = slider.Curve.PositionAt(progress); + if (!HeadCircle.Judgements.Any(j => j.IsHit)) + HeadCircle.Position = slider.Curve.PositionAt(progress); foreach (var c in components.OfType()) c.UpdateProgress(progress, span); foreach (var c in components.OfType()) c.UpdateSnakingPosition(slider.Curve.PositionAt(Body.SnakedStart ?? 0), slider.Curve.PositionAt(Body.SnakedEnd ?? 0)); @@ -135,13 +121,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { var judgementsCount = ticks.Children.Count + repeatPoints.Children.Count + 1; var judgementsHit = ticks.Children.Count(t => t.Judgements.Any(j => j.IsHit)) + repeatPoints.Children.Count(t => t.Judgements.Any(j => j.IsHit)); - if (InitialCircle.Judgements.Any(j => j.IsHit)) + if (HeadCircle.Judgements.Any(j => j.IsHit)) judgementsHit++; var hitFraction = (double)judgementsHit / judgementsCount; - if (hitFraction == 1 && InitialCircle.Judgements.Any(j => j.Result == HitResult.Great)) + if (hitFraction == 1 && HeadCircle.Judgements.Any(j => j.Result == HitResult.Great)) AddJudgement(new OsuJudgement { Result = HitResult.Great }); - else if (hitFraction >= 0.5 && InitialCircle.Judgements.Any(j => j.Result >= HitResult.Good)) + else if (hitFraction >= 0.5 && HeadCircle.Judgements.Any(j => j.Result >= HitResult.Good)) AddJudgement(new OsuJudgement { Result = HitResult.Good }); else if (hitFraction > 0) AddJudgement(new OsuJudgement { Result = HitResult.Meh }); @@ -173,7 +159,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables } } - public Drawable ProxiedLayer => InitialCircle.ApproachCircle; + public Drawable ProxiedLayer => HeadCircle.ApproachCircle; public override Vector2 SelectionPoint => ToScreenSpace(Body.Position); public override Quad SelectionQuad => Body.PathDrawQuad; diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 79bb14a475..fba016a55e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -80,6 +80,8 @@ namespace osu.Game.Rulesets.Osu.Objects public double Velocity; public double TickDistance; + public HitCircle HeadCircle; + protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { base.ApplyDefaultsToSelf(controlPointInfo, difficulty); @@ -91,6 +93,18 @@ namespace osu.Game.Rulesets.Osu.Objects Velocity = scoringDistance / timingPoint.BeatLength; TickDistance = scoringDistance / difficulty.SliderTickRate; + + HeadCircle = new HitCircle + { + StartTime = StartTime, + Position = StackedPosition, + IndexInCurrentCombo = IndexInCurrentCombo, + ComboColour = ComboColour, + Samples = Samples, + SampleControlPoint = SampleControlPoint + }; + + HeadCircle.ApplyDefaults(controlPointInfo, difficulty); } protected override void CreateNestedHitObjects() From 702c4efb88eb26fb87c6ce9f3321ecd9ba288685 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 30 Jan 2018 16:24:23 +0900 Subject: [PATCH 117/138] Give Slider a tail hitobject to make slider ends counts towards score --- .../Judgements/OsuSliderTailJudgement.cs | 13 ++++++++ .../Objects/Drawables/DrawableSlider.cs | 26 ++++++++------- .../Objects/Drawables/DrawableSliderTail.cs | 32 +++++++++++++++++++ .../Objects/Drawables/DrawableSliderTick.cs | 4 +-- .../Objects/Drawables/IRequireTracking.cs | 13 ++++++++ osu.Game.Rulesets.Osu/Objects/Slider.cs | 12 +++++++ .../osu.Game.Rulesets.Osu.csproj | 3 ++ 7 files changed, 89 insertions(+), 14 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs create mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs create mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/IRequireTracking.cs diff --git a/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs b/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs new file mode 100644 index 0000000000..a6e67ea979 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Judgements/OsuSliderTailJudgement.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Osu.Judgements +{ + public class OsuSliderTailJudgement : OsuJudgement + { + public override bool AffectsCombo => false; + protected override int NumericResultFor(HitResult result) => 0; + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 7462478408..7247fe1362 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -18,14 +18,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables public class DrawableSlider : DrawableOsuHitObject, IDrawableHitObjectWithProxiedApproach { private readonly Slider slider; - - public readonly DrawableHitCircle HeadCircle; - private readonly List components = new List(); - private readonly Container ticks; - private readonly Container repeatPoints; - + public readonly DrawableHitCircle HeadCircle; public readonly SliderBody Body; public readonly SliderBall Ball; @@ -34,6 +29,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables { slider = s; + DrawableSliderTail tail; + Container ticks; + Container repeatPoints; + Children = new Drawable[] { Body = new SliderBody(s) @@ -51,7 +50,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables AlwaysPresent = true, Alpha = 0 }, - HeadCircle = new DrawableHitCircle(s.HeadCircle) + HeadCircle = new DrawableHitCircle(s.HeadCircle), + tail = new DrawableSliderTail(s.TailCircle) }; components.Add(Body); @@ -59,6 +59,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables AddNested(HeadCircle); + AddNested(tail); + components.Add(tail); + foreach (var tick in s.NestedHitObjects.OfType()) { var spanStartTime = s.StartTime + tick.SpanIndex * s.SpanDuration; @@ -73,6 +76,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables }; ticks.Add(drawableTick); + components.Add(drawableTick); AddNested(drawableTick); } @@ -112,17 +116,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables foreach (var c in components.OfType()) c.UpdateProgress(progress, span); foreach (var c in components.OfType()) c.UpdateSnakingPosition(slider.Curve.PositionAt(Body.SnakedStart ?? 0), slider.Curve.PositionAt(Body.SnakedEnd ?? 0)); - foreach (var t in ticks.Children) t.Tracking = Ball.Tracking; + foreach (var t in components.OfType()) t.Tracking = Ball.Tracking; } protected override void CheckForJudgements(bool userTriggered, double timeOffset) { if (!userTriggered && Time.Current >= slider.EndTime) { - var judgementsCount = ticks.Children.Count + repeatPoints.Children.Count + 1; - var judgementsHit = ticks.Children.Count(t => t.Judgements.Any(j => j.IsHit)) + repeatPoints.Children.Count(t => t.Judgements.Any(j => j.IsHit)); - if (HeadCircle.Judgements.Any(j => j.IsHit)) - judgementsHit++; + var judgementsCount = NestedHitObjects.Count; + var judgementsHit = NestedHitObjects.Count(h => h.IsHit); var hitFraction = (double)judgementsHit / judgementsCount; if (hitFraction == 1 && HeadCircle.Judgements.Any(j => j.Result == HitResult.Great)) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs new file mode 100644 index 0000000000..8835fc2b29 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTail.cs @@ -0,0 +1,32 @@ +// 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.Game.Rulesets.Osu.Judgements; +using osu.Game.Rulesets.Scoring; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables +{ + public class DrawableSliderTail : DrawableOsuHitObject, IRequireTracking + { + /// + /// The judgement text is provided by the . + /// + public override bool DisplayJudgement => false; + + public bool Tracking { get; set; } + + public DrawableSliderTail(HitCircle hitCircle) + : base(hitCircle) + { + AlwaysPresent = true; + RelativeSizeAxes = Axes.Both; + } + + protected override void CheckForJudgements(bool userTriggered, double timeOffset) + { + if (!userTriggered && timeOffset >= 0) + AddJudgement(new OsuSliderTailJudgement { Result = Tracking ? HitResult.Great : HitResult.Miss }); + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs index 09985752a4..ae76f1e0e1 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSliderTick.cs @@ -12,14 +12,14 @@ using osu.Game.Rulesets.Scoring; namespace osu.Game.Rulesets.Osu.Objects.Drawables { - public class DrawableSliderTick : DrawableOsuHitObject + public class DrawableSliderTick : DrawableOsuHitObject, IRequireTracking { private readonly SliderTick sliderTick; public double FadeInTime; public double FadeOutTime; - public bool Tracking; + public bool Tracking { get; set; } public override bool DisplayJudgement => false; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/IRequireTracking.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/IRequireTracking.cs new file mode 100644 index 0000000000..98fc686dd3 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/IRequireTracking.cs @@ -0,0 +1,13 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +namespace osu.Game.Rulesets.Osu.Objects.Drawables +{ + public interface IRequireTracking + { + /// + /// Whether the is currently being tracked by the user. + /// + bool Tracking { get; set; } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index fba016a55e..0c86c64be7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -81,6 +81,7 @@ namespace osu.Game.Rulesets.Osu.Objects public double TickDistance; public HitCircle HeadCircle; + public HitCircle TailCircle; protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty) { @@ -104,7 +105,18 @@ namespace osu.Game.Rulesets.Osu.Objects SampleControlPoint = SampleControlPoint }; + TailCircle = new HitCircle + { + StartTime = EndTime, + Position = StackedEndPosition, + IndexInCurrentCombo = IndexInCurrentCombo, + ComboColour = ComboColour, + Samples = Samples, + SampleControlPoint = SampleControlPoint + }; + HeadCircle.ApplyDefaults(controlPointInfo, difficulty); + TailCircle.ApplyDefaults(controlPointInfo, difficulty); } protected override void CreateNestedHitObjects() diff --git a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj index 74a3883f0a..97a003513f 100644 --- a/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj +++ b/osu.Game.Rulesets.Osu/osu.Game.Rulesets.Osu.csproj @@ -55,6 +55,7 @@ + @@ -75,6 +76,8 @@ + + From 27357e100acc1f82a9f5188a4f2a53c6d7eaaa99 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 30 Jan 2018 16:28:38 +0900 Subject: [PATCH 118/138] Simplify condition --- osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs index 7247fe1362..5f464402d0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/DrawableSlider.cs @@ -111,7 +111,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables currentSpan = span; //todo: we probably want to reconsider this before adding scoring, but it looks and feels nice. - if (!HeadCircle.Judgements.Any(j => j.IsHit)) + if (!HeadCircle.IsHit) HeadCircle.Position = slider.Curve.PositionAt(progress); foreach (var c in components.OfType()) c.UpdateProgress(progress, span); From 1dbaf9b7a75d86ad22c619a770a5ca9283bfdc63 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jan 2018 16:37:59 +0900 Subject: [PATCH 119/138] Add many more tests --- osu.Game.Tests/Chat/MessageFormatterTests.cs | 60 +++++++++++++++++++- osu.Game.Tests/Visual/TestCaseChatLink.cs | 5 +- 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index 0fc3054f44..2a7967bd6f 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -108,9 +108,9 @@ namespace osu.Game.Tests.Chat [Test] public void TestOldFormatLink() { - Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a (simple test)[https://osu.ppy.sh]." }); + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a (simple test)[https://osu.ppy.sh] of links." }); - Assert.AreEqual("This is a simple test.", result.DisplayContent); + Assert.AreEqual("This is a simple test of links.", result.DisplayContent); Assert.AreEqual(1, result.Links.Count); Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); Assert.AreEqual(10, result.Links[0].Index); @@ -129,6 +129,62 @@ namespace osu.Game.Tests.Chat Assert.AreEqual(11, result.Links[0].Length); } + [Test] + public void TestMarkdownFormatLink() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a [simple test](https://osu.ppy.sh)." }); + + Assert.AreEqual("This is a simple test.", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("https://osu.ppy.sh", result.Links[0].Url); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(11, result.Links[0].Length); + } + + [Test] + public void TestChannelLink() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is an #english and #japanese." }); + + Assert.AreEqual("This is an #english and #japanese.", result.DisplayContent); + Assert.AreEqual(2, result.Links.Count); + Assert.AreEqual("osu://chan/#english", result.Links[0].Url); + Assert.AreEqual("osu://chan/#japanese", result.Links[1].Url); + } + + [Test] + public void TestOsuProtocol() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a custom protocol osu://chan/#english." }); + + Assert.AreEqual("This is a custom protocol osu://chan/#english.", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("osu://chan/#english", result.Links[0].Url); + Assert.AreEqual(26, result.Links[0].Index); + Assert.AreEqual(19, result.Links[0].Length); + + result = MessageFormatter.FormatMessage(new Message { Content = "This is a [custom protocol](osu://chan/#english)." }); + + Assert.AreEqual("This is a custom protocol.", result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("osu://chan/#english", result.Links[0].Url); + Assert.AreEqual("#english", result.Links[0].Argument); + Assert.AreEqual(10, result.Links[0].Index); + Assert.AreEqual(15, result.Links[0].Length); + } + + [Test] + public void TestOsuMpProtocol() + { + Message result = MessageFormatter.FormatMessage(new Message { Content = "Join my multiplayer game osump://12346." }); + + Assert.AreEqual(result.Content, result.DisplayContent); + Assert.AreEqual(1, result.Links.Count); + Assert.AreEqual("osump://12346", result.Links[0].Url); + Assert.AreEqual(25, result.Links[0].Index); + Assert.AreEqual(13, result.Links[0].Length); + } + [Test] public void TestRecursiveBreaking() { diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index eeb4889f94..ee72fcb1ed 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -100,6 +100,7 @@ namespace osu.Game.Tests.Visual addMessageWithChecks("Wiki link for tasty [[Performance Points]]", 1, expectedActions: LinkAction.External); addMessageWithChecks("(osu forums)[https://osu.ppy.sh/forum] (old link format)", 1, expectedActions: LinkAction.External); addMessageWithChecks("[https://osu.ppy.sh/home New site] (new link format)", 1, expectedActions: LinkAction.External); + addMessageWithChecks("[osu forums](https://osu.ppy.sh/forum) (new link format 2)", 1, expectedActions: LinkAction.External); addMessageWithChecks("[https://osu.ppy.sh/home This is only a link to the new osu webpage but this is supposed to test word wrap.]", 1, expectedActions: LinkAction.External); addMessageWithChecks("is now listening to [https://osu.ppy.sh/s/93523 IMAGE -MATERIAL- ]", 1, true, expectedActions: LinkAction.OpenBeatmapSet); addMessageWithChecks("is now playing [https://osu.ppy.sh/b/252238 IMAGE -MATERIAL- ]", 1, true, expectedActions: LinkAction.OpenBeatmap); @@ -113,8 +114,8 @@ namespace osu.Game.Tests.Visual addMessageWithChecks("Join my multiplayer game osump://12346.",1, expectedActions: LinkAction.JoinMultiplayerMatch); addMessageWithChecks("Join my [multiplayer game](osump://12346).", 1, expectedActions: LinkAction.JoinMultiplayerMatch); addMessageWithChecks("Join my [#english](osu://chan/english).", 1, expectedActions: LinkAction.OpenChannel); - addMessageWithChecks("Join my osu://chan/english.", 1, expectedActions: LinkAction.OpenChannel); - addMessageWithChecks("Join my #english.", 1, expectedActions: LinkAction.OpenChannel); + addMessageWithChecks("Join my osu://chan/#english.", 1, expectedActions: LinkAction.OpenChannel); + addMessageWithChecks("Join my #english or #japanese channels.", 2, expectedActions: new [] { LinkAction.OpenChannel, LinkAction.OpenChannel }); } private void testEcho() From dd2731b8732df6a13f2f4ccd3570025a4c9fae50 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jan 2018 16:38:45 +0900 Subject: [PATCH 120/138] Add support for markdown style links --- osu.Game/Online/Chat/MessageFormatter.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 2c15f48f95..4ba5412633 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -18,6 +18,9 @@ namespace osu.Game.Online.Chat // [https://osu.ppy.sh/b/1234 Beatmap [Hard] (poop)] -> Beatmap [hard] (poop) (https://osu.ppy.sh/b/1234) private static readonly Regex new_link_regex = new Regex(@"\[([a-z]+://[^ ]+) ([^\[\]]*(((?\[)[^\[\]]*)+((?\])[^\[\]]*)+)*(?(open)(?!)))\]"); + // [test](https://osu.ppy.sh/b/1234) -> test (https://osu.ppy.sh/b/1234) aka correct markdown format + private static readonly Regex markdown_link_regex = new Regex(@"\[([^\]]*)\]\(([a-z]+://[^ ]+)\)"); + // advanced, RFC-compatible regular expression that matches any possible URL, *but* allows certain invalid characters that are widely used // This is in the format (, [optional]): // http[s]://.[:port][/path][?query][#fragment] @@ -168,6 +171,9 @@ namespace osu.Game.Online.Chat // handle the [link display] format handleMatches(new_link_regex, "{2}", "{1}", result, startIndex); + // handle the standard markdown []() format + handleMatches(markdown_link_regex, "{1}", "{2}", result, startIndex); + // handle the ()[] link format handleMatches(old_link_regex, "{1}", "{2}", result, startIndex); From 662c7c5bdcb8229c2667ed6a9fb59cec53bd3d3f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jan 2018 16:39:08 +0900 Subject: [PATCH 121/138] Fix osump links --- osu.Game/Online/Chat/MessageFormatter.cs | 21 ++++++++++++++------- 1 file changed, 14 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 4ba5412633..855770b953 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -24,13 +24,20 @@ namespace osu.Game.Online.Chat // advanced, RFC-compatible regular expression that matches any possible URL, *but* allows certain invalid characters that are widely used // This is in the format (, [optional]): // http[s]://.[:port][/path][?query][#fragment] - private static readonly Regex advanced_link_regex = new Regex(@"(?\([^)]*)?" + - @"(?https?:\/\/" + - @"(?(?:[a-z0-9]\.|[a-z0-9][a-z0-9-]*[a-z0-9]\.)*[a-z][a-z0-9-]*[a-z0-9]" + // domain, TLD - @"(?::\d+)?)" + // port - @"(?(?:(?:\/+(?:[a-z0-9$_\.\+!\*\',;:\(\)@&~=-]|%[0-9a-f]{2})*)*" + // path - @"(?:\?(?:[a-z0-9$_\+!\*\',;:\(\)@&=\/~-]|%[0-9a-f]{2})*)?)?" + // query - @"(?:#(?:[a-z0-9$_\+!\*\',;:\(\)@&=\/~-]|%[0-9a-f]{2})*)?)?)", // fragment + private static readonly Regex advanced_link_regex = new Regex( + @"(?\([^)]*)?" + + // protocol + @"(?[a-z]*?:\/\/" + + // domain + tld + @"(?(?:[a-z0-9]\.|[a-z0-9][a-z0-9-]*[a-z0-9]\.)*[a-z0-9-]*[a-z0-9]" + + // port (optional) + @"(?::\d+)?)" + + // path (optional) + @"(?(?:(?:\/+(?:[a-z0-9$_\.\+!\*\',;:\(\)@&~=-]|%[0-9a-f]{2})*)*" + + // query (optional) + @"(?:\?(?:[a-z0-9$_\+!\*\',;:\(\)@&=\/~-]|%[0-9a-f]{2})*)?)?" + + // fragment (optional) + @"(?:#(?:[a-z0-9$_\+!\*\',;:\(\)@&=\/~-]|%[0-9a-f]{2})*)?)?)", RegexOptions.IgnoreCase); // 00:00:000 (1,2,3) - test From 4b63d25871bb3f99cf22ffdb94e8560f1efe2822 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 30 Jan 2018 16:45:48 +0900 Subject: [PATCH 122/138] Add hit/miss display to TestCaseSlider --- osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs index 5060137ec6..b98124b9c9 100644 --- a/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs +++ b/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs @@ -16,6 +16,9 @@ using OpenTK; using OpenTK.Graphics; using osu.Game.Rulesets.Mods; using System.Linq; +using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets.Judgements; +using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces; namespace osu.Game.Rulesets.Osu.Tests @@ -142,7 +145,31 @@ namespace osu.Game.Rulesets.Osu.Tests foreach (var mod in Mods.OfType()) mod.ApplyToDrawableHitObjects(new[] { drawable }); + drawable.OnJudgement += onJudgement; + Add(drawable); } + + private void onJudgement(DrawableHitObject judgedObject, Judgement judgement) + { + var osuObject = judgedObject as DrawableOsuHitObject; + if (osuObject == null) + return; + + OsuSpriteText text; + Add(text = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = judgement.IsHit ? "Hit!" : "Miss!", + Colour = judgement.IsHit ? Color4.Green : Color4.Red, + TextSize = 30, + Position = osuObject.HitObject.StackedEndPosition - new Vector2(0, 45) + }); + + text.Delay(150) + .Then().FadeOut(200) + .Then().Expire(); + } } } From cd4a0612c06c15b9803dd7a88205c7a672f3b37e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 30 Jan 2018 16:53:19 +0900 Subject: [PATCH 123/138] Properly construct slider ends using HitObject.AddNested --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 0c86c64be7..d4444c5c5d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -94,7 +94,19 @@ namespace osu.Game.Rulesets.Osu.Objects Velocity = scoringDistance / timingPoint.BeatLength; TickDistance = scoringDistance / difficulty.SliderTickRate; + } + protected override void CreateNestedHitObjects() + { + base.CreateNestedHitObjects(); + + createSliderEnds(); + createTicks(); + createRepeatPoints(); + } + + private void createSliderEnds() + { HeadCircle = new HitCircle { StartTime = StartTime, @@ -115,16 +127,8 @@ namespace osu.Game.Rulesets.Osu.Objects SampleControlPoint = SampleControlPoint }; - HeadCircle.ApplyDefaults(controlPointInfo, difficulty); - TailCircle.ApplyDefaults(controlPointInfo, difficulty); - } - - protected override void CreateNestedHitObjects() - { - base.CreateNestedHitObjects(); - - createTicks(); - createRepeatPoints(); + AddNested(HeadCircle); + AddNested(TailCircle); } private void createTicks() From d81d884a01e7355e21eef3e50f5bc9e02b0f53c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jan 2018 17:16:01 +0900 Subject: [PATCH 124/138] Remove unnecessary paren handling from regex Can't find a reason for this to exist --- osu.Game/Online/Chat/MessageFormatter.cs | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 855770b953..5a1e866650 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -25,7 +25,6 @@ namespace osu.Game.Online.Chat // This is in the format (, [optional]): // http[s]://.[:port][/path][?query][#fragment] private static readonly Regex advanced_link_regex = new Regex( - @"(?\([^)]*)?" + // protocol @"(?[a-z]*?:\/\/" + // domain + tld @@ -90,20 +89,9 @@ namespace osu.Game.Online.Chat foreach (Match m in regex.Matches(result.Text, startIndex)) { var index = m.Index; - var prefix = m.Groups["paren"].Value; var link = m.Groups["link"].Value; var indexLength = link.Length; - if (!String.IsNullOrEmpty(prefix)) - { - index += prefix.Length; - if (link.EndsWith(")")) - { - indexLength = indexLength - 1; - link = link.Remove(link.Length - 1); - } - } - var details = getLinkDetails(link); result.Links.Add(new Link(link, index, indexLength, details.Action, details.Argument)); } From e5188fd1517379ef0bdc7ddb276ab12ea4621fc2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jan 2018 17:43:19 +0900 Subject: [PATCH 125/138] Add better channel test cases (testing non-existent channels) --- osu.Game.Tests/Visual/TestCaseChatLink.cs | 32 ++++++++++++++++------- osu.Game/Online/Chat/MessageFormatter.cs | 2 +- osu.Game/Overlays/Chat/ChatLine.cs | 3 ++- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseChatLink.cs b/osu.Game.Tests/Visual/TestCaseChatLink.cs index ee72fcb1ed..3a7be686e1 100644 --- a/osu.Game.Tests/Visual/TestCaseChatLink.cs +++ b/osu.Game.Tests/Visual/TestCaseChatLink.cs @@ -14,6 +14,7 @@ using System.Collections.Generic; using System.Linq; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Overlays; namespace osu.Game.Tests.Visual { @@ -32,6 +33,9 @@ namespace osu.Game.Tests.Visual typeof(MessageFormatter) }; + private DependencyContainer dependencies; + protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) => dependencies = new DependencyContainer(parent); + public TestCaseChatLink() { Add(textContainer = new TestChatLineContainer @@ -41,6 +45,20 @@ namespace osu.Game.Tests.Visual AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, }); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + linkColour = colours.Blue; + dependencies.Cache(new ChatOverlay + { + AvailableChannels = + { + new Channel { Name = "#english" }, + new Channel { Name = "#japanese" } + } + }); testLinksGeneral(); testEcho(); @@ -111,11 +129,12 @@ namespace osu.Game.Tests.Visual addMessageWithChecks("I am important!", 0, false, true); addMessageWithChecks("feels important", 0, true, true); addMessageWithChecks("likes to post this [https://osu.ppy.sh/home link].", 1, true, true, expectedActions: LinkAction.External); - addMessageWithChecks("Join my multiplayer game osump://12346.",1, expectedActions: LinkAction.JoinMultiplayerMatch); + addMessageWithChecks("Join my multiplayer game osump://12346.", 1, expectedActions: LinkAction.JoinMultiplayerMatch); addMessageWithChecks("Join my [multiplayer game](osump://12346).", 1, expectedActions: LinkAction.JoinMultiplayerMatch); - addMessageWithChecks("Join my [#english](osu://chan/english).", 1, expectedActions: LinkAction.OpenChannel); + addMessageWithChecks("Join my [#english](osu://chan/#english).", 1, expectedActions: LinkAction.OpenChannel); addMessageWithChecks("Join my osu://chan/#english.", 1, expectedActions: LinkAction.OpenChannel); - addMessageWithChecks("Join my #english or #japanese channels.", 2, expectedActions: new [] { LinkAction.OpenChannel, LinkAction.OpenChannel }); + addMessageWithChecks("Join my #english or #japanese channels.", 2, expectedActions: new[] { LinkAction.OpenChannel, LinkAction.OpenChannel }); + addMessageWithChecks("Join my #english or #nonexistent #hashtag channels.", 1, expectedActions: LinkAction.OpenChannel); } private void testEcho() @@ -141,12 +160,6 @@ namespace osu.Game.Tests.Visual } } - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - linkColour = colours.Blue; - } - private class DummyEchoMessage : LocalEchoMessage { public DummyEchoMessage(string text) @@ -160,6 +173,7 @@ namespace osu.Game.Tests.Visual private class DummyMessage : Message { private static long messageCounter; + internal static readonly User TEST_SENDER_BACKGROUND = new User { Username = @"i-am-important", diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 5a1e866650..906f42d50e 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -43,7 +43,7 @@ namespace osu.Game.Online.Chat private static readonly Regex time_regex = new Regex(@"\d\d:\d\d:\d\d\d? [^-]*"); // #osu - private static readonly Regex channel_regex = new Regex(@"#[a-zA-Z]+[a-zA-Z0-9]+"); + private static readonly Regex channel_regex = new Regex(@"(#[a-zA-Z]+[a-zA-Z0-9]+)"); // Unicode emojis private static readonly Regex emoji_regex = new Regex(@"(\uD83D[\uDC00-\uDE4F])"); diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index d87f60a350..dd41dd5428 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2018 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System.Linq; using OpenTK; using OpenTK.Graphics; using osu.Framework.Allocation; @@ -225,7 +226,7 @@ namespace osu.Game.Overlays.Chat username.Text = $@"{message.Sender.Username}" + (senderHasBackground || message.IsAction ? "" : ":"); // remove non-existent channels from the link list - message.Links.RemoveAll(link => link.Action == LinkAction.OpenChannel && chat?.AvailableChannels.TrueForAll(c => c.Name != link.Argument) != false); + message.Links.RemoveAll(link => link.Action == LinkAction.OpenChannel && chat?.AvailableChannels.Any(c => c.Name == link.Argument) != true); contentFlow.Clear(); contentFlow.AddLinks(message.DisplayContent, message.Links); From 53129e52359147b28544b36aeda6e39e91243b63 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jan 2018 17:47:22 +0900 Subject: [PATCH 126/138] Fix text getting truncated after last link in chat line --- osu.Game/Graphics/Containers/LinkFlowContainer.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index f35282fc93..9f1b44af44 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -54,10 +54,11 @@ namespace osu.Game.Graphics.Containers foreach (var link in links) { AddText(text.Substring(previousLinkEnd, link.Index - previousLinkEnd)); - AddLink(text.Substring(link.Index, link.Length), link.Url, link.Action, link.Argument); previousLinkEnd = link.Index + link.Length; } + + AddText(text.Substring(previousLinkEnd)); } public void AddLink(string text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null) From 00001364c32cf0a20174d3fb211765847c958296 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 30 Jan 2018 18:10:14 +0900 Subject: [PATCH 127/138] Better judgement visualisations in testcase --- osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs b/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs index b98124b9c9..2d26b74d01 100644 --- a/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs +++ b/osu.Game.Rulesets.Osu/Tests/TestCaseSlider.cs @@ -150,6 +150,7 @@ namespace osu.Game.Rulesets.Osu.Tests Add(drawable); } + private float judgementOffsetDirection = 1; private void onJudgement(DrawableHitObject judgedObject, Judgement judgement) { var osuObject = judgedObject as DrawableOsuHitObject; @@ -164,12 +165,14 @@ namespace osu.Game.Rulesets.Osu.Tests Text = judgement.IsHit ? "Hit!" : "Miss!", Colour = judgement.IsHit ? Color4.Green : Color4.Red, TextSize = 30, - Position = osuObject.HitObject.StackedEndPosition - new Vector2(0, 45) + Position = osuObject.HitObject.StackedEndPosition + judgementOffsetDirection * new Vector2(0, 45) }); text.Delay(150) .Then().FadeOut(200) .Then().Expire(); + + judgementOffsetDirection *= -1; } } } From 58cdb59a27835acfae71885e47384585589e2627 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 30 Jan 2018 18:22:40 +0900 Subject: [PATCH 128/138] Fix failing testcase --- .../Visual/TestCaseEditorSelectionLayer.cs | 20 ++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs index f236182939..755800c4e1 100644 --- a/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs +++ b/osu.Game.Tests/Visual/TestCaseEditorSelectionLayer.cs @@ -8,6 +8,8 @@ using OpenTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Timing; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Edit.Layers.Selection; using osu.Game.Rulesets.Osu.Edit; using osu.Game.Rulesets.Osu.Objects; @@ -35,9 +37,9 @@ namespace osu.Game.Tests.Visual new SelectionLayer(playfield) }; - playfield.Add(new DrawableHitCircle(new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f })); - playfield.Add(new DrawableHitCircle(new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f })); - playfield.Add(new DrawableSlider(new Slider + var hitCircle1 = new HitCircle { Position = new Vector2(256, 192), Scale = 0.5f }; + var hitCircle2 = new HitCircle { Position = new Vector2(344, 148), Scale = 0.5f }; + var slider = new Slider { ControlPoints = new List { @@ -48,8 +50,16 @@ namespace osu.Game.Tests.Visual Position = new Vector2(128, 256), Velocity = 1, TickDistance = 100, - Scale = 0.5f - })); + Scale = 0.5f, + }; + + hitCircle1.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + hitCircle2.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + playfield.Add(new DrawableHitCircle(hitCircle1)); + playfield.Add(new DrawableHitCircle(hitCircle2)); + playfield.Add(new DrawableSlider(slider)); } } } From 9b4546bdc42107bcc713b1ea25a5bcd3373516b6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jan 2018 18:23:25 +0900 Subject: [PATCH 129/138] Update taiko hitsounds (courtesey of cYsmix) --- osu-resources | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu-resources b/osu-resources index 266965f0d7..92ec3d10b1 160000 --- a/osu-resources +++ b/osu-resources @@ -1 +1 @@ -Subproject commit 266965f0d795b94a126e2da302bd2c10eadd642a +Subproject commit 92ec3d10b12c5e9bfc1d3b05d3db174a506efd6d From 2ffe16d3d2224efe751f9205e3915ca17941ffa8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 30 Jan 2018 18:51:13 +0900 Subject: [PATCH 130/138] Improve clarity and coverage of unit tests --- osu.Game.Tests/Chat/MessageFormatterTests.cs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Chat/MessageFormatterTests.cs b/osu.Game.Tests/Chat/MessageFormatterTests.cs index 2a7967bd6f..f102e4c59f 100644 --- a/osu.Game.Tests/Chat/MessageFormatterTests.cs +++ b/osu.Game.Tests/Chat/MessageFormatterTests.cs @@ -14,7 +14,7 @@ namespace osu.Game.Tests.Chat { Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a http://www.basic-link.com/?test=test." }); - Assert.AreEqual("This is a http://www.basic-link.com/?test=test.", result.DisplayContent); + Assert.AreEqual(result.Content, result.DisplayContent); Assert.AreEqual(1, result.Links.Count); Assert.AreEqual("http://www.basic-link.com/?test=test", result.Links[0].Url); Assert.AreEqual(10, result.Links[0].Index); @@ -26,7 +26,7 @@ namespace osu.Game.Tests.Chat { Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a http://test.io/link#fragment. (see https://twitter.com). Also, This string should not be altered. http://example.com/" }); - Assert.AreEqual("This is a http://test.io/link#fragment. (see https://twitter.com). Also, This string should not be altered. http://example.com/", result.DisplayContent); + Assert.AreEqual(result.Content, result.DisplayContent); Assert.AreEqual(3, result.Links.Count); Assert.AreEqual("http://test.io/link#fragment", result.Links[0].Url); @@ -47,7 +47,8 @@ namespace osu.Game.Tests.Chat { Message result = MessageFormatter.FormatMessage(new Message { Content = "https://twitter.com/#!/hashbanglinks" }); - Assert.AreEqual("https://twitter.com/#!/hashbanglinks", result.DisplayContent); + Assert.AreEqual(result.Content, result.DisplayContent); + Assert.AreEqual(result.Content, result.Links[0].Url); Assert.AreEqual(0, result.Links[0].Index); Assert.AreEqual(36, result.Links[0].Length); } @@ -57,7 +58,8 @@ namespace osu.Game.Tests.Chat { Message result = MessageFormatter.FormatMessage(new Message { Content = "http://www.chiark.greenend.org.uk/~sgtatham/putty/" }); - Assert.AreEqual("http://www.chiark.greenend.org.uk/~sgtatham/putty/", result.DisplayContent); + Assert.AreEqual(result.Content, result.DisplayContent); + Assert.AreEqual(result.Content, result.Links[0].Url); Assert.AreEqual(0, result.Links[0].Index); Assert.AreEqual(50, result.Links[0].Length); } @@ -67,7 +69,7 @@ namespace osu.Game.Tests.Chat { Message result = MessageFormatter.FormatMessage(new Message { Content = "look: http://puu.sh/7Ggh8xcC6/asf0asd9876.NEF" }); - Assert.AreEqual("look: http://puu.sh/7Ggh8xcC6/asf0asd9876.NEF", result.DisplayContent); + Assert.AreEqual(result.Content, result.DisplayContent); Assert.AreEqual(6, result.Links[0].Index); Assert.AreEqual(39, result.Links[0].Length); } @@ -146,7 +148,7 @@ namespace osu.Game.Tests.Chat { Message result = MessageFormatter.FormatMessage(new Message { Content = "This is an #english and #japanese." }); - Assert.AreEqual("This is an #english and #japanese.", result.DisplayContent); + Assert.AreEqual(result.Content, result.DisplayContent); Assert.AreEqual(2, result.Links.Count); Assert.AreEqual("osu://chan/#english", result.Links[0].Url); Assert.AreEqual("osu://chan/#japanese", result.Links[1].Url); @@ -157,7 +159,7 @@ namespace osu.Game.Tests.Chat { Message result = MessageFormatter.FormatMessage(new Message { Content = "This is a custom protocol osu://chan/#english." }); - Assert.AreEqual("This is a custom protocol osu://chan/#english.", result.DisplayContent); + Assert.AreEqual(result.Content, result.DisplayContent); Assert.AreEqual(1, result.Links.Count); Assert.AreEqual("osu://chan/#english", result.Links[0].Url); Assert.AreEqual(26, result.Links[0].Index); From 042a34e1c23b263bd3d50f24e29e156ba8afb29b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Jan 2018 16:10:48 +0900 Subject: [PATCH 131/138] Add migration to ensure database aligns to changed enum --- .../20180131154205_AddMuteBinding.cs | 25 +++++++++++++++++++ osu.Game/osu.Game.csproj | 1 + 2 files changed, 26 insertions(+) create mode 100644 osu.Game/Migrations/20180131154205_AddMuteBinding.cs diff --git a/osu.Game/Migrations/20180131154205_AddMuteBinding.cs b/osu.Game/Migrations/20180131154205_AddMuteBinding.cs new file mode 100644 index 0000000000..6948ad03da --- /dev/null +++ b/osu.Game/Migrations/20180131154205_AddMuteBinding.cs @@ -0,0 +1,25 @@ +using Microsoft.EntityFrameworkCore.Migrations; +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Infrastructure; +using osu.Game.Database; +using osu.Game.Input.Bindings; + +namespace osu.Game.Migrations +{ + [DbContext(typeof(OsuDbContext))] + [Migration("20180131154205_AddMuteBinding")] + public partial class AddMuteBinding : Migration + { + protected override void Up(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql($"UPDATE KeyBinding SET Action = Action + 1 WHERE RulesetID IS NULL AND Variant IS NULL AND Action >= {(int)GlobalAction.ToggleMute}"); + } + + protected override void Down(MigrationBuilder migrationBuilder) + { + migrationBuilder.Sql("DELETE FROM KeyBinding WHERE RulesetID IS NULL AND Variant IS NULL AND Action = 8"); + migrationBuilder.Sql($"UPDATE KeyBinding SET Action = Action - 1 WHERE RulesetID IS NULL AND Variant IS NULL AND Action > {(int)GlobalAction.ToggleMute}"); + } + } +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 4e048d60b9..05cf61c23c 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -280,6 +280,7 @@ 20180125143340_Settings.cs + From 5a99651561c97d2b607b69a7fd9485b17ab66817 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Jan 2018 16:10:55 +0900 Subject: [PATCH 132/138] Remove unnecessary arrays --- osu.Game/Input/Bindings/GlobalActionContainer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Input/Bindings/GlobalActionContainer.cs b/osu.Game/Input/Bindings/GlobalActionContainer.cs index 918a4e374a..17ec2af4b9 100644 --- a/osu.Game/Input/Bindings/GlobalActionContainer.cs +++ b/osu.Game/Input/Bindings/GlobalActionContainer.cs @@ -29,10 +29,10 @@ namespace osu.Game.Input.Bindings new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings), new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar), new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings), - new KeyBinding(new[] { InputKey.Up }, GlobalAction.IncreaseVolume), - new KeyBinding(new[] { InputKey.MouseWheelUp }, GlobalAction.IncreaseVolume), - new KeyBinding(new[] { InputKey.Down }, GlobalAction.DecreaseVolume), - new KeyBinding(new[] { InputKey.MouseWheelDown }, GlobalAction.DecreaseVolume), + new KeyBinding(InputKey.Up, GlobalAction.IncreaseVolume), + new KeyBinding(InputKey.MouseWheelUp, GlobalAction.IncreaseVolume), + new KeyBinding(InputKey.Down, GlobalAction.DecreaseVolume), + new KeyBinding(InputKey.MouseWheelDown, GlobalAction.DecreaseVolume), new KeyBinding(InputKey.F4, GlobalAction.ToggleMute), }; From 2865dd3a10404408145ffe0a01caeef9734247a5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Jan 2018 16:20:01 +0900 Subject: [PATCH 133/138] Replace missed hardcoded int with enum reference --- osu.Game/Migrations/20180131154205_AddMuteBinding.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Migrations/20180131154205_AddMuteBinding.cs b/osu.Game/Migrations/20180131154205_AddMuteBinding.cs index 6948ad03da..fc1f2fff55 100644 --- a/osu.Game/Migrations/20180131154205_AddMuteBinding.cs +++ b/osu.Game/Migrations/20180131154205_AddMuteBinding.cs @@ -18,7 +18,7 @@ namespace osu.Game.Migrations protected override void Down(MigrationBuilder migrationBuilder) { - migrationBuilder.Sql("DELETE FROM KeyBinding WHERE RulesetID IS NULL AND Variant IS NULL AND Action = 8"); + migrationBuilder.Sql($"DELETE FROM KeyBinding WHERE RulesetID IS NULL AND Variant IS NULL AND Action = {(int)GlobalAction.ToggleMute}"); migrationBuilder.Sql($"UPDATE KeyBinding SET Action = Action - 1 WHERE RulesetID IS NULL AND Variant IS NULL AND Action > {(int)GlobalAction.ToggleMute}"); } } From 97ae44f23c5b694249ad33acd27c3200a9871346 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Jan 2018 16:56:26 +0900 Subject: [PATCH 134/138] Remove outwards exposure of mute property --- osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs index 60242393ab..cfc8b81420 100644 --- a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs +++ b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs @@ -86,7 +86,7 @@ namespace osu.Game.Graphics.UserInterface.Volume return true; case GlobalAction.ToggleMute: Show(); - Muted = !Muted; + muted.Toggle(); return true; } @@ -103,12 +103,6 @@ namespace osu.Game.Graphics.UserInterface.Volume private readonly BindableBool muted = new BindableBool(); - public bool Muted - { - get => muted.Value; - set => muted.Value = value; - } - [BackgroundDependencyLoader] private void load(AudioManager audio) { From 86f5c9d6f140ce3ee117fdf56797766b4cce315d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Jan 2018 16:57:26 +0900 Subject: [PATCH 135/138] Add inactive volume ducking, rather than outright mute --- osu.Game/Configuration/OsuConfigManager.cs | 4 ++-- osu.Game/OsuGame.cs | 16 ++++++---------- .../Settings/Sections/Audio/VolumeSettings.cs | 2 +- 3 files changed, 9 insertions(+), 13 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 230aabb2cd..c33dd91330 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -39,7 +39,7 @@ namespace osu.Game.Configuration }; // Audio - Set(OsuSetting.MuteWhenInactive, false); + Set(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01); Set(OsuSetting.MenuVoice, true); Set(OsuSetting.MenuMusic, true); @@ -103,7 +103,7 @@ namespace osu.Game.Configuration MouseDisableButtons, MouseDisableWheel, AudioOffset, - MuteWhenInactive, + VolumeInactive, MenuMusic, MenuVoice, CursorRotation, diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index f1914b53b5..fbbc14dd5e 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -19,6 +19,7 @@ using OpenTK; using System.Linq; using System.Threading; using System.Threading.Tasks; +using osu.Framework.Audio; using osu.Framework.Input.Bindings; using osu.Framework.Platform; using osu.Framework.Threading; @@ -122,7 +123,7 @@ namespace osu.Game Ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value) ?? RulesetStore.AvailableRulesets.First(); Ruleset.ValueChanged += r => configRuleset.Value = r.ID ?? 0; - muteWhenInactive = LocalConfig.GetBindable(OsuSetting.MuteWhenInactive); + LocalConfig.BindWith(OsuSetting.VolumeInactive, inactiveDuckVolume); } private ScheduledDelegate scoreLoad; @@ -400,24 +401,19 @@ namespace osu.Game return false; } - private Bindable muteWhenInactive = new Bindable(); - private bool wasMuted; + private readonly BindableDouble inactiveDuckVolume = new BindableDouble(); protected override void OnDeactivated() { base.OnDeactivated(); - if (muteWhenInactive) - { - wasMuted = volume.Muted; - volume.Muted = true; - } + Audio.AddAdjustment(AdjustableProperty.Volume, inactiveDuckVolume); } protected override void OnActivated() { base.OnActivated(); - if (IsLoaded && muteWhenInactive && !wasMuted) - volume.Muted = false; + Audio.RemoveAdjustment(AdjustableProperty.Volume, inactiveDuckVolume); + } public bool OnReleased(GlobalAction action) => false; diff --git a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs index 01bcf989dc..92ee01dd7a 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs @@ -18,9 +18,9 @@ namespace osu.Game.Overlays.Settings.Sections.Audio Children = new Drawable[] { new SettingsSlider { LabelText = "Master", Bindable = audio.Volume, KeyboardStep = 0.1f }, + new SettingsSlider { LabelText = "Master (Window Inactive)", Bindable = config.GetBindable(OsuSetting.VolumeInactive), KeyboardStep = 0.1f }, new SettingsSlider { LabelText = "Effect", Bindable = audio.VolumeSample, KeyboardStep = 0.1f }, new SettingsSlider { LabelText = "Music", Bindable = audio.VolumeTrack, KeyboardStep = 0.1f }, - new SettingsCheckbox { LabelText = "Mute osu! when inactive", Bindable = config.GetBindable(OsuSetting.MuteWhenInactive) } }; } } From 93ffa1f8a2a1920e321522302dac97849db73926 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Jan 2018 17:36:53 +0900 Subject: [PATCH 136/138] Fix button alignment and animation --- osu.Game/Graphics/UserInterface/IconButton.cs | 4 ++-- .../Graphics/UserInterface/Volume/VolumeControl.cs | 13 +++++++++---- 2 files changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/IconButton.cs b/osu.Game/Graphics/UserInterface/IconButton.cs index bcf6ab92b6..5b266d9a59 100644 --- a/osu.Game/Graphics/UserInterface/IconButton.cs +++ b/osu.Game/Graphics/UserInterface/IconButton.cs @@ -15,7 +15,7 @@ namespace osu.Game.Graphics.UserInterface { public class IconButton : OsuClickableContainer { - private const float button_size = 30; + public const float BUTTON_SIZE = 30; private Color4? flashColour; /// @@ -106,7 +106,7 @@ namespace osu.Game.Graphics.UserInterface { Origin = Anchor.Centre, Anchor = Anchor.Centre, - Size = new Vector2(button_size), + Size = new Vector2(BUTTON_SIZE), CornerRadius = 5, Masking = true, EdgeEffect = new EdgeEffectParameters diff --git a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs index cfc8b81420..40c46d4ff8 100644 --- a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs +++ b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs @@ -36,11 +36,16 @@ namespace osu.Game.Graphics.UserInterface.Volume Spacing = new Vector2(15, 0), Children = new Drawable[] { - muteIcon = new IconButton + new Container { - Icon = FontAwesome.fa_volume_up, - Scale = new Vector2(2.0f), - Action = () => Adjust(GlobalAction.ToggleMute), + Size = new Vector2(IconButton.BUTTON_SIZE), + Child = muteIcon = new IconButton + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Icon = FontAwesome.fa_volume_up, + Action = () => Adjust(GlobalAction.ToggleMute), + } }, volumeMeterMaster = new VolumeMeter("Master"), volumeMeterEffect = new VolumeMeter("Effects"), From 47b92f3d1d6433b766d872669ec6691a63f73455 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Jan 2018 18:00:44 +0900 Subject: [PATCH 137/138] Fix mute button not prolonging volume control display --- .../UserInterface/Volume/VolumeControl.cs | 24 +++++++------------ 1 file changed, 8 insertions(+), 16 deletions(-) diff --git a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs index 40c46d4ff8..ccf70af6ed 100644 --- a/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs +++ b/osu.Game/Graphics/UserInterface/Volume/VolumeControl.cs @@ -59,18 +59,10 @@ namespace osu.Game.Graphics.UserInterface.Volume { base.LoadComplete(); - volumeMeterMaster.Bindable.ValueChanged += volumeChanged; - volumeMeterEffect.Bindable.ValueChanged += volumeChanged; - volumeMeterMusic.Bindable.ValueChanged += volumeChanged; - } - - protected override void Dispose(bool isDisposing) - { - base.Dispose(isDisposing); - - volumeMeterMaster.Bindable.ValueChanged -= volumeChanged; - volumeMeterEffect.Bindable.ValueChanged -= volumeChanged; - volumeMeterMusic.Bindable.ValueChanged -= volumeChanged; + volumeMeterMaster.Bindable.ValueChanged += _ => settingChanged(); + volumeMeterEffect.Bindable.ValueChanged += _ => settingChanged(); + volumeMeterMusic.Bindable.ValueChanged += _ => settingChanged(); + muted.ValueChanged += _ => settingChanged(); } public bool Adjust(GlobalAction action) @@ -98,13 +90,13 @@ namespace osu.Game.Graphics.UserInterface.Volume return false; } - private void volumeChanged(double newVolume) + private void settingChanged() { Show(); schedulePopOut(); } - private readonly BindableDouble muteBindable = new BindableDouble(); + private readonly BindableDouble muteAdjustment = new BindableDouble(); private readonly BindableBool muted = new BindableBool(); @@ -119,12 +111,12 @@ namespace osu.Game.Graphics.UserInterface.Volume { if (mute) { - audio.AddAdjustment(AdjustableProperty.Volume, muteBindable); + audio.AddAdjustment(AdjustableProperty.Volume, muteAdjustment); muteIcon.Icon = FontAwesome.fa_volume_off; } else { - audio.RemoveAdjustment(AdjustableProperty.Volume, muteBindable); + audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment); muteIcon.Icon = FontAwesome.fa_volume_up; } }; From fef69cea04049e376a0e29973fb9189cc859aca0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 31 Jan 2018 18:11:38 +0900 Subject: [PATCH 138/138] Revert "Add volume ducking" This reverts commit 01325de3a2897a24b2ba0f585d7e976d0fb44b70. --- osu.Game/Configuration/OsuConfigManager.cs | 3 --- osu.Game/OsuGame.cs | 18 ------------------ .../Settings/Sections/Audio/VolumeSettings.cs | 4 +--- 3 files changed, 1 insertion(+), 24 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index c33dd91330..33810c9712 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -39,8 +39,6 @@ namespace osu.Game.Configuration }; // Audio - Set(OsuSetting.VolumeInactive, 0.25, 0, 1, 0.01); - Set(OsuSetting.MenuVoice, true); Set(OsuSetting.MenuMusic, true); @@ -103,7 +101,6 @@ namespace osu.Game.Configuration MouseDisableButtons, MouseDisableWheel, AudioOffset, - VolumeInactive, MenuMusic, MenuVoice, CursorRotation, diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index fbbc14dd5e..bd71d37f97 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -19,7 +19,6 @@ using OpenTK; using System.Linq; using System.Threading; using System.Threading.Tasks; -using osu.Framework.Audio; using osu.Framework.Input.Bindings; using osu.Framework.Platform; using osu.Framework.Threading; @@ -122,8 +121,6 @@ namespace osu.Game configRuleset = LocalConfig.GetBindable(OsuSetting.Ruleset); Ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value) ?? RulesetStore.AvailableRulesets.First(); Ruleset.ValueChanged += r => configRuleset.Value = r.ID ?? 0; - - LocalConfig.BindWith(OsuSetting.VolumeInactive, inactiveDuckVolume); } private ScheduledDelegate scoreLoad; @@ -401,21 +398,6 @@ namespace osu.Game return false; } - private readonly BindableDouble inactiveDuckVolume = new BindableDouble(); - - protected override void OnDeactivated() - { - base.OnDeactivated(); - Audio.AddAdjustment(AdjustableProperty.Volume, inactiveDuckVolume); - } - - protected override void OnActivated() - { - base.OnActivated(); - Audio.RemoveAdjustment(AdjustableProperty.Volume, inactiveDuckVolume); - - } - public bool OnReleased(GlobalAction action) => false; private Container mainContent; diff --git a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs index 92ee01dd7a..40b9ff069b 100644 --- a/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Audio/VolumeSettings.cs @@ -4,7 +4,6 @@ using osu.Framework.Allocation; using osu.Framework.Audio; using osu.Framework.Graphics; -using osu.Game.Configuration; namespace osu.Game.Overlays.Settings.Sections.Audio { @@ -13,12 +12,11 @@ namespace osu.Game.Overlays.Settings.Sections.Audio protected override string Header => "Volume"; [BackgroundDependencyLoader] - private void load(AudioManager audio, OsuConfigManager config) + private void load(AudioManager audio) { Children = new Drawable[] { new SettingsSlider { LabelText = "Master", Bindable = audio.Volume, KeyboardStep = 0.1f }, - new SettingsSlider { LabelText = "Master (Window Inactive)", Bindable = config.GetBindable(OsuSetting.VolumeInactive), KeyboardStep = 0.1f }, new SettingsSlider { LabelText = "Effect", Bindable = audio.VolumeSample, KeyboardStep = 0.1f }, new SettingsSlider { LabelText = "Music", Bindable = audio.VolumeTrack, KeyboardStep = 0.1f }, };