1
0
mirror of https://github.com/ppy/osu.git synced 2024-09-22 01:27:29 +08:00

Merge pull request #6685 from peppy/game-handles-links

Move link handling code to OsuGame
This commit is contained in:
Dan Balasescu 2019-11-05 12:51:18 +09:00 committed by GitHub
commit af7322a763
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 130 additions and 91 deletions

View File

@ -8,9 +8,6 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics.Sprites;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Logging;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osu.Game.Users;
namespace osu.Game.Graphics.Containers
@ -23,21 +20,12 @@ namespace osu.Game.Graphics.Containers
}
private OsuGame game;
private ChannelManager channelManager;
private Action showNotImplementedError;
[BackgroundDependencyLoader(true)]
private void load(OsuGame game, NotificationOverlay notifications, ChannelManager channelManager)
private void load(OsuGame game)
{
// will be null in tests
this.game = game;
this.channelManager = channelManager;
showNotImplementedError = () => notifications?.Post(new SimpleNotification
{
Text = @"This link type is not yet supported!",
Icon = FontAwesome.Solid.LifeRing,
});
}
public void AddLinks(string text, List<Link> links)
@ -56,85 +44,47 @@ 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);
AddLink(text.Substring(link.Index, link.Length), link.Action, link.Argument ?? link.Url);
previousLinkEnd = link.Index + link.Length;
}
AddText(text.Substring(previousLinkEnd));
}
public IEnumerable<Drawable> AddLink(string text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null, Action<SpriteText> creationParameters = null)
=> createLink(AddText(text, creationParameters), text, url, linkType, linkArgument, tooltipText);
public void AddLink(string text, string url, Action<SpriteText> creationParameters = null) =>
createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.External, url), url);
public IEnumerable<Drawable> AddLink(string text, Action action, string tooltipText = null, Action<SpriteText> creationParameters = null)
=> createLink(AddText(text, creationParameters), text, tooltipText: tooltipText, action: action);
public void AddLink(string text, Action action, string tooltipText = null, Action<SpriteText> creationParameters = null)
=> createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.Custom, null), tooltipText, action);
public IEnumerable<Drawable> AddLink(IEnumerable<SpriteText> text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null)
public void AddLink(string text, LinkAction action, string argument, string tooltipText = null, Action<SpriteText> creationParameters = null)
=> createLink(AddText(text, creationParameters), new LinkDetails(action, argument), null);
public void AddLink(IEnumerable<SpriteText> text, LinkAction action = LinkAction.External, string linkArgument = null, string tooltipText = null)
{
foreach (var t in text)
AddArbitraryDrawable(t);
return createLink(text, null, url, linkType, linkArgument, tooltipText);
createLink(text, new LinkDetails(action, linkArgument), tooltipText);
}
public IEnumerable<Drawable> AddUserLink(User user, Action<SpriteText> creationParameters = null)
=> createLink(AddText(user.Username, creationParameters), user.Username, null, LinkAction.OpenUserProfile, user.Id.ToString(), "View profile");
public void AddUserLink(User user, Action<SpriteText> creationParameters = null)
=> createLink(AddText(user.Username, creationParameters), new LinkDetails(LinkAction.OpenUserProfile, user.Id.ToString()), "View Profile");
private IEnumerable<Drawable> createLink(IEnumerable<Drawable> drawables, string text, string url = null, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null, Action action = null)
private void createLink(IEnumerable<Drawable> drawables, LinkDetails link, string tooltipText, Action action = null)
{
AddInternal(new DrawableLinkCompiler(drawables.OfType<SpriteText>().ToList())
{
RelativeSizeAxes = Axes.Both,
TooltipText = tooltipText ?? (url != text ? url : string.Empty),
Action = action ?? (() =>
TooltipText = tooltipText,
Action = () =>
{
switch (linkType)
{
case LinkAction.OpenBeatmap:
// TODO: proper query params handling
if (linkArgument != null && int.TryParse(linkArgument.Contains('?') ? linkArgument.Split('?')[0] : linkArgument, out int beatmapId))
game?.ShowBeatmap(beatmapId);
break;
case LinkAction.OpenBeatmapSet:
if (int.TryParse(linkArgument, out int setId))
game?.ShowBeatmapSet(setId);
break;
case LinkAction.OpenChannel:
try
{
channelManager?.OpenChannel(linkArgument);
}
catch (ChannelNotFoundException)
{
Logger.Log($"The requested channel \"{linkArgument}\" does not exist");
}
break;
case LinkAction.OpenEditorTimestamp:
case LinkAction.JoinMultiplayerMatch:
case LinkAction.Spectate:
showNotImplementedError?.Invoke();
break;
case LinkAction.External:
game?.OpenUrlExternally(url);
break;
case LinkAction.OpenUserProfile:
if (long.TryParse(linkArgument, out long userId))
game?.ShowUser(userId);
break;
default:
throw new NotImplementedException($"This {nameof(LinkAction)} ({linkType.ToString()}) is missing an associated action.");
}
}),
if (action != null)
action();
else
game.HandleLink(link);
},
});
return drawables;
}
// We want the compilers to always be visible no matter where they are, so RelativeSizeAxes is used.

View File

@ -81,7 +81,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(linkText);
var details = GetLinkDetails(linkText);
result.Links.Add(new Link(linkText, index, displayText.Length, linkActionOverride ?? details.Action, details.Argument));
//adjust the offset for processing the current matches group.
@ -98,7 +98,7 @@ namespace osu.Game.Online.Chat
var linkText = m.Groups["link"].Value;
var indexLength = linkText.Length;
var details = getLinkDetails(linkText);
var details = GetLinkDetails(linkText);
var link = new Link(linkText, index, indexLength, details.Action, details.Argument);
// sometimes an already-processed formatted link can reduce to a simple URL, too
@ -109,7 +109,7 @@ namespace osu.Game.Online.Chat
}
}
private static LinkDetails getLinkDetails(string url)
public static LinkDetails GetLinkDetails(string url)
{
var args = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries);
args[0] = args[0].TrimEnd(':');
@ -255,17 +255,17 @@ namespace osu.Game.Online.Chat
OriginalText = Text = text;
}
}
}
public class LinkDetails
public class LinkDetails
{
public LinkAction Action;
public string Argument;
public LinkDetails(LinkAction action, string argument)
{
public LinkAction Action;
public string Argument;
public LinkDetails(LinkAction action, string argument)
{
Action = action;
Argument = argument;
}
Action = action;
Argument = argument;
}
}
@ -279,6 +279,7 @@ namespace osu.Game.Online.Chat
JoinMultiplayerMatch,
Spectate,
OpenUserProfile,
Custom
}
public class Link : IComparable<Link>

View File

@ -215,31 +215,102 @@ namespace osu.Game
private ExternalLinkOpener externalLinkOpener;
public void OpenUrlExternally(string url)
/// <summary>
/// Handle an arbitrary URL. Displays via in-game overlays where possible.
/// This can be called from a non-thread-safe non-game-loaded state.
/// </summary>
/// <param name="url">The URL to load.</param>
public void HandleLink(string url) => HandleLink(MessageFormatter.GetLinkDetails(url));
/// <summary>
/// Handle a specific <see cref="LinkDetails"/>.
/// This can be called from a non-thread-safe non-game-loaded state.
/// </summary>
/// <param name="link">The link to load.</param>
public void HandleLink(LinkDetails link) => Schedule(() =>
{
switch (link.Action)
{
case LinkAction.OpenBeatmap:
// TODO: proper query params handling
if (link.Argument != null && int.TryParse(link.Argument.Contains('?') ? link.Argument.Split('?')[0] : link.Argument, out int beatmapId))
ShowBeatmap(beatmapId);
break;
case LinkAction.OpenBeatmapSet:
if (int.TryParse(link.Argument, out int setId))
ShowBeatmapSet(setId);
break;
case LinkAction.OpenChannel:
ShowChannel(link.Argument);
break;
case LinkAction.OpenEditorTimestamp:
case LinkAction.JoinMultiplayerMatch:
case LinkAction.Spectate:
waitForReady(() => notifications, _ => notifications?.Post(new SimpleNotification
{
Text = @"This link type is not yet supported!",
Icon = FontAwesome.Solid.LifeRing,
}));
break;
case LinkAction.External:
OpenUrlExternally(link.Argument);
break;
case LinkAction.OpenUserProfile:
if (long.TryParse(link.Argument, out long userId))
ShowUser(userId);
break;
default:
throw new NotImplementedException($"This {nameof(LinkAction)} ({link.Action.ToString()}) is missing an associated action.");
}
});
public void OpenUrlExternally(string url) => waitForReady(() => externalLinkOpener, _ =>
{
if (url.StartsWith("/"))
url = $"{API.Endpoint}{url}";
externalLinkOpener.OpenUrlExternally(url);
}
});
/// <summary>
/// Open a specific channel in chat.
/// </summary>
/// <param name="channel">The channel to display.</param>
public void ShowChannel(string channel) => waitForReady(() => channelManager, _ =>
{
try
{
channelManager.OpenChannel(channel);
}
catch (ChannelNotFoundException)
{
Logger.Log($"The requested channel \"{channel}\" does not exist");
}
});
/// <summary>
/// Show a beatmap set as an overlay.
/// </summary>
/// <param name="setId">The set to display.</param>
public void ShowBeatmapSet(int setId) => beatmapSetOverlay.FetchAndShowBeatmapSet(setId);
public void ShowBeatmapSet(int setId) => waitForReady(() => beatmapSetOverlay, _ => beatmapSetOverlay.FetchAndShowBeatmapSet(setId));
/// <summary>
/// Show a user's profile as an overlay.
/// </summary>
/// <param name="userId">The user to display.</param>
public void ShowUser(long userId) => userProfile.ShowUser(userId);
public void ShowUser(long userId) => waitForReady(() => userProfile, _ => userProfile.ShowUser(userId));
/// <summary>
/// Show a beatmap's set as an overlay, displaying the given beatmap.
/// </summary>
/// <param name="beatmapId">The beatmap to show.</param>
public void ShowBeatmap(int beatmapId) => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId);
public void ShowBeatmap(int beatmapId) => waitForReady(() => beatmapSetOverlay, _ => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId));
/// <summary>
/// Present a beatmap at song select immediately.
@ -397,6 +468,23 @@ namespace osu.Game
performFromMainMenuTask = Schedule(() => performFromMainMenu(action, taskName));
}
/// <summary>
/// Wait for the game (and target component) to become loaded and then run an action.
/// </summary>
/// <param name="retrieveInstance">A function to retrieve a (potentially not-yet-constructed) target instance.</param>
/// <param name="action">The action to perform on the instance when load is confirmed.</param>
/// <typeparam name="T">The type of the target instance.</typeparam>
private void waitForReady<T>(Func<T> retrieveInstance, Action<T> action)
where T : Drawable
{
var instance = retrieveInstance();
if (ScreenStack == null || ScreenStack.CurrentScreen is StartupScreen || instance?.IsLoaded != true)
Schedule(() => waitForReady(retrieveInstance, action));
else
action(instance);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);

View File

@ -110,7 +110,7 @@ namespace osu.Game.Overlays.Changelog
t.Font = fontLarge;
t.Colour = entryColour;
});
title.AddLink($"{entry.Repository.Replace("ppy/", "")}#{entry.GithubPullRequestId}", entry.GithubUrl, Online.Chat.LinkAction.External,
title.AddLink($"{entry.Repository.Replace("ppy/", "")}#{entry.GithubPullRequestId}", entry.GithubUrl,
creationParameters: t =>
{
t.Font = fontLarge;
@ -140,7 +140,7 @@ namespace osu.Game.Overlays.Changelog
t.Colour = entryColour;
});
else if (entry.GithubUser.GithubUrl != null)
title.AddLink(entry.GithubUser.DisplayName, entry.GithubUser.GithubUrl, Online.Chat.LinkAction.External, null, null, t =>
title.AddLink(entry.GithubUser.DisplayName, entry.GithubUser.GithubUrl, t =>
{
t.Font = fontMedium;
t.Colour = entryColour;

View File

@ -81,7 +81,7 @@ namespace osu.Game.Screens.Multi.Components
Text = new LocalisedString((beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title)),
Font = OsuFont.GetFont(size: TextSize),
}
}, null, LinkAction.OpenBeatmap, beatmap.OnlineBeatmapID.ToString(), "Open beatmap");
}, LinkAction.OpenBeatmap, beatmap.OnlineBeatmapID.ToString(), "Open beatmap");
}
}
}