mirror of
https://github.com/ppy/osu.git
synced 2025-02-15 18:13:09 +08:00
Changed chat link implementation according to review
This commit is contained in:
parent
d66d741af2
commit
e7721d71f3
@ -6,6 +6,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
using osu.Framework.Graphics.Colour;
|
using osu.Framework.Graphics.Colour;
|
||||||
using osu.Framework.Graphics.Containers;
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Game.Graphics;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
using osu.Game.Online.Chat;
|
using osu.Game.Online.Chat;
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
@ -47,19 +48,32 @@ namespace osu.Game.Tests.Visual
|
|||||||
|
|
||||||
private void clear() => AddStep("clear messages", textContainer.Clear);
|
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);
|
textContainer.Add(newLine);
|
||||||
|
|
||||||
AddAssert($"msg #{textContainer.Count} has {linkAmount} link(s)", () => newLine.Message.Links.Count == linkAmount);
|
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} 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<ChatLink>().All(sprite => sprite.Font == @"Exo2.0-MediumItalic");
|
bool isItalic(ChatFlowContainer c) => c.Cast<ChatLink>().All(sprite => sprite.Font == @"Exo2.0-MediumItalic");
|
||||||
|
|
||||||
bool isShowingLinks(OsuTextFlowContainer c) => c.Cast<ChatLink>().All(sprite => sprite.HandleInput && !sprite.TextColour.Equals((SRGBColour)Color4.White)
|
bool isShowingLinks()
|
||||||
|| !sprite.HandleInput && sprite.TextColour.Equals((SRGBColour)Color4.White));
|
{
|
||||||
|
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<ChatLink>()
|
||||||
|
.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()
|
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);
|
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)
|
// 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("#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()
|
private void testAddingLinks()
|
||||||
@ -132,37 +149,27 @@ namespace osu.Game.Tests.Visual
|
|||||||
private class DummyMessage : Message
|
private class DummyMessage : Message
|
||||||
{
|
{
|
||||||
private static long messageCounter;
|
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
|
internal static readonly User TEST_SENDER = new User
|
||||||
{
|
{
|
||||||
Username = @"Somebody",
|
Username = @"Somebody",
|
||||||
Id = 1,
|
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 new DateTimeOffset Timestamp = DateTimeOffset.Now;
|
||||||
|
|
||||||
public DummyMessage(string text, bool isAction = false)
|
public DummyMessage(string text, bool isAction = false, bool isImportant = false)
|
||||||
: base(messageCounter++)
|
: base(messageCounter++)
|
||||||
{
|
{
|
||||||
Content = text;
|
Content = text;
|
||||||
IsAction = isAction;
|
IsAction = isAction;
|
||||||
Sender = TEST_SENDER;
|
Sender = isImportant ? TEST_SENDER_BACKGROUND : TEST_SENDER;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
61
osu.Game/Graphics/Containers/ChatFlowContainer.cs
Normal file
61
osu.Game/Graphics/Containers/ChatFlowContainer.cs
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
||||||
|
// 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<ChatLink> defaultCreationParameters;
|
||||||
|
private ColourInfo urlColour;
|
||||||
|
|
||||||
|
public ChatFlowContainer(Action<ChatLink> 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<ChatLink> 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,59 +0,0 @@
|
|||||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
|
||||||
// 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<OsuSpriteLink>
|
|
||||||
{
|
|
||||||
public OsuLinkFlowContainer(Action<SpriteText> defaultCreationParameters = null)
|
|
||||||
: base(defaultCreationParameters)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public class OsuLinkFlowContainer<T> : OsuTextFlowContainer
|
|
||||||
where T : OsuSpriteLink, new()
|
|
||||||
{
|
|
||||||
public override bool HandleInput => true;
|
|
||||||
|
|
||||||
public OsuLinkFlowContainer(Action<SpriteText> defaultCreationParameters = null)
|
|
||||||
: base(defaultCreationParameters)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override SpriteText CreateSpriteText() => new T();
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The colour for text (links override this). Will only be used for new text elements.
|
|
||||||
/// </summary>
|
|
||||||
public ColourInfo TextColour = Color4.White;
|
|
||||||
|
|
||||||
public IEnumerable<SpriteText> AddLink(string text, string url, Action<SpriteText> 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<SpriteText> AddText(string text, Action<SpriteText> creationParameters = null)
|
|
||||||
{
|
|
||||||
return base.AddText(text, sprite =>
|
|
||||||
{
|
|
||||||
((OsuSpriteLink)sprite).TextColour = TextColour;
|
|
||||||
creationParameters?.Invoke(sprite);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -12,6 +12,8 @@ namespace osu.Game.Graphics.Sprites
|
|||||||
{
|
{
|
||||||
public class OsuSpriteLink : OsuSpriteText
|
public class OsuSpriteLink : OsuSpriteText
|
||||||
{
|
{
|
||||||
|
public override bool HandleInput => !string.IsNullOrEmpty(Url);
|
||||||
|
|
||||||
protected override IEnumerable<Drawable> FlowingChildren => Children;
|
protected override IEnumerable<Drawable> FlowingChildren => Children;
|
||||||
|
|
||||||
protected override Container<Drawable> Content => content;
|
protected override Container<Drawable> Content => content;
|
||||||
|
@ -1,19 +0,0 @@
|
|||||||
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
|
|
||||||
// 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<BeatmapInfo>
|
|
||||||
{
|
|
||||||
private readonly int beatmapId;
|
|
||||||
|
|
||||||
public GetBeatmapRequest(int beatmapId)
|
|
||||||
{
|
|
||||||
this.beatmapId = beatmapId;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected override string Target => $@"beatmaps/{beatmapId}";
|
|
||||||
}
|
|
||||||
}
|
|
@ -4,123 +4,57 @@
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Graphics.Cursor;
|
using osu.Framework.Graphics.Cursor;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Online.API;
|
|
||||||
using osu.Game.Online.API.Requests;
|
|
||||||
using osu.Game.Overlays;
|
using osu.Game.Overlays;
|
||||||
using System;
|
using System;
|
||||||
using System.Text.RegularExpressions;
|
|
||||||
|
|
||||||
namespace osu.Game.Online.Chat
|
namespace osu.Game.Online.Chat
|
||||||
{
|
{
|
||||||
public class ChatLink : OsuSpriteLink, IHasTooltip
|
public class ChatLink : OsuSpriteLink, IHasTooltip
|
||||||
{
|
{
|
||||||
private APIAccess api;
|
|
||||||
private BeatmapSetOverlay beatmapSetOverlay;
|
private BeatmapSetOverlay beatmapSetOverlay;
|
||||||
private ChatOverlay chat;
|
private ChatOverlay chat;
|
||||||
|
private OsuGame game;
|
||||||
|
|
||||||
public override bool HandleInput => !string.IsNullOrEmpty(Url);
|
/// <summary>
|
||||||
|
/// The type of action executed on clicking this link.
|
||||||
|
/// </summary>
|
||||||
|
public LinkAction LinkAction { get; set; }
|
||||||
|
|
||||||
// 'protocol' -> 'https', 'http', 'osu', 'osump' etc.
|
/// <summary>
|
||||||
// 'content' -> everything after '<protocol>://'
|
/// The argument necessary for the action specified by <see cref="LinkAction"/> to execute.
|
||||||
private Match getUrlMatch() => Regex.Match(Url, @"^(?<protocol>osu(?:mp)?|https?):\/\/(?<content>.*)");
|
/// <para>Usually a part of the URL.</para>
|
||||||
|
/// </summary>
|
||||||
|
public string LinkArgument { get; set; }
|
||||||
|
|
||||||
protected override void OnLinkClicked()
|
protected override void OnLinkClicked()
|
||||||
{
|
{
|
||||||
var urlMatch = getUrlMatch();
|
switch (LinkAction)
|
||||||
if (urlMatch.Success)
|
|
||||||
{
|
{
|
||||||
var args = urlMatch.Groups["content"].Value.Split('/');
|
case LinkAction.OpenBeatmap:
|
||||||
|
// todo: implement this when overlay.ShowBeatmap(id) exists
|
||||||
switch (urlMatch.Groups["protocol"].Value)
|
break;
|
||||||
{
|
case LinkAction.OpenBeatmapSet:
|
||||||
case "osu":
|
if (int.TryParse(LinkArgument, out int setId))
|
||||||
if (args.Length == 1)
|
beatmapSetOverlay.ShowBeatmapSet(setId);
|
||||||
{
|
break;
|
||||||
base.OnLinkClicked();
|
case LinkAction.OpenChannel:
|
||||||
break;
|
chat.OpenChannel(chat.AvailableChannels.Find(c => c.Name == LinkArgument));
|
||||||
}
|
break;
|
||||||
|
case LinkAction.OpenEditorTimestamp:
|
||||||
switch (args[0])
|
game?.LoadEditorTimestamp();
|
||||||
{
|
break;
|
||||||
case "chan":
|
case LinkAction.JoinMultiplayerMatch:
|
||||||
var foundChannel = chat.AvailableChannels.Find(channel => channel.Name == args[1]);
|
if (int.TryParse(LinkArgument, out int matchId))
|
||||||
|
game?.JoinMultiplayerMatch(matchId);
|
||||||
// links should be filtered out by now if a channel doesn't exist
|
break;
|
||||||
chat.OpenChannel(foundChannel ?? throw new ArgumentException($"Unknown channel name ({args[1]})."));
|
case LinkAction.Spectate:
|
||||||
break;
|
// todo: implement this when spectating exists
|
||||||
case "edit":
|
break;
|
||||||
chat.Game?.LoadEditorTimestamp();
|
case LinkAction.External:
|
||||||
break;
|
base.OnLinkClicked();
|
||||||
case "b":
|
break;
|
||||||
if (args.Length > 1 && int.TryParse(args[1], out int mapId))
|
default:
|
||||||
beatmapSetOverlay.ShowBeatmap(mapId);
|
throw new NotImplementedException($"This {nameof(Chat.LinkAction)} ({LinkAction.ToString()}) is missing an associated action.");
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,30 +65,25 @@ namespace osu.Game.Online.Chat
|
|||||||
if (Url == Text)
|
if (Url == Text)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
var urlMatch = getUrlMatch();
|
switch (LinkAction)
|
||||||
if (urlMatch.Success && urlMatch.Groups["protocol"].Value == "osu")
|
|
||||||
{
|
{
|
||||||
var args = urlMatch.Groups["content"].Value.Split('/');
|
case LinkAction.OpenChannel:
|
||||||
|
return "Switch to channel " + LinkArgument;
|
||||||
if (args.Length < 2)
|
case LinkAction.OpenEditorTimestamp:
|
||||||
|
return "Go to " + LinkArgument;
|
||||||
|
default:
|
||||||
return Url;
|
return Url;
|
||||||
|
|
||||||
if (args[0] == "chan")
|
|
||||||
return "Switch to channel " + args[1];
|
|
||||||
if (args[0] == "edit")
|
|
||||||
return "Go to " + args[1];
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return Url;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader(true)]
|
||||||
private void load(APIAccess api, BeatmapSetOverlay beatmapSetOverlay, ChatOverlay chat)
|
private void load(BeatmapSetOverlay beatmapSetOverlay, ChatOverlay chat, OsuGame game)
|
||||||
{
|
{
|
||||||
this.api = api;
|
|
||||||
this.beatmapSetOverlay = beatmapSetOverlay;
|
this.beatmapSetOverlay = beatmapSetOverlay;
|
||||||
this.chat = chat;
|
this.chat = chat;
|
||||||
|
// this will be null in tests
|
||||||
|
this.game = game;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -41,7 +41,7 @@ namespace osu.Game.Online.Chat
|
|||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<MessageFormatter.Link> Links;
|
public List<Link> Links;
|
||||||
|
|
||||||
public Message(long? id)
|
public Message(long? id)
|
||||||
{
|
{
|
||||||
|
@ -39,7 +39,7 @@ namespace osu.Game.Online.Chat
|
|||||||
// Unicode emojis
|
// Unicode emojis
|
||||||
private static readonly Regex emoji_regex = 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)
|
private static void handleMatches(Regex regex, string display, string link, MessageFormatterResult result, int startIndex = 0, LinkAction? linkActionOverride = null)
|
||||||
{
|
{
|
||||||
int captureOffset = 0;
|
int captureOffset = 0;
|
||||||
foreach (Match m in regex.Matches(result.Text, startIndex))
|
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.
|
//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.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.
|
//adjust the offset for processing the current matches group.
|
||||||
captureOffset += m.Length - displayText.Length;
|
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);
|
handleAdvanced(advanced_link_regex, result, startIndex);
|
||||||
|
|
||||||
// handle editor times
|
// 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
|
// handle channels
|
||||||
handleMatches(channel_regex, "{0}", "osu://chan/{0}", result, startIndex);
|
handleMatches(channel_regex, "{0}", "osu://chan/{0}", result, startIndex, LinkAction.OpenChannel);
|
||||||
|
|
||||||
var empty = "";
|
var empty = "";
|
||||||
while (space-- > 0)
|
while (space-- > 0)
|
||||||
@ -151,21 +215,36 @@ namespace osu.Game.Online.Chat
|
|||||||
OriginalText = Text = text;
|
OriginalText = Text = text;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class Link : IComparable<Link>
|
public enum LinkAction
|
||||||
|
{
|
||||||
|
External,
|
||||||
|
OpenBeatmap,
|
||||||
|
OpenBeatmapSet,
|
||||||
|
OpenChannel,
|
||||||
|
OpenEditorTimestamp,
|
||||||
|
JoinMultiplayerMatch,
|
||||||
|
Spectate,
|
||||||
|
}
|
||||||
|
|
||||||
|
public class Link : IComparable<Link>
|
||||||
|
{
|
||||||
|
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;
|
Url = url;
|
||||||
public int Index;
|
Index = startIndex;
|
||||||
public int Length;
|
Length = length;
|
||||||
|
Action = action;
|
||||||
public Link(string url, int startIndex, int length)
|
Argument = argument;
|
||||||
{
|
|
||||||
Url = url;
|
|
||||||
Index = startIndex;
|
|
||||||
Length = length;
|
|
||||||
}
|
|
||||||
|
|
||||||
public int CompareTo(Link otherLink) => Index > otherLink.Index ? 1 : -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public int CompareTo(Link otherLink) => Index > otherLink.Index ? 1 : -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -134,7 +134,7 @@ namespace osu.Game
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
internal void LoadMultiplayerLobby(int lobbyId)
|
internal void JoinMultiplayerMatch(int matchId)
|
||||||
{
|
{
|
||||||
notifications.Post(new SimpleNotification
|
notifications.Post(new SimpleNotification
|
||||||
{
|
{
|
||||||
|
@ -139,20 +139,6 @@ namespace osu.Game.Overlays
|
|||||||
return true;
|
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)
|
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.
|
// todo: display the overlay while we are loading here. we need to support setting BeatmapSet to null for this to work.
|
||||||
|
@ -83,13 +83,13 @@ namespace osu.Game.Overlays.Chat
|
|||||||
|
|
||||||
private Message message;
|
private Message message;
|
||||||
private OsuSpriteText username;
|
private OsuSpriteText username;
|
||||||
private OsuLinkFlowContainer<ChatLink> contentFlow;
|
private ChatFlowContainer contentFlow;
|
||||||
|
|
||||||
public OsuTextFlowContainer ContentFlow => contentFlow;
|
public ChatFlowContainer ContentFlow => contentFlow;
|
||||||
|
|
||||||
public Message Message
|
public Message Message
|
||||||
{
|
{
|
||||||
get { return message; }
|
get => message;
|
||||||
set
|
set
|
||||||
{
|
{
|
||||||
if (message == value) return;
|
if (message == value) return;
|
||||||
@ -107,7 +107,6 @@ namespace osu.Game.Overlays.Chat
|
|||||||
private void load(OsuColour colours, ChatOverlay chat)
|
private void load(OsuColour colours, ChatOverlay chat)
|
||||||
{
|
{
|
||||||
this.chat = chat;
|
this.chat = chat;
|
||||||
urlColour = colours.Blue;
|
|
||||||
customUsernameColour = colours.ChatBlue;
|
customUsernameColour = colours.ChatBlue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,10 +191,16 @@ namespace osu.Game.Overlays.Chat
|
|||||||
Padding = new MarginPadding { Left = message_padding + padding },
|
Padding = new MarginPadding { Left = message_padding + padding },
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
contentFlow = new OsuLinkFlowContainer<ChatLink>(t =>
|
contentFlow = new ChatFlowContainer(t =>
|
||||||
{
|
{
|
||||||
if (Message.IsAction)
|
if (Message.IsAction)
|
||||||
|
{
|
||||||
t.Font = @"Exo2.0-MediumItalic";
|
t.Font = @"Exo2.0-MediumItalic";
|
||||||
|
|
||||||
|
if (senderHasBackground)
|
||||||
|
t.TextColour = OsuColour.FromHex(message.Sender.Colour);
|
||||||
|
}
|
||||||
|
|
||||||
t.TextSize = text_size;
|
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();
|
updateMessageContent();
|
||||||
FinishTransforms(true);
|
FinishTransforms(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ChatOverlay chat;
|
private ChatOverlay chat;
|
||||||
private Color4 urlColour;
|
|
||||||
|
|
||||||
private void updateMessageContent()
|
private void updateMessageContent()
|
||||||
{
|
{
|
||||||
@ -230,7 +232,7 @@ namespace osu.Game.Overlays.Chat
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
int lastLinkEndIndex = 0;
|
int lastLinkEndIndex = 0;
|
||||||
List<MessageFormatter.Link> linksToRemove = new List<MessageFormatter.Link>();
|
List<Link> linksToRemove = new List<Link>();
|
||||||
|
|
||||||
foreach (var link in message.Links)
|
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 =>
|
contentFlow.AddLink(message.Content.Substring(link.Index, link.Length), link.Url, link.Action, link.Argument);
|
||||||
{
|
|
||||||
((ChatLink)sprite).TextColour = urlColour;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var lastLink = message.Links[message.Links.Count - 1];
|
var lastLink = message.Links[message.Links.Count - 1];
|
||||||
|
@ -41,11 +41,6 @@ namespace osu.Game.Overlays
|
|||||||
|
|
||||||
private readonly FocusedTextBox textbox;
|
private readonly FocusedTextBox textbox;
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// The current OsuGame instance. Will be null for Tests.
|
|
||||||
/// </summary>
|
|
||||||
public OsuGame Game;
|
|
||||||
|
|
||||||
private APIAccess api;
|
private APIAccess api;
|
||||||
|
|
||||||
private const int transition_length = 500;
|
private const int transition_length = 500;
|
||||||
@ -275,11 +270,9 @@ namespace osu.Game.Overlays
|
|||||||
base.PopOut();
|
base.PopOut();
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader(true)]
|
[BackgroundDependencyLoader]
|
||||||
private void load(APIAccess api, OsuConfigManager config, OsuColour colours, OsuGame game)
|
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;
|
this.api = api;
|
||||||
api.Register(this);
|
api.Register(this);
|
||||||
|
|
||||||
|
@ -23,7 +23,7 @@ namespace osu.Game.Overlays.Profile
|
|||||||
public class ProfileHeader : Container
|
public class ProfileHeader : Container
|
||||||
{
|
{
|
||||||
private readonly OsuTextFlowContainer infoTextLeft;
|
private readonly OsuTextFlowContainer infoTextLeft;
|
||||||
private readonly OsuLinkFlowContainer infoTextRight;
|
private readonly LinkFlowContainer infoTextRight;
|
||||||
private readonly FillFlowContainer<SpriteText> scoreText, scoreNumberText;
|
private readonly FillFlowContainer<SpriteText> scoreText, scoreNumberText;
|
||||||
private readonly RankGraph rankGraph;
|
private readonly RankGraph rankGraph;
|
||||||
|
|
||||||
@ -142,7 +142,7 @@ namespace osu.Game.Overlays.Profile
|
|||||||
ParagraphSpacing = 0.8f,
|
ParagraphSpacing = 0.8f,
|
||||||
LineSpacing = 0.2f
|
LineSpacing = 0.2f
|
||||||
},
|
},
|
||||||
infoTextRight = new OsuLinkFlowContainer(t =>
|
infoTextRight = new LinkFlowContainer(t =>
|
||||||
{
|
{
|
||||||
t.TextSize = 14;
|
t.TextSize = 14;
|
||||||
t.Font = @"Exo2.0-RegularItalic";
|
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<SpriteText> 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
|
private class ProfileLink : OsuSpriteLink, IHasTooltip
|
||||||
{
|
{
|
||||||
public string TooltipText => "View Profile in Browser";
|
public string TooltipText => "View Profile in Browser";
|
||||||
|
@ -268,9 +268,10 @@
|
|||||||
<Compile Include="Beatmaps\Formats\LegacyStoryboardDecoder.cs" />
|
<Compile Include="Beatmaps\Formats\LegacyStoryboardDecoder.cs" />
|
||||||
<Compile Include="Database\DatabaseContextFactory.cs" />
|
<Compile Include="Database\DatabaseContextFactory.cs" />
|
||||||
<Compile Include="Database\IHasPrimaryKey.cs" />
|
<Compile Include="Database\IHasPrimaryKey.cs" />
|
||||||
|
<Compile Include="Graphics\Containers\ChatFlowContainer.cs" />
|
||||||
|
<Compile Include="Online\API\Requests\GetUserRequest.cs" />
|
||||||
<Compile Include="Overlays\Profile\SupporterIcon.cs" />
|
<Compile Include="Overlays\Profile\SupporterIcon.cs" />
|
||||||
<Compile Include="Overlays\Settings\DangerousSettingsButton.cs" />
|
<Compile Include="Overlays\Settings\DangerousSettingsButton.cs" />
|
||||||
<Compile Include="Graphics\Containers\OsuLinkFlowContainer.cs" />
|
|
||||||
<Compile Include="Graphics\Sprites\OsuSpriteLink.cs" />
|
<Compile Include="Graphics\Sprites\OsuSpriteLink.cs" />
|
||||||
<Compile Include="Graphics\UserInterface\HoverClickSounds.cs" />
|
<Compile Include="Graphics\UserInterface\HoverClickSounds.cs" />
|
||||||
<Compile Include="Graphics\UserInterface\HoverSounds.cs" />
|
<Compile Include="Graphics\UserInterface\HoverSounds.cs" />
|
||||||
@ -293,7 +294,6 @@
|
|||||||
<DependentUpon>20171209034410_AddRulesetInfoShortName.cs</DependentUpon>
|
<DependentUpon>20171209034410_AddRulesetInfoShortName.cs</DependentUpon>
|
||||||
</Compile>
|
</Compile>
|
||||||
<Compile Include="Migrations\OsuDbContextModelSnapshot.cs" />
|
<Compile Include="Migrations\OsuDbContextModelSnapshot.cs" />
|
||||||
<Compile Include="Online\API\Requests\GetBeatmapRequest.cs" />
|
|
||||||
<Compile Include="Online\API\Requests\GetBeatmapSetRequest.cs" />
|
<Compile Include="Online\API\Requests\GetBeatmapSetRequest.cs" />
|
||||||
<Compile Include="Online\Chat\ChatLink.cs" />
|
<Compile Include="Online\Chat\ChatLink.cs" />
|
||||||
<Compile Include="Online\Chat\MessageFormatter.cs" />
|
<Compile Include="Online\Chat\MessageFormatter.cs" />
|
||||||
@ -441,7 +441,6 @@
|
|||||||
<Compile Include="Online\API\Requests\SearchBeatmapSetsRequest.cs" />
|
<Compile Include="Online\API\Requests\SearchBeatmapSetsRequest.cs" />
|
||||||
<Compile Include="Online\API\Requests\GetMessagesRequest.cs" />
|
<Compile Include="Online\API\Requests\GetMessagesRequest.cs" />
|
||||||
<Compile Include="Online\API\Requests\GetScoresRequest.cs" />
|
<Compile Include="Online\API\Requests\GetScoresRequest.cs" />
|
||||||
<Compile Include="Online\API\Requests\GetUserRequest.cs" />
|
|
||||||
<Compile Include="Online\API\Requests\GetUsersRequest.cs" />
|
<Compile Include="Online\API\Requests\GetUsersRequest.cs" />
|
||||||
<Compile Include="Online\API\Requests\ListChannelsRequest.cs" />
|
<Compile Include="Online\API\Requests\ListChannelsRequest.cs" />
|
||||||
<Compile Include="Online\API\Requests\PostMessageRequest.cs" />
|
<Compile Include="Online\API\Requests\PostMessageRequest.cs" />
|
||||||
|
Loading…
Reference in New Issue
Block a user