// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; using System.Linq; using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Effects; 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 osu.Game.Online.API; using osu.Game.Online.API.Requests.Responses; using osu.Game.Online.Chat; using osuTK; using osuTK.Graphics; namespace osu.Game.Overlays.Chat { public class ChatLine : CompositeDrawable { public const float LEFT_PADDING = default_message_padding + default_horizontal_padding * 2; private const float default_message_padding = 200; protected virtual float MessagePadding => default_message_padding; private const float default_timestamp_padding = 65; protected virtual float TimestampPadding => default_timestamp_padding; private const float default_horizontal_padding = 15; protected virtual float HorizontalPadding => default_horizontal_padding; protected virtual float TextSize => 20; private Container lineHighlightBackground; private Color4 usernameColour; private OsuSpriteText timestamp; public ChatLine(Message message) { Message = message; Padding = new MarginPadding { Left = HorizontalPadding, Right = HorizontalPadding }; RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; } [Resolved(CanBeNull = true)] private ChannelManager chatManager { get; set; } private Message message; private OsuSpriteText username; public LinkFlowContainer ContentFlow { get; private set; } public Message Message { get => message; set { if (message == value) return; message = MessageFormatter.FormatMessage(value); if (!IsLoaded) return; updateMessageContent(); } } private bool senderHasColour => !string.IsNullOrEmpty(message.Sender.Colour); [Resolved] private OsuColour colours { get; set; } [BackgroundDependencyLoader] private void load() { usernameColour = senderHasColour ? Color4Extensions.FromHex(message.Sender.Colour) : username_colours[message.Sender.Id % username_colours.Length]; Drawable effectedUsername = username = new OsuSpriteText { Shadow = false, Colour = senderHasColour ? colours.ChatBlue : usernameColour, Truncate = true, EllipsisString = "… :", Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Bold, italics: true), Anchor = Anchor.TopRight, Origin = Anchor.TopRight, MaxWidth = MessagePadding - TimestampPadding }; if (senderHasColour) { // Background effect effectedUsername = new Container { AutoSizeAxes = Axes.Both, Masking = true, CornerRadius = 4, EdgeEffect = new EdgeEffectParameters { Roundness = 1, Radius = 1, Colour = Color4.Black.Opacity(0.3f), Offset = new Vector2(0, 1), Type = EdgeEffectType.Shadow, }, Child = new Container { AutoSizeAxes = Axes.Both, Y = 0, Masking = true, CornerRadius = 4, Children = new Drawable[] { new Box { RelativeSizeAxes = Axes.Both, Colour = usernameColour, }, new Container { AutoSizeAxes = Axes.Both, Padding = new MarginPadding { Left = 4, Right = 4, Bottom = 1, Top = -2 }, Child = username } } } }; } InternalChildren = new Drawable[] { lineHighlightBackground = new Container { Alpha = 0f, RelativeSizeAxes = Axes.Both, Colour = usernameColour.Darken(1f), CornerRadius = 2f, Masking = true, Child = new Box { RelativeSizeAxes = Axes.Both } }, new Container { Size = new Vector2(MessagePadding, TextSize), Children = new Drawable[] { timestamp = new OsuSpriteText { Shadow = false, Anchor = Anchor.CentreLeft, Origin = Anchor.CentreLeft, Font = OsuFont.GetFont(size: TextSize * 0.75f, weight: FontWeight.SemiBold, fixedWidth: true) }, new MessageSender(message.Sender) { AutoSizeAxes = Axes.Both, Origin = Anchor.TopRight, Anchor = Anchor.TopRight, Child = effectedUsername, }, } }, new Container { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Padding = new MarginPadding { Left = MessagePadding + HorizontalPadding }, Children = new Drawable[] { ContentFlow = new LinkFlowContainer(t => { t.Shadow = false; if (Message.IsAction) { t.Font = OsuFont.GetFont(italics: true); if (senderHasColour) t.Colour = Color4Extensions.FromHex(message.Sender.Colour); } t.Font = t.Font.With(size: TextSize); }) { AutoSizeAxes = Axes.Y, RelativeSizeAxes = Axes.X, } } } }; } protected override void LoadComplete() { base.LoadComplete(); updateMessageContent(); FinishTransforms(true); } /// /// Schedules a message highlight animation. /// /// /// Scheduling is required to ensure the animation doesn't play until the chat line is in view and not scrolled away. /// public void ScheduleHighlight() => Schedule(() => lineHighlightBackground.FadeTo(0.5f).FadeOut(1000, Easing.InQuint)); private void updateMessageContent() { this.FadeTo(message is LocalEchoMessage ? 0.4f : 1.0f, 500, Easing.OutQuint); timestamp.FadeTo(message is LocalEchoMessage ? 0 : 1, 500, Easing.OutQuint); timestamp.Text = $@"{message.Timestamp.LocalDateTime:HH:mm:ss}"; username.Text = $@"{message.Sender.Username}" + (senderHasColour || message.IsAction ? "" : ":"); // remove non-existent channels from the link list message.Links.RemoveAll(link => link.Action == LinkAction.OpenChannel && chatManager?.AvailableChannels.Any(c => c.Name == link.Argument.ToString()) != true); ContentFlow.Clear(); ContentFlow.AddLinks(message.DisplayContent, message.Links); } private class MessageSender : OsuClickableContainer, IHasContextMenu { private readonly APIUser sender; private Action startChatAction; [Resolved] private IAPIProvider api { get; set; } public MessageSender(APIUser sender) { this.sender = sender; } [BackgroundDependencyLoader(true)] private void load(UserProfileOverlay profile, ChannelManager chatManager) { Action = () => profile?.ShowUser(sender); startChatAction = () => chatManager?.OpenPrivateChannel(sender); } public MenuItem[] ContextMenuItems { get { if (sender.Equals(APIUser.SYSTEM_USER)) return Array.Empty(); List items = new List { new OsuMenuItem("View Profile", MenuItemType.Highlighted, Action) }; if (!sender.Equals(api.LocalUser.Value)) items.Add(new OsuMenuItem("Start Chat", MenuItemType.Standard, startChatAction)); return items.ToArray(); } } } private static readonly Color4[] username_colours = { Color4Extensions.FromHex("588c7e"), Color4Extensions.FromHex("b2a367"), Color4Extensions.FromHex("c98f65"), Color4Extensions.FromHex("bc5151"), Color4Extensions.FromHex("5c8bd6"), Color4Extensions.FromHex("7f6ab7"), Color4Extensions.FromHex("a368ad"), Color4Extensions.FromHex("aa6880"), Color4Extensions.FromHex("6fad9b"), Color4Extensions.FromHex("f2e394"), Color4Extensions.FromHex("f2ae72"), Color4Extensions.FromHex("f98f8a"), Color4Extensions.FromHex("7daef4"), Color4Extensions.FromHex("a691f2"), Color4Extensions.FromHex("c894d3"), Color4Extensions.FromHex("d895b0"), Color4Extensions.FromHex("53c4a1"), Color4Extensions.FromHex("eace5c"), Color4Extensions.FromHex("ea8c47"), Color4Extensions.FromHex("fc4f4f"), Color4Extensions.FromHex("3d94ea"), Color4Extensions.FromHex("7760ea"), Color4Extensions.FromHex("af52c6"), Color4Extensions.FromHex("e25696"), Color4Extensions.FromHex("677c66"), Color4Extensions.FromHex("9b8732"), Color4Extensions.FromHex("8c5129"), Color4Extensions.FromHex("8c3030"), Color4Extensions.FromHex("1f5d91"), Color4Extensions.FromHex("4335a5"), Color4Extensions.FromHex("812a96"), Color4Extensions.FromHex("992861"), }; } }