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

Use a better method of link compilation

Adds word wrap back, simplifies a lot.
This commit is contained in:
Dean Herbert 2018-01-09 20:22:23 +09:00
parent 1be0569743
commit 72624aea18
11 changed files with 172 additions and 269 deletions

View File

@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.ComponentModel; using System.ComponentModel;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Overlays; using osu.Game.Overlays;
@ -11,28 +10,12 @@ namespace osu.Game.Tests.Visual
[Description("Testing chat api and overlay")] [Description("Testing chat api and overlay")]
public class TestCaseChatDisplay : OsuTestCase 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() public TestCaseChatDisplay()
{ {
Add(chat = new ChatOverlay Add(new ChatOverlay
{ {
State = Visibility.Visible State = Visibility.Visible
}); });
Add(beatmapSetOverlay = new BeatmapSetOverlay());
}
[BackgroundDependencyLoader]
private void load()
{
dependencies.Cache(chat);
dependencies.Cache(beatmapSetOverlay);
} }
} }
} }

View File

@ -14,6 +14,7 @@ using osu.Game.Overlays.Chat;
using osu.Game.Users; using osu.Game.Users;
using System; using System;
using System.Linq; using System.Linq;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Tests.Visual 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} is " + (isAction ? "italic" : "not italic"), () => newLine.ContentFlow.Any() && isAction == isItalic(newLine.ContentFlow));
AddAssert($"msg #{textContainer.Count} shows link(s)", isShowingLinks); AddAssert($"msg #{textContainer.Count} shows link(s)", isShowingLinks);
bool isItalic(ChatFlowContainer c) => c.Cast<ChatLink>().All(sprite => sprite.Font == @"Exo2.0-MediumItalic"); bool isItalic(LinkFlowContainer c) => c.Cast<OsuSpriteText>().All(sprite => sprite.Font == @"Exo2.0-MediumItalic");
bool isShowingLinks() bool isShowingLinks()
{ {
@ -68,11 +69,11 @@ namespace osu.Game.Tests.Visual
textColour = OsuColour.FromHex(newLine.Message.Sender.Colour); textColour = OsuColour.FromHex(newLine.Message.Sender.Colour);
return newLine.ContentFlow return newLine.ContentFlow
.Cast<ChatLink>() .Cast<OsuSpriteText>()
.All(sprite => sprite.HandleInput && !sprite.TextColour.Equals(textColour) .All(sprite => sprite.HandleInput && !sprite.Colour.Equals(textColour)
|| !sprite.HandleInput && sprite.TextColour.Equals(textColour) || !sprite.HandleInput && sprite.Colour.Equals(textColour)
// if someone with a background uses /me with a link, the usual link colour is overridden // 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));
} }
} }

View File

@ -1,61 +0,0 @@
// Copyright (c) 2007-2018 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;
}
}
}

View File

@ -0,0 +1,75 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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<SpriteText> 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.");
}
}
});
}
}
}

View File

@ -1,8 +1,10 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Input; using osu.Framework.Input;
@ -10,26 +12,34 @@ namespace osu.Game.Graphics.Containers
{ {
public class OsuHoverContainer : OsuClickableContainer public class OsuHoverContainer : OsuClickableContainer
{ {
private Color4 hoverColour; protected Color4 HoverColour;
private Color4 unhoverColour;
protected Color4 IdleColour = Color4.White;
protected virtual IEnumerable<Drawable> EffectTargets => new[] { Content };
protected override bool OnHover(InputState state) 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); return base.OnHover(state);
} }
protected override void OnHoverLost(InputState 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); base.OnHoverLost(state);
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
hoverColour = colours.Yellow; HoverColour = colours.Yellow;
unhoverColour = Colour; }
protected override void LoadComplete()
{
base.LoadComplete();
EffectTargets.ForEach(d => d.FadeColour(IdleColour));
} }
} }
} }

View File

@ -1,52 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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<Drawable> FlowingChildren => Children;
protected override Container<Drawable> 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);
}
}

View File

@ -1,89 +0,0 @@
// Copyright (c) 2007-2018 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.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;
/// <summary>
/// The type of action executed on clicking this link.
/// </summary>
public LinkAction LinkAction { get; set; }
/// <summary>
/// The argument necessary for the action specified by <see cref="LinkAction"/> to execute.
/// <para>Usually a part of the URL.</para>
/// </summary>
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;
}
}
}

View File

@ -0,0 +1,43 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// 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
{
/// <summary>
/// An invisible drawable that brings multiple <see cref="SpriteText"/> pieces together to form a consumable clickable link.
/// </summary>
public class DrawableLinkCompiler : OsuHoverContainer, IHasTooltip
{
/// <summary>
/// Each word part of a chat link (split for word-wrap support).
/// </summary>
public List<SpriteText> Parts;
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => Parts.Any(d => d.ReceiveMouseInputAt(screenSpacePos));
public DrawableLinkCompiler(IEnumerable<SpriteText> parts)
{
Parts = parts.ToList();
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
IdleColour = colours.Blue;
}
protected override IEnumerable<Drawable> EffectTargets => Parts;
public string TooltipText { get; set; }
}
}

View File

@ -83,9 +83,9 @@ namespace osu.Game.Overlays.Chat
private Message message; private Message message;
private OsuSpriteText username; private OsuSpriteText username;
private ChatFlowContainer contentFlow; private LinkFlowContainer contentFlow;
public ChatFlowContainer ContentFlow => contentFlow; public LinkFlowContainer ContentFlow => contentFlow;
public Message Message public Message Message
{ {
@ -191,14 +191,14 @@ 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 ChatFlowContainer(t => contentFlow = new LinkFlowContainer(t =>
{ {
if (Message.IsAction) if (Message.IsAction)
{ {
t.Font = @"Exo2.0-MediumItalic"; t.Font = @"Exo2.0-MediumItalic";
if (senderHasBackground) if (senderHasBackground)
t.TextColour = OsuColour.FromHex(message.Sender.Colour); t.Colour = OsuColour.FromHex(message.Sender.Colour);
} }
t.TextSize = text_size; 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]; var lastLink = message.Links[message.Links.Count - 1];

View File

@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Diagnostics;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
@ -9,6 +10,7 @@ using osu.Framework.Extensions.Color4Extensions;
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.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
@ -16,7 +18,6 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Users; using osu.Game.Users;
using osu.Framework.Graphics.Cursor;
namespace osu.Game.Overlays.Profile namespace osu.Game.Overlays.Profile
{ {
@ -435,6 +436,28 @@ namespace osu.Game.Overlays.Profile
infoTextRight.NewLine(); 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 class GradeBadge : Container
{ {
private const float width = 50; private const float width = 50;
@ -472,34 +495,5 @@ namespace osu.Game.Overlays.Profile
badge.Texture = textures.Get($"Grades/{grade}"); badge.Texture = textures.Get($"Grades/{grade}");
} }
} }
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
{
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;
}
}
} }
} }

View File

@ -267,12 +267,11 @@
<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="Graphics\Containers\LinkFlowContainer.cs" />
<Compile Include="Graphics\Textures\LargeTextureStore.cs" /> <Compile Include="Graphics\Textures\LargeTextureStore.cs" />
<Compile Include="Online\API\Requests\GetUserRequest.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\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" />
<Compile Include="Graphics\UserInterface\OsuButton.cs" /> <Compile Include="Graphics\UserInterface\OsuButton.cs" />
@ -295,7 +294,7 @@
</Compile> </Compile>
<Compile Include="Migrations\OsuDbContextModelSnapshot.cs" /> <Compile Include="Migrations\OsuDbContextModelSnapshot.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\DrawableLinkCompiler.cs" />
<Compile Include="Online\Chat\MessageFormatter.cs" /> <Compile Include="Online\Chat\MessageFormatter.cs" />
<Compile Include="Online\API\Requests\APIResponseBeatmapSet.cs" /> <Compile Include="Online\API\Requests\APIResponseBeatmapSet.cs" />
<Compile Include="Online\API\Requests\GetUserMostPlayedBeatmapsRequest.cs" /> <Compile Include="Online\API\Requests\GetUserMostPlayedBeatmapsRequest.cs" />