diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 3c5641fcd6..39c2fbfcc9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -6,6 +6,7 @@ using osu.Framework.Graphics; using osu.Game.Online.Chat; using osu.Game.Users; using osuTK; +using System; namespace osu.Game.Tests.Visual.Online { @@ -111,6 +112,13 @@ namespace osu.Game.Tests.Visual.Online Sender = longUsernameUser, Content = "Hi guys, my new username is lit!" })); + + AddStep("message with new date", () => testChannel.AddNewMessages(new Message(sequence++) + { + Sender = longUsernameUser, + Content = "Message from the future!", + Timestamp = DateTimeOffset.Now + })); } } } diff --git a/osu.Game/Online/Chat/StandAloneChatDisplay.cs b/osu.Game/Online/Chat/StandAloneChatDisplay.cs index 8f39fb9006..21d0bcc4bf 100644 --- a/osu.Game/Online/Chat/StandAloneChatDisplay.cs +++ b/osu.Game/Online/Chat/StandAloneChatDisplay.cs @@ -8,6 +8,7 @@ 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.UserInterface; using osu.Game.Overlays.Chat; using osuTK.Graphics; @@ -124,6 +125,8 @@ namespace osu.Game.Online.Chat protected override ChatLine CreateChatLine(Message m) => CreateChatLineAction(m); + protected override DaySeparator CreateDaySeparator(DateTimeOffset time) => new CustomDaySeparator(time); + public StandAloneDrawableChannel(Channel channel) : base(channel) { @@ -134,6 +137,24 @@ namespace osu.Game.Online.Chat { ChatLineFlow.Padding = new MarginPadding { Horizontal = 0 }; } + + private class CustomDaySeparator : DaySeparator + { + public CustomDaySeparator(DateTimeOffset time) + : base(time) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + Colour = colours.Yellow; + TextSize = 14; + LineHeight = 1; + Padding = new MarginPadding { Horizontal = 10 }; + Margin = new MarginPadding { Vertical = 5 }; + } + } } protected class StandAloneMessage : ChatLine diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index f831266b1b..6cdbfabe0f 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -12,15 +12,22 @@ using osu.Framework.Graphics.Containers; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Cursor; using osu.Game.Online.Chat; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics.Sprites; namespace osu.Game.Overlays.Chat { public class DrawableChannel : Container { public readonly Channel Channel; - protected ChatLineContainer ChatLineFlow; + protected FillFlowContainer ChatLineFlow; private OsuScrollContainer scroll; + [Resolved] + private OsuColour colours { get; set; } + public DrawableChannel(Channel channel) { Channel = channel; @@ -40,7 +47,7 @@ namespace osu.Game.Overlays.Chat // Some chat lines have effects that slightly protrude to the bottom, // which we do not want to mask away, hence the padding. Padding = new MarginPadding { Bottom = 5 }, - Child = ChatLineFlow = new ChatLineContainer + Child = ChatLineFlow = new FillFlowContainer { Padding = new MarginPadding { Left = 20, Right = 20 }, RelativeSizeAxes = Axes.X, @@ -74,17 +81,32 @@ namespace osu.Game.Overlays.Chat protected virtual ChatLine CreateChatLine(Message m) => new ChatLine(m); + protected virtual DaySeparator CreateDaySeparator(DateTimeOffset time) => new DaySeparator(time) + { + Margin = new MarginPadding { Vertical = 10 }, + Colour = colours.ChatBlue.Lighten(0.7f), + }; + private void newMessagesArrived(IEnumerable newMessages) { // Add up to last Channel.MAX_HISTORY messages var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MaxHistory)); - ChatLineFlow.AddRange(displayMessages.Select(CreateChatLine)); + Message lastMessage = chatLines.LastOrDefault()?.Message; - if (scroll.IsScrolledToEnd(10) || !ChatLineFlow.Children.Any() || newMessages.Any(m => m is LocalMessage)) + foreach (var message in displayMessages) + { + if (lastMessage == null || lastMessage.Timestamp.ToLocalTime().Date != message.Timestamp.ToLocalTime().Date) + ChatLineFlow.Add(CreateDaySeparator(message.Timestamp)); + + ChatLineFlow.Add(CreateChatLine(message)); + lastMessage = message; + } + + if (scroll.IsScrolledToEnd(10) || !chatLines.Any() || newMessages.Any(m => m is LocalMessage)) scrollToEnd(); - var staleMessages = ChatLineFlow.Children.Where(c => c.LifetimeEnd == double.MaxValue).ToArray(); + var staleMessages = chatLines.Where(c => c.LifetimeEnd == double.MaxValue).ToArray(); int count = staleMessages.Length - Channel.MaxHistory; for (int i = 0; i < count; i++) @@ -98,7 +120,7 @@ namespace osu.Game.Overlays.Chat private void pendingMessageResolved(Message existing, Message updated) { - var found = ChatLineFlow.Children.LastOrDefault(c => c.Message == existing); + var found = chatLines.LastOrDefault(c => c.Message == existing); if (found != null) { @@ -112,19 +134,74 @@ namespace osu.Game.Overlays.Chat private void messageRemoved(Message removed) { - ChatLineFlow.Children.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); + chatLines.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); } + private IEnumerable chatLines => ChatLineFlow.Children.OfType(); + private void scrollToEnd() => ScheduleAfterChildren(() => scroll.ScrollToEnd()); - protected class ChatLineContainer : FillFlowContainer + protected class DaySeparator : Container { - protected override int Compare(Drawable x, Drawable y) + public float TextSize { - var xC = (ChatLine)x; - var yC = (ChatLine)y; + get => text.Font.Size; + set => text.Font = text.Font.With(size: value); + } - return xC.Message.CompareTo(yC.Message); + private float lineHeight = 2; + + public float LineHeight + { + get => lineHeight; + set => lineHeight = leftBox.Height = rightBox.Height = value; + } + + private readonly SpriteText text; + private readonly Box leftBox; + private readonly Box rightBox; + + public DaySeparator(DateTimeOffset time) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + Child = new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + }, + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize), }, + Content = new[] + { + new Drawable[] + { + leftBox = new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = lineHeight, + }, + text = new SpriteText + { + Margin = new MarginPadding { Horizontal = 10 }, + Text = time.ToLocalTime().ToString("dd MMM yyyy"), + }, + rightBox = new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Height = lineHeight, + }, + } + } + }; } } }