1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-13 19:52:55 +08:00

Merge pull request #3887 from peppy/standalone-chat

Add a stand-alone chat display component
This commit is contained in:
Dan Balasescu 2018-12-21 12:47:01 +09:00 committed by GitHub
commit e8a03a94d1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 354 additions and 19 deletions

View File

@ -0,0 +1,102 @@
// 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;
using osu.Game.Online.Chat;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Tests.Visual
{
public class TestCaseStandAloneChatDisplay : OsuTestCase
{
private readonly Channel testChannel = new Channel();
private readonly User admin = new User
{
Username = "HappyStick",
Id = 2,
Colour = "f2ca34"
};
private readonly User redUser = new User
{
Username = "BanchoBot",
Id = 3,
};
private readonly User blueUser = new User
{
Username = "Zallius",
Id = 4,
};
[Cached]
private ChannelManager channelManager = new ChannelManager();
private readonly StandAloneChatDisplay chatDisplay;
private readonly StandAloneChatDisplay chatDisplay2;
public TestCaseStandAloneChatDisplay()
{
Add(channelManager);
Add(chatDisplay = new StandAloneChatDisplay
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Margin = new MarginPadding(20),
Size = new Vector2(400, 80)
});
Add(chatDisplay2 = new StandAloneChatDisplay(true)
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Margin = new MarginPadding(20),
Size = new Vector2(400, 150)
});
}
protected override void LoadComplete()
{
base.LoadComplete();
channelManager.CurrentChannel.Value = testChannel;
chatDisplay.Channel.Value = testChannel;
chatDisplay2.Channel.Value = testChannel;
AddStep("message from admin", () => testChannel.AddLocalEcho(new LocalEchoMessage
{
Sender = admin,
Content = "I am a wang!"
}));
AddStep("message from team red", () => testChannel.AddLocalEcho(new LocalEchoMessage
{
Sender = redUser,
Content = "I am team red."
}));
AddStep("message from team red", () => testChannel.AddLocalEcho(new LocalEchoMessage
{
Sender = redUser,
Content = "I plan to win!"
}));
AddStep("message from team blue", () => testChannel.AddLocalEcho(new LocalEchoMessage
{
Sender = blueUser,
Content = "Not on my watch. Prepare to eat saaaaaaaaaand. Lots and lots of saaaaaaand."
}));
AddStep("message from admin", () => testChannel.AddLocalEcho(new LocalEchoMessage
{
Sender = admin,
Content = "Okay okay, calm down guys. Let's do this!"
}));
}
}
}

View File

@ -96,12 +96,14 @@ namespace osu.Game.Online.Chat
/// </summary> /// </summary>
/// <param name="text">The message text that is going to be posted</param> /// <param name="text">The message text that is going to be posted</param>
/// <param name="isAction">Is true if the message is an action, e.g.: user is currently eating </param> /// <param name="isAction">Is true if the message is an action, e.g.: user is currently eating </param>
public void PostMessage(string text, bool isAction = false) /// <param name="target">An optional target channel. If null, <see cref="CurrentChannel"/> will be used.</param>
public void PostMessage(string text, bool isAction = false, Channel target = null)
{ {
if (CurrentChannel.Value == null) if (target == null)
return; target = CurrentChannel.Value;
var currentChannel = CurrentChannel.Value; if (target == null)
return;
void dequeueAndRun() void dequeueAndRun()
{ {
@ -113,7 +115,7 @@ namespace osu.Game.Online.Chat
{ {
if (!api.IsLoggedIn) if (!api.IsLoggedIn)
{ {
currentChannel.AddNewMessages(new ErrorMessage("Please sign in to participate in chat!")); target.AddNewMessages(new ErrorMessage("Please sign in to participate in chat!"));
return; return;
} }
@ -121,29 +123,29 @@ namespace osu.Game.Online.Chat
{ {
Sender = api.LocalUser.Value, Sender = api.LocalUser.Value,
Timestamp = DateTimeOffset.Now, Timestamp = DateTimeOffset.Now,
ChannelId = CurrentChannel.Value.Id, ChannelId = target.Id,
IsAction = isAction, IsAction = isAction,
Content = text Content = text
}; };
currentChannel.AddLocalEcho(message); target.AddLocalEcho(message);
// if this is a PM and the first message, we need to do a special request to create the PM channel // if this is a PM and the first message, we need to do a special request to create the PM channel
if (currentChannel.Type == ChannelType.PM && !currentChannel.Joined) if (target.Type == ChannelType.PM && !target.Joined)
{ {
var createNewPrivateMessageRequest = new CreateNewPrivateMessageRequest(currentChannel.Users.First(), message); var createNewPrivateMessageRequest = new CreateNewPrivateMessageRequest(target.Users.First(), message);
createNewPrivateMessageRequest.Success += createRes => createNewPrivateMessageRequest.Success += createRes =>
{ {
currentChannel.Id = createRes.ChannelID; target.Id = createRes.ChannelID;
currentChannel.ReplaceMessage(message, createRes.Message); target.ReplaceMessage(message, createRes.Message);
dequeueAndRun(); dequeueAndRun();
}; };
createNewPrivateMessageRequest.Failure += exception => createNewPrivateMessageRequest.Failure += exception =>
{ {
Logger.Error(exception, "Posting message failed."); Logger.Error(exception, "Posting message failed.");
currentChannel.ReplaceMessage(message, null); target.ReplaceMessage(message, null);
dequeueAndRun(); dequeueAndRun();
}; };
@ -155,14 +157,14 @@ namespace osu.Game.Online.Chat
req.Success += m => req.Success += m =>
{ {
currentChannel.ReplaceMessage(message, m); target.ReplaceMessage(message, m);
dequeueAndRun(); dequeueAndRun();
}; };
req.Failure += exception => req.Failure += exception =>
{ {
Logger.Error(exception, "Posting message failed."); Logger.Error(exception, "Posting message failed.");
currentChannel.ReplaceMessage(message, null); target.ReplaceMessage(message, null);
dequeueAndRun(); dequeueAndRun();
}; };
@ -178,9 +180,13 @@ namespace osu.Game.Online.Chat
/// Posts a command locally. Commands like /help will result in a help message written in the current channel. /// Posts a command locally. Commands like /help will result in a help message written in the current channel.
/// </summary> /// </summary>
/// <param name="text">the text containing the command identifier and command parameters.</param> /// <param name="text">the text containing the command identifier and command parameters.</param>
public void PostCommand(string text) /// <param name="target">An optional target channel. If null, <see cref="CurrentChannel"/> will be used.</param>
public void PostCommand(string text, Channel target = null)
{ {
if (CurrentChannel.Value == null) if (target == null)
target = CurrentChannel.Value;
if (target == null)
return; return;
var parameters = text.Split(new[] { ' ' }, 2); var parameters = text.Split(new[] { ' ' }, 2);
@ -192,7 +198,7 @@ namespace osu.Game.Online.Chat
case "me": case "me":
if (string.IsNullOrWhiteSpace(content)) if (string.IsNullOrWhiteSpace(content))
{ {
CurrentChannel.Value.AddNewMessages(new ErrorMessage("Usage: /me [action]")); target.AddNewMessages(new ErrorMessage("Usage: /me [action]"));
break; break;
} }
@ -200,11 +206,11 @@ namespace osu.Game.Online.Chat
break; break;
case "help": case "help":
CurrentChannel.Value.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action]")); target.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action]"));
break; break;
default: default:
CurrentChannel.Value.AddNewMessages(new ErrorMessage($@"""/{command}"" is not supported! For a list of supported commands see /help")); target.AddNewMessages(new ErrorMessage($@"""/{command}"" is not supported! For a list of supported commands see /help"));
break; break;
} }
} }

View File

@ -0,0 +1,227 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Online.Chat
{
/// <summary>
/// Display a chat channel in an insolated region.
/// </summary>
public class StandAloneChatDisplay : CompositeDrawable
{
public readonly Bindable<Channel> Channel = new Bindable<Channel>();
private readonly FillFlowContainer messagesFlow;
private Channel lastChannel;
private readonly FocusedTextBox textbox;
protected ChannelManager ChannelManager;
/// <summary>
/// Construct a new instance.
/// </summary>
/// <param name="postingTextbox">Whether a textbox for posting new messages should be displayed.</param>
public StandAloneChatDisplay(bool postingTextbox = false)
{
CornerRadius = 10;
Masking = true;
InternalChildren = new Drawable[]
{
new Box
{
Colour = Color4.Black,
Alpha = 0.8f,
RelativeSizeAxes = Axes.Both
},
messagesFlow = new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
LayoutEasing = Easing.Out,
LayoutDuration = 500,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
Direction = FillDirection.Vertical
}
};
const float textbox_height = 30;
if (postingTextbox)
{
messagesFlow.Y -= textbox_height;
AddInternal(textbox = new FocusedTextBox
{
RelativeSizeAxes = Axes.X,
Height = textbox_height,
PlaceholderText = "type your message",
OnCommit = postMessage,
ReleaseFocusOnCommit = false,
HoldFocus = true,
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
});
}
Channel.BindValueChanged(channelChanged);
}
[BackgroundDependencyLoader(true)]
private void load(ChannelManager manager)
{
if (ChannelManager == null)
ChannelManager = manager;
}
private void postMessage(TextBox sender, bool newtext)
{
var text = textbox.Text.Trim();
if (string.IsNullOrWhiteSpace(text))
return;
if (text[0] == '/')
ChannelManager?.PostCommand(text.Substring(1));
else
ChannelManager?.PostMessage(text);
textbox.Text = string.Empty;
}
public void Contract()
{
this.FadeIn(300);
this.MoveToY(0, 500, Easing.OutQuint);
}
public void Expand()
{
this.FadeOut(200);
this.MoveToY(100, 500, Easing.In);
}
protected virtual Drawable CreateMessage(Message message)
{
return new StandAloneMessage(message);
}
private void channelChanged(Channel channel)
{
if (lastChannel != null)
lastChannel.NewMessagesArrived -= newMessages;
lastChannel = channel;
messagesFlow.Clear();
if (channel == null) return;
channel.NewMessagesArrived += newMessages;
}
private void newMessages(IEnumerable<Message> messages)
{
var excessChildren = messagesFlow.Children.Count - 10;
if (excessChildren > 0)
foreach (var c in messagesFlow.Children.Take(excessChildren))
c.Expire();
foreach (var message in messages)
{
var formatted = MessageFormatter.FormatMessage(message);
var drawable = CreateMessage(formatted);
drawable.Y = messagesFlow.Height;
messagesFlow.Add(drawable);
}
}
protected class StandAloneMessage : CompositeDrawable
{
protected readonly Message Message;
protected OsuSpriteText SenderText;
protected Circle ColourBox;
public StandAloneMessage(Message message)
{
Message = message;
}
[BackgroundDependencyLoader]
private void load()
{
Margin = new MarginPadding(3);
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChildren = new Drawable[]
{
new FillFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.X,
Width = 0.2f,
Children = new Drawable[]
{
SenderText = new OsuSpriteText
{
Font = @"Exo2.0-Bold",
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Text = Message.Sender.ToString()
}
}
},
new Container
{
Size = new Vector2(8, OsuSpriteText.FONT_SIZE),
Margin = new MarginPadding { Horizontal = 3 },
Children = new Drawable[]
{
ColourBox = new Circle
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(8)
}
}
},
new OsuTextFlowContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Width = 0.5f,
Text = Message.DisplayContent
}
}
}
};
if (!string.IsNullOrEmpty(Message.Sender.Colour))
SenderText.Colour = ColourBox.Colour = OsuColour.FromHex(Message.Sender.Colour);
}
}
}
}