From 30b38345aad33a37419f32528cc1ff831e70699a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Mar 2022 23:21:29 +0300 Subject: [PATCH 01/29] Add ability to highlight chat lines --- osu.Game/Overlays/Chat/ChatLine.cs | 43 +++++++++++++++++------ osu.Game/Overlays/Chat/DrawableChannel.cs | 22 ++++++++++++ 2 files changed, 54 insertions(+), 11 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 87d1b1a3ad..e7b349b313 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -42,7 +42,9 @@ namespace osu.Game.Overlays.Chat protected virtual float TextSize => 20; - private Color4 customUsernameColour; + private Container lineBackground; + + private Color4 usernameColour; private OsuSpriteText timestamp; @@ -78,19 +80,22 @@ namespace osu.Game.Overlays.Chat } } - private bool senderHasBackground => !string.IsNullOrEmpty(message.Sender.Colour); + private bool senderHasColour => !string.IsNullOrEmpty(message.Sender.Colour); + + [Resolved] + private OsuColour colours { get; set; } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load() { - customUsernameColour = colours.ChatBlue; - - bool hasBackground = senderHasBackground; + usernameColour = senderHasColour + ? Color4Extensions.FromHex(message.Sender.Colour) + : username_colours[message.Sender.Id % username_colours.Length]; Drawable effectedUsername = username = new OsuSpriteText { Shadow = false, - Colour = hasBackground ? customUsernameColour : username_colours[message.Sender.Id % username_colours.Length], + Colour = senderHasColour ? colours.ChatBlue : usernameColour, Truncate = true, EllipsisString = "… :", Font = OsuFont.GetFont(size: TextSize, weight: FontWeight.Bold, italics: true), @@ -99,7 +104,7 @@ namespace osu.Game.Overlays.Chat MaxWidth = MessagePadding - TimestampPadding }; - if (hasBackground) + if (senderHasColour) { // Background effect effectedUsername = new Container @@ -126,7 +131,7 @@ namespace osu.Game.Overlays.Chat new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4Extensions.FromHex(message.Sender.Colour), + Colour = usernameColour, }, new Container { @@ -141,6 +146,14 @@ namespace osu.Game.Overlays.Chat InternalChildren = new Drawable[] { + lineBackground = new Container + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.0f), + CornerRadius = 2f, + Masking = true, + Child = new Box { RelativeSizeAxes = Axes.Both } + }, new Container { Size = new Vector2(MessagePadding, TextSize), @@ -177,7 +190,7 @@ namespace osu.Game.Overlays.Chat { t.Font = OsuFont.GetFont(italics: true); - if (senderHasBackground) + if (senderHasColour) t.Colour = Color4Extensions.FromHex(message.Sender.Colour); } @@ -200,13 +213,21 @@ namespace osu.Game.Overlays.Chat 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(() => lineBackground.FlashColour(usernameColour.Darken(1f).Opacity(0.5f), 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}" + (senderHasBackground || message.IsAction ? "" : ":"); + 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); diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 6220beeb82..cca089873f 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -79,6 +79,28 @@ namespace osu.Game.Overlays.Chat Channel.PendingMessageResolved += pendingMessageResolved; } + /// + /// Highlights a specific message in this drawable channel. + /// + /// The message to highlight. + public void HighlightMessage(Message message) + { + if (IsLoaded) + highlightMessage(message); + else + Schedule(highlightMessage, message); + } + + private void highlightMessage(Message message) + { + var chatLine = chatLines.SingleOrDefault(c => c.Message == message); + if (chatLine == null) + return; + + scroll.ScrollIntoView(chatLine); + chatLine.ScheduleHighlight(); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From cb2133944dfdd6cd212fbbfb8385f4e97fa51ce1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Mar 2022 23:27:09 +0300 Subject: [PATCH 02/29] Add test coverage for channel message highlighting --- .../Online/TestSceneStandAloneChatDisplay.cs | 147 +++++++++++++----- 1 file changed, 104 insertions(+), 43 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index a21647712d..89ee8b03e9 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -9,6 +9,8 @@ using System; using System.Linq; using NUnit.Framework; using osu.Framework.Graphics.Containers; +using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Online.API.Requests.Responses; using osu.Game.Overlays.Chat; using osuTK.Input; @@ -107,49 +109,7 @@ namespace osu.Game.Tests.Visual.Online [Test] public void TestManyMessages() { - AddStep("message from admin", () => testChannel.AddNewMessages(new Message(messageIdSequence++) - { - Sender = admin, - Content = "I am a wang!" - })); - - AddStep("message from team red", () => testChannel.AddNewMessages(new Message(messageIdSequence++) - { - Sender = redUser, - Content = "I am team red." - })); - - AddStep("message from team red", () => testChannel.AddNewMessages(new Message(messageIdSequence++) - { - Sender = redUser, - Content = "I plan to win!" - })); - - AddStep("message from team blue", () => testChannel.AddNewMessages(new Message(messageIdSequence++) - { - Sender = blueUser, - Content = "Not on my watch. Prepare to eat saaaaaaaaaand. Lots and lots of saaaaaaand." - })); - - AddStep("message from admin", () => testChannel.AddNewMessages(new Message(messageIdSequence++) - { - Sender = admin, - Content = "Okay okay, calm down guys. Let's do this!" - })); - - AddStep("message from long username", () => testChannel.AddNewMessages(new Message(messageIdSequence++) - { - Sender = longUsernameUser, - Content = "Hi guys, my new username is lit!" - })); - - AddStep("message with new date", () => testChannel.AddNewMessages(new Message(messageIdSequence++) - { - Sender = longUsernameUser, - Content = "Message from the future!", - Timestamp = DateTimeOffset.Now - })); - + sendRegularMessages(); checkScrolledToBottom(); const int messages_per_call = 10; @@ -182,6 +142,61 @@ namespace osu.Game.Tests.Visual.Online checkScrolledToBottom(); } + [Test] + public void TestMessageHighlighting() + { + Message highlighted = null; + + sendRegularMessages(); + + AddStep("highlight first message", () => + { + highlighted = testChannel.Messages[0]; + chatDisplay.DrawableChannel.HighlightMessage(highlighted); + }); + + AddUntilStep("chat scrolled to first message", () => + { + var line = chatDisplay.ChildrenOfType().Single(c => c.Message == highlighted); + return chatDisplay.ScrollContainer.ScreenSpaceDrawQuad.Contains(line.ScreenSpaceDrawQuad.Centre); + }); + + sendMessage(); + checkNotScrolledToBottom(); + + AddStep("highlight last message", () => + { + highlighted = testChannel.Messages[^1]; + chatDisplay.DrawableChannel.HighlightMessage(highlighted); + }); + + AddUntilStep("chat scrolled to last message", () => + { + var line = chatDisplay.ChildrenOfType().Single(c => c.Message == highlighted); + return chatDisplay.ScrollContainer.ScreenSpaceDrawQuad.Contains(line.ScreenSpaceDrawQuad.Centre); + }); + + sendMessage(); + checkScrolledToBottom(); + + AddRepeatStep("highlight other random messages", () => + { + highlighted = testChannel.Messages[RNG.Next(0, testChannel.Messages.Count - 1)]; + chatDisplay.DrawableChannel.HighlightMessage(highlighted); + }, 10); + } + + [Test] + public void TestMessageHighlightingOnFilledChat() + { + fillChat(); + + AddRepeatStep("highlight random messages", () => + { + chatDisplay.DrawableChannel.HighlightMessage(testChannel.Messages[RNG.Next(0, testChannel.Messages.Count - 1)]); + }, 10); + } + /// /// Tests that when a message gets wrapped by the chat display getting contracted while scrolled to bottom, the chat will still keep scrolling down. /// @@ -321,6 +336,52 @@ namespace osu.Game.Tests.Visual.Online })); } + private void sendRegularMessages() + { + AddStep("message from admin", () => testChannel.AddNewMessages(new Message(messageIdSequence++) + { + Sender = admin, + Content = "I am a wang!" + })); + + AddStep("message from team red", () => testChannel.AddNewMessages(new Message(messageIdSequence++) + { + Sender = redUser, + Content = "I am team red." + })); + + AddStep("message from team red", () => testChannel.AddNewMessages(new Message(messageIdSequence++) + { + Sender = redUser, + Content = "I plan to win!" + })); + + AddStep("message from team blue", () => testChannel.AddNewMessages(new Message(messageIdSequence++) + { + Sender = blueUser, + Content = "Not on my watch. Prepare to eat saaaaaaaaaand. Lots and lots of saaaaaaand." + })); + + AddStep("message from admin", () => testChannel.AddNewMessages(new Message(messageIdSequence++) + { + Sender = admin, + Content = "Okay okay, calm down guys. Let's do this!" + })); + + AddStep("message from long username", () => testChannel.AddNewMessages(new Message(messageIdSequence++) + { + Sender = longUsernameUser, + Content = "Hi guys, my new username is lit!" + })); + + AddStep("message with new date", () => testChannel.AddNewMessages(new Message(messageIdSequence++) + { + Sender = longUsernameUser, + Content = "Message from the future!", + Timestamp = DateTimeOffset.Now + })); + } + private void checkScrolledToBottom() => AddUntilStep("is scrolled to bottom", () => chatDisplay.ScrolledToBottom); From f4fa80c1e3bd9ae5056185a087f7ee4d65c071c8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Mar 2022 23:28:40 +0300 Subject: [PATCH 03/29] Add support to highlight messages in chat overlay --- osu.Game/Overlays/ChatOverlay.cs | 41 ++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index fde9d28b43..d642bf3bb2 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -4,6 +4,7 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; +using JetBrains.Annotations; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; @@ -46,6 +47,8 @@ namespace osu.Game.Overlays private Container currentChannelContainer; + private DrawableChannel currentDrawableChannel => currentChannelContainer.SingleOrDefault(); + private readonly List loadedChannels = new List(); private LoadingSpinner loading; @@ -249,6 +252,9 @@ namespace osu.Game.Overlays private Bindable currentChannel; + [CanBeNull] + private Message messagePendingHighlight; + private void currentChannelChanged(ValueChangedEvent e) { if (e.NewValue == null) @@ -290,12 +296,24 @@ namespace osu.Game.Overlays currentChannelContainer.Clear(false); currentChannelContainer.Add(loaded); currentChannelContainer.FadeIn(500, Easing.OutQuint); + + if (messagePendingHighlight != null) + { + tryHighlightMessage(messagePendingHighlight); + messagePendingHighlight = null; + } }); } else { currentChannelContainer.Clear(false); currentChannelContainer.Add(loaded); + + if (messagePendingHighlight != null) + { + tryHighlightMessage(messagePendingHighlight); + messagePendingHighlight = null; + } } // mark channel as read when channel switched @@ -303,6 +321,29 @@ namespace osu.Game.Overlays channelManager.MarkChannelAsRead(e.NewValue); } + /// + /// Highlights a certain message in the specified channel. + /// + /// The message to highlight. + public void HighlightMessage(Message message) + { + if (currentDrawableChannel?.Channel.Id == message.ChannelId) + tryHighlightMessage(message); + else + { + messagePendingHighlight = message; + currentChannel.Value = channelManager.JoinedChannels.Single(c => c.Id == message.ChannelId); + } + } + + private void tryHighlightMessage(Message message) + { + if (message.ChannelId != currentChannel.Value.Id) + return; + + currentDrawableChannel.HighlightMessage(message); + } + private float startDragChatHeight; private bool isDragging; From 741702549b6d45e8e7abb52c7fbd28b52057148d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Mar 2022 23:31:03 +0300 Subject: [PATCH 04/29] Add test coverage for chat overlay message highlighting --- .../Visual/Online/TestSceneChatOverlay.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 14f32df653..a91cd53ec1 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -412,6 +412,59 @@ namespace osu.Game.Tests.Visual.Online AddAssert("channel left", () => !channelManager.JoinedChannels.Contains(multiplayerChannel)); } + [Test] + public void TestHighlightOnCurrentChannel() + { + Message message = null; + + AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); + AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); + + AddStep("Send message in channel 1", () => + { + channel1.AddNewMessages(message = new Message + { + ChannelId = channel1.Id, + Content = "Message to highlight!", + Timestamp = DateTimeOffset.Now, + Sender = new APIUser + { + Id = 2, + Username = "Someone", + } + }); + }); + + AddStep("Highlight message", () => chatOverlay.HighlightMessage(message)); + } + + [Test] + public void TestHighlightOnAnotherChannel() + { + Message message = null; + + AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); + AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); + + AddStep("Join channel 2", () => channelManager.JoinChannel(channel2)); + AddStep("Send message in channel 2", () => + { + channel2.AddNewMessages(message = new Message + { + ChannelId = channel2.Id, + Content = "Message to highlight!", + Timestamp = DateTimeOffset.Now, + Sender = new APIUser + { + Id = 2, + Username = "Someone", + } + }); + }); + + AddStep("Highlight message", () => chatOverlay.HighlightMessage(message)); + } + private void pressChannelHotkey(int number) { var channelKey = Key.Number0 + number; From 32d242dd627412a38aff0340ae6ba8039e68d84f Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 4 Mar 2022 23:31:32 +0300 Subject: [PATCH 05/29] Hook up message notifications to chat message highlighting logic --- osu.Game/Online/Chat/MessageNotifier.cs | 28 ++++++++++++------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 2c99e9f9b9..de3b9f71ad 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -99,7 +99,7 @@ namespace osu.Game.Online.Chat if (checkForPMs(channel, message)) continue; - checkForMentions(channel, message); + checkForMentions(message); } } @@ -114,15 +114,15 @@ namespace osu.Game.Online.Chat if (!notifyOnPrivateMessage.Value || channel.Type != ChannelType.PM) return false; - notifications.Post(new PrivateMessageNotification(message.Sender.Username, channel)); + notifications.Post(new PrivateMessageNotification(message)); return true; } - private void checkForMentions(Channel channel, Message message) + private void checkForMentions(Message message) { if (!notifyOnUsername.Value || !CheckContainsUsername(message.Content, localUser.Value.Username)) return; - notifications.Post(new MentionNotification(message.Sender.Username, channel)); + notifications.Post(new MentionNotification(message)); } /// @@ -138,32 +138,32 @@ namespace osu.Game.Online.Chat public class PrivateMessageNotification : OpenChannelNotification { - public PrivateMessageNotification(string username, Channel channel) - : base(channel) + public PrivateMessageNotification(Message message) + : base(message) { Icon = FontAwesome.Solid.Envelope; - Text = $"You received a private message from '{username}'. Click to read it!"; + Text = $"You received a private message from '{message.Sender.Username}'. Click to read it!"; } } public class MentionNotification : OpenChannelNotification { - public MentionNotification(string username, Channel channel) - : base(channel) + public MentionNotification(Message message) + : base(message) { Icon = FontAwesome.Solid.At; - Text = $"Your name was mentioned in chat by '{username}'. Click to find out why!"; + Text = $"Your name was mentioned in chat by '{message.Sender.Username}'. Click to find out why!"; } } public abstract class OpenChannelNotification : SimpleNotification { - protected OpenChannelNotification(Channel channel) + protected OpenChannelNotification(Message message) { - this.channel = channel; + this.message = message; } - private readonly Channel channel; + private readonly Message message; public override bool IsImportant => false; @@ -175,8 +175,8 @@ namespace osu.Game.Online.Chat Activated = delegate { notificationOverlay.Hide(); + chatOverlay.HighlightMessage(message); chatOverlay.Show(); - channelManager.CurrentChannel.Value = channel; return true; }; From 22a2ef42c5e855e95df6970eb3659ac6c43ee0f0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 8 Mar 2022 01:22:47 +0300 Subject: [PATCH 06/29] Check channel ID on message highlight using `currentDrawableChannel` --- osu.Game/Overlays/ChatOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index d642bf3bb2..39513f7963 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -338,7 +338,7 @@ namespace osu.Game.Overlays private void tryHighlightMessage(Message message) { - if (message.ChannelId != currentChannel.Value.Id) + if (message.ChannelId != currentDrawableChannel.Channel.Id) return; currentDrawableChannel.HighlightMessage(message); From 5764c53c1789acbdf37e129ecd0a490c698c589d Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 8 Mar 2022 01:22:58 +0300 Subject: [PATCH 07/29] OpenChannelNotification -> HighlightMessageNotification --- osu.Game/Online/Chat/MessageNotifier.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index de3b9f71ad..90b6d927c7 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -136,7 +136,7 @@ namespace osu.Game.Online.Chat return Regex.IsMatch(message, $@"(^|\W)({fullName}|{underscoreName})($|\W)", RegexOptions.IgnoreCase); } - public class PrivateMessageNotification : OpenChannelNotification + public class PrivateMessageNotification : HighlightMessageNotification { public PrivateMessageNotification(Message message) : base(message) @@ -146,7 +146,7 @@ namespace osu.Game.Online.Chat } } - public class MentionNotification : OpenChannelNotification + public class MentionNotification : HighlightMessageNotification { public MentionNotification(Message message) : base(message) @@ -156,9 +156,9 @@ namespace osu.Game.Online.Chat } } - public abstract class OpenChannelNotification : SimpleNotification + public abstract class HighlightMessageNotification : SimpleNotification { - protected OpenChannelNotification(Message message) + protected HighlightMessageNotification(Message message) { this.message = message; } @@ -168,7 +168,7 @@ namespace osu.Game.Online.Chat public override bool IsImportant => false; [BackgroundDependencyLoader] - private void load(OsuColour colours, ChatOverlay chatOverlay, NotificationOverlay notificationOverlay, ChannelManager channelManager) + private void load(OsuColour colours, ChatOverlay chatOverlay, NotificationOverlay notificationOverlay) { IconBackground.Colour = colours.PurpleDark; From 7f47be4680ee16ca16d6bd59185852623306c971 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 8 Mar 2022 03:19:35 +0300 Subject: [PATCH 08/29] Refactor message highlighting logic to rely on a `Channel` data bindable --- .../Online/TestSceneStandAloneChatDisplay.cs | 8 +- osu.Game/Online/Chat/Channel.cs | 7 ++ osu.Game/Overlays/Chat/DrawableChannel.cs | 95 ++++++++++--------- osu.Game/Overlays/ChatOverlay.cs | 33 +------ 4 files changed, 63 insertions(+), 80 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 89ee8b03e9..c9837be934 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -152,7 +152,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("highlight first message", () => { highlighted = testChannel.Messages[0]; - chatDisplay.DrawableChannel.HighlightMessage(highlighted); + chatDisplay.DrawableChannel.Channel.HighlightedMessage.Value = highlighted; }); AddUntilStep("chat scrolled to first message", () => @@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("highlight last message", () => { highlighted = testChannel.Messages[^1]; - chatDisplay.DrawableChannel.HighlightMessage(highlighted); + chatDisplay.DrawableChannel.Channel.HighlightedMessage.Value = highlighted; }); AddUntilStep("chat scrolled to last message", () => @@ -182,7 +182,7 @@ namespace osu.Game.Tests.Visual.Online AddRepeatStep("highlight other random messages", () => { highlighted = testChannel.Messages[RNG.Next(0, testChannel.Messages.Count - 1)]; - chatDisplay.DrawableChannel.HighlightMessage(highlighted); + chatDisplay.DrawableChannel.Channel.HighlightedMessage.Value = highlighted; }, 10); } @@ -193,7 +193,7 @@ namespace osu.Game.Tests.Visual.Online AddRepeatStep("highlight random messages", () => { - chatDisplay.DrawableChannel.HighlightMessage(testChannel.Messages[RNG.Next(0, testChannel.Messages.Count - 1)]); + chatDisplay.DrawableChannel.Channel.HighlightedMessage.Value = testChannel.Messages[RNG.Next(0, testChannel.Messages.Count - 1)]; }, 10); } diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index 9cbb2f37e4..a37d3084f0 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -9,6 +9,7 @@ using Newtonsoft.Json; using osu.Framework.Bindables; using osu.Framework.Lists; using osu.Game.Online.API.Requests.Responses; +using osu.Game.Overlays.Chat; namespace osu.Game.Online.Chat { @@ -89,6 +90,12 @@ namespace osu.Game.Online.Chat /// public Bindable Joined = new Bindable(); + /// + /// Signals if there is a message to highlight. + /// This is automatically cleared by the associated after highlighting. + /// + public Bindable HighlightedMessage = new Bindable(); + [JsonConstructor] public Channel() { diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index cca089873f..6ff7cccd65 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -6,6 +6,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -48,6 +49,8 @@ namespace osu.Game.Overlays.Chat RelativeSizeAxes = Axes.Both; } + private Bindable highlightedMessage; + [BackgroundDependencyLoader] private void load() { @@ -71,34 +74,34 @@ namespace osu.Game.Overlays.Chat } }, }; + } - newMessagesArrived(Channel.Messages); + protected override void LoadComplete() + { + base.LoadComplete(); + + addChatLines(Channel.Messages); Channel.NewMessagesArrived += newMessagesArrived; Channel.MessageRemoved += messageRemoved; Channel.PendingMessageResolved += pendingMessageResolved; - } - /// - /// Highlights a specific message in this drawable channel. - /// - /// The message to highlight. - public void HighlightMessage(Message message) - { - if (IsLoaded) - highlightMessage(message); - else - Schedule(highlightMessage, message); - } + highlightedMessage = Channel.HighlightedMessage.GetBoundCopy(); + highlightedMessage.BindValueChanged(m => + { + if (m.NewValue == null) + return; - private void highlightMessage(Message message) - { - var chatLine = chatLines.SingleOrDefault(c => c.Message == message); - if (chatLine == null) - return; + var chatLine = chatLines.SingleOrDefault(c => c.Message == m.NewValue); - scroll.ScrollIntoView(chatLine); - chatLine.ScheduleHighlight(); + if (chatLine != null) + { + scroll.ScrollIntoView(chatLine); + chatLine.ScheduleHighlight(); + } + + highlightedMessage.Value = null; + }, true); } protected override void Dispose(bool isDisposing) @@ -118,18 +121,39 @@ namespace osu.Game.Overlays.Chat Colour = colours.ChatBlue.Lighten(0.7f), }; - private void newMessagesArrived(IEnumerable newMessages) => Schedule(() => + private void newMessagesArrived(IEnumerable newMessages) => Schedule(addChatLines, newMessages); + + private void pendingMessageResolved(Message existing, Message updated) => Schedule(() => { - if (newMessages.Min(m => m.Id) < chatLines.Max(c => c.Message.Id)) + var found = chatLines.LastOrDefault(c => c.Message == existing); + + if (found != null) + { + Trace.Assert(updated.Id.HasValue, "An updated message was returned with no ID."); + + ChatLineFlow.Remove(found); + found.Message = updated; + ChatLineFlow.Add(found); + } + }); + + private void messageRemoved(Message removed) => Schedule(() => + { + chatLines.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); + }); + + private void addChatLines(IEnumerable messages) + { + if (messages.Min(m => m.Id) < chatLines.Max(c => c.Message.Id)) { // there is a case (on initial population) that we may receive past messages and need to reorder. // easiest way is to just combine messages and recreate drawables (less worrying about day separators etc.) - newMessages = newMessages.Concat(chatLines.Select(c => c.Message)).OrderBy(m => m.Id).ToList(); + messages = messages.Concat(chatLines.Select(c => c.Message)).OrderBy(m => m.Id).ToList(); ChatLineFlow.Clear(); } // Add up to last Channel.MAX_HISTORY messages - var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MAX_HISTORY)); + var displayMessages = messages.Skip(Math.Max(0, messages.Count() - Channel.MAX_HISTORY)); Message lastMessage = chatLines.LastOrDefault()?.Message; @@ -168,28 +192,9 @@ namespace osu.Game.Overlays.Chat // due to the scroll adjusts from old messages removal above, a scroll-to-end must be enforced, // to avoid making the container think the user has scrolled back up and unwantedly disable auto-scrolling. - if (newMessages.Any(m => m is LocalMessage)) + if (messages.Any(m => m is LocalMessage)) scroll.ScrollToEnd(); - }); - - private void pendingMessageResolved(Message existing, Message updated) => Schedule(() => - { - var found = chatLines.LastOrDefault(c => c.Message == existing); - - if (found != null) - { - Trace.Assert(updated.Id.HasValue, "An updated message was returned with no ID."); - - ChatLineFlow.Remove(found); - found.Message = updated; - ChatLineFlow.Add(found); - } - }); - - private void messageRemoved(Message removed) => Schedule(() => - { - chatLines.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); - }); + } private IEnumerable chatLines => ChatLineFlow.Children.OfType(); diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index 39513f7963..d5e8b209f0 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -4,7 +4,6 @@ using System.Collections.Generic; using System.Collections.Specialized; using System.Linq; -using JetBrains.Annotations; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; @@ -47,8 +46,6 @@ namespace osu.Game.Overlays private Container currentChannelContainer; - private DrawableChannel currentDrawableChannel => currentChannelContainer.SingleOrDefault(); - private readonly List loadedChannels = new List(); private LoadingSpinner loading; @@ -252,9 +249,6 @@ namespace osu.Game.Overlays private Bindable currentChannel; - [CanBeNull] - private Message messagePendingHighlight; - private void currentChannelChanged(ValueChangedEvent e) { if (e.NewValue == null) @@ -296,24 +290,12 @@ namespace osu.Game.Overlays currentChannelContainer.Clear(false); currentChannelContainer.Add(loaded); currentChannelContainer.FadeIn(500, Easing.OutQuint); - - if (messagePendingHighlight != null) - { - tryHighlightMessage(messagePendingHighlight); - messagePendingHighlight = null; - } }); } else { currentChannelContainer.Clear(false); currentChannelContainer.Add(loaded); - - if (messagePendingHighlight != null) - { - tryHighlightMessage(messagePendingHighlight); - messagePendingHighlight = null; - } } // mark channel as read when channel switched @@ -327,21 +309,10 @@ namespace osu.Game.Overlays /// The message to highlight. public void HighlightMessage(Message message) { - if (currentDrawableChannel?.Channel.Id == message.ChannelId) - tryHighlightMessage(message); - else - { - messagePendingHighlight = message; + if (currentChannel.Value.Id != message.ChannelId) currentChannel.Value = channelManager.JoinedChannels.Single(c => c.Id == message.ChannelId); - } - } - private void tryHighlightMessage(Message message) - { - if (message.ChannelId != currentDrawableChannel.Channel.Id) - return; - - currentDrawableChannel.HighlightMessage(message); + currentChannel.Value.HighlightedMessage.Value = message; } private float startDragChatHeight; From f8e5570e4152b2ac52c859f086a73abb8ec0f3f0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 8 Mar 2022 03:20:20 +0300 Subject: [PATCH 09/29] Fix `Message` equality not passing on equal references --- osu.Game/Online/Chat/Message.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs index dcd15f9028..3db63c3fca 100644 --- a/osu.Game/Online/Chat/Message.cs +++ b/osu.Game/Online/Chat/Message.cs @@ -59,7 +59,13 @@ namespace osu.Game.Online.Chat return Id.Value.CompareTo(other.Id.Value); } - public virtual bool Equals(Message other) => Id.HasValue && Id == other?.Id; + public virtual bool Equals(Message other) + { + if (ReferenceEquals(null, other)) return false; + if (ReferenceEquals(this, other)) return true; + + return Id.HasValue && Id == other?.Id; + } // ReSharper disable once ImpureMethodCallOnReadonlyValueField public override int GetHashCode() => Id.GetHashCode(); From f64586995888b1b2d05a9e586d9dd888107954c1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 8 Mar 2022 03:42:17 +0300 Subject: [PATCH 10/29] Update `ChannelManager.CurrentChannel` directly to handle non-loaded chat scenario `currentChannel` gets instantiated once the chat overlay is open, while `HighlightMessage` could be called while the chat overlay has never been open. This will all be rewritten with the new chat overlay design anyways, so should be fine for now. --- osu.Game/Overlays/ChatOverlay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index d5e8b209f0..d6a1b23c46 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -309,10 +309,10 @@ namespace osu.Game.Overlays /// The message to highlight. public void HighlightMessage(Message message) { - if (currentChannel.Value.Id != message.ChannelId) - currentChannel.Value = channelManager.JoinedChannels.Single(c => c.Id == message.ChannelId); + if (channelManager.CurrentChannel.Value.Id != message.ChannelId) + channelManager.CurrentChannel.Value = channelManager.JoinedChannels.Single(c => c.Id == message.ChannelId); - currentChannel.Value.HighlightedMessage.Value = message; + channelManager.CurrentChannel.Value.HighlightedMessage.Value = message; } private float startDragChatHeight; From d74064b94b93d229e39b4c2ebc6161bb8d5ff000 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 8 Mar 2022 03:56:27 +0300 Subject: [PATCH 11/29] Use `Equals` instead of reference equality operator --- osu.Game/Overlays/Chat/DrawableChannel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 6ff7cccd65..a73e61c52b 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -92,7 +92,7 @@ namespace osu.Game.Overlays.Chat if (m.NewValue == null) return; - var chatLine = chatLines.SingleOrDefault(c => c.Message == m.NewValue); + var chatLine = chatLines.SingleOrDefault(c => c.Message.Equals(m.NewValue)); if (chatLine != null) { From 5e0882df8de12a0229f6d0c39910ebd6ebb1b94b Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 8 Mar 2022 04:00:11 +0300 Subject: [PATCH 12/29] Simplify message highlighting transforms --- osu.Game/Overlays/Chat/ChatLine.cs | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index e7b349b313..97fda80492 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -42,7 +42,7 @@ namespace osu.Game.Overlays.Chat protected virtual float TextSize => 20; - private Container lineBackground; + private Container lineHighlightBackground; private Color4 usernameColour; @@ -146,10 +146,11 @@ namespace osu.Game.Overlays.Chat InternalChildren = new Drawable[] { - lineBackground = new Container + lineHighlightBackground = new Container { + Alpha = 0f, RelativeSizeAxes = Axes.Both, - Colour = Color4.Black.Opacity(0.0f), + Colour = usernameColour.Darken(1f), CornerRadius = 2f, Masking = true, Child = new Box { RelativeSizeAxes = Axes.Both } @@ -219,7 +220,7 @@ namespace osu.Game.Overlays.Chat /// /// 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(() => lineBackground.FlashColour(usernameColour.Darken(1f).Opacity(0.5f), 1000, Easing.InQuint)); + public void ScheduleHighlight() => Schedule(() => lineHighlightBackground.FadeTo(0.5f).FadeOut(1000, Easing.InQuint)); private void updateMessageContent() { From c867068cae9fbf589327ed4616939b1e91c1a0a7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Mar 2022 21:15:14 +0900 Subject: [PATCH 13/29] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 5b26b8f36e..c2788f9a48 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -52,7 +52,7 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index d86fbc693e..14cb751958 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -36,7 +36,7 @@ runtime; build; native; contentfiles; analyzers; buildtransitive - + diff --git a/osu.iOS.props b/osu.iOS.props index c37692f0d8..e485c69096 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -61,7 +61,7 @@ - + @@ -84,7 +84,7 @@ - + From 512536f5fe3465b329c1ccb6f7a2a7e23d8d6a65 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 8 Mar 2022 23:25:51 +0900 Subject: [PATCH 14/29] Fix unconditional null in `Equals` implementation --- osu.Game/Online/Chat/Message.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs index 3db63c3fca..ad004e2881 100644 --- a/osu.Game/Online/Chat/Message.cs +++ b/osu.Game/Online/Chat/Message.cs @@ -64,7 +64,7 @@ namespace osu.Game.Online.Chat if (ReferenceEquals(null, other)) return false; if (ReferenceEquals(this, other)) return true; - return Id.HasValue && Id == other?.Id; + return Id.HasValue && Id == other.Id; } // ReSharper disable once ImpureMethodCallOnReadonlyValueField From 2d8983383a02a830b4e1d84af34c795d077eb562 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Tue, 8 Mar 2022 04:43:40 +0300 Subject: [PATCH 15/29] Revert `newMessagesArrived` changes and always schedule highlight In the case a message arrives and the chat overlay is hidden, clicking on the mention notification will not work as the `HighlightedMessage` bindable callback will execute before the scheduled `newMessagesArrived` logic (which was hanging since the message arrived until the chat overlay became open because of the notification). Simplify things by always scheduling the `HighlightedMessage` bindable callback. --- osu.Game/Overlays/Chat/DrawableChannel.cs | 82 ++++++++++++----------- 1 file changed, 42 insertions(+), 40 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index a73e61c52b..0cc3260e60 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -74,33 +74,37 @@ namespace osu.Game.Overlays.Chat } }, }; + + newMessagesArrived(Channel.Messages); + + Channel.NewMessagesArrived += newMessagesArrived; + Channel.MessageRemoved += messageRemoved; + Channel.PendingMessageResolved += pendingMessageResolved; } protected override void LoadComplete() { base.LoadComplete(); - addChatLines(Channel.Messages); - - Channel.NewMessagesArrived += newMessagesArrived; - Channel.MessageRemoved += messageRemoved; - Channel.PendingMessageResolved += pendingMessageResolved; - highlightedMessage = Channel.HighlightedMessage.GetBoundCopy(); highlightedMessage.BindValueChanged(m => { if (m.NewValue == null) return; - var chatLine = chatLines.SingleOrDefault(c => c.Message.Equals(m.NewValue)); - - if (chatLine != null) + // schedule highlight to ensure it performs after any pending "newMessagesArrived" calls. + Schedule(() => { - scroll.ScrollIntoView(chatLine); - chatLine.ScheduleHighlight(); - } + var chatLine = chatLines.SingleOrDefault(c => c.Message.Equals(m.NewValue)); - highlightedMessage.Value = null; + if (chatLine != null) + { + scroll.ScrollIntoView(chatLine); + chatLine.ScheduleHighlight(); + } + + highlightedMessage.Value = null; + }); }, true); } @@ -121,39 +125,18 @@ namespace osu.Game.Overlays.Chat Colour = colours.ChatBlue.Lighten(0.7f), }; - private void newMessagesArrived(IEnumerable newMessages) => Schedule(addChatLines, newMessages); - - private void pendingMessageResolved(Message existing, Message updated) => Schedule(() => + private void newMessagesArrived(IEnumerable newMessages) => Schedule(() => { - var found = chatLines.LastOrDefault(c => c.Message == existing); - - if (found != null) - { - Trace.Assert(updated.Id.HasValue, "An updated message was returned with no ID."); - - ChatLineFlow.Remove(found); - found.Message = updated; - ChatLineFlow.Add(found); - } - }); - - private void messageRemoved(Message removed) => Schedule(() => - { - chatLines.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); - }); - - private void addChatLines(IEnumerable messages) - { - if (messages.Min(m => m.Id) < chatLines.Max(c => c.Message.Id)) + if (newMessages.Min(m => m.Id) < chatLines.Max(c => c.Message.Id)) { // there is a case (on initial population) that we may receive past messages and need to reorder. // easiest way is to just combine messages and recreate drawables (less worrying about day separators etc.) - messages = messages.Concat(chatLines.Select(c => c.Message)).OrderBy(m => m.Id).ToList(); + newMessages = newMessages.Concat(chatLines.Select(c => c.Message)).OrderBy(m => m.Id).ToList(); ChatLineFlow.Clear(); } // Add up to last Channel.MAX_HISTORY messages - var displayMessages = messages.Skip(Math.Max(0, messages.Count() - Channel.MAX_HISTORY)); + var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MAX_HISTORY)); Message lastMessage = chatLines.LastOrDefault()?.Message; @@ -192,9 +175,28 @@ namespace osu.Game.Overlays.Chat // due to the scroll adjusts from old messages removal above, a scroll-to-end must be enforced, // to avoid making the container think the user has scrolled back up and unwantedly disable auto-scrolling. - if (messages.Any(m => m is LocalMessage)) + if (newMessages.Any(m => m is LocalMessage)) scroll.ScrollToEnd(); - } + }); + + private void pendingMessageResolved(Message existing, Message updated) => Schedule(() => + { + var found = chatLines.LastOrDefault(c => c.Message == existing); + + if (found != null) + { + Trace.Assert(updated.Id.HasValue, "An updated message was returned with no ID."); + + ChatLineFlow.Remove(found); + found.Message = updated; + ChatLineFlow.Add(found); + } + }); + + private void messageRemoved(Message removed) => Schedule(() => + { + chatLines.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire(); + }); private IEnumerable chatLines => ChatLineFlow.Children.OfType(); From 93cf93943f5ff339443e7cffba97c09f51113ec9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 02:48:33 +0300 Subject: [PATCH 16/29] Schedule chat line highlight after children to handle non-loaded lines --- osu.Game/Overlays/Chat/DrawableChannel.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 0cc3260e60..d5df6102f2 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -93,7 +93,8 @@ namespace osu.Game.Overlays.Chat return; // schedule highlight to ensure it performs after any pending "newMessagesArrived" calls. - Schedule(() => + // also schedule after children to ensure the scroll flow is updated with any new chat lines. + ScheduleAfterChildren(() => { var chatLine = chatLines.SingleOrDefault(c => c.Message.Equals(m.NewValue)); From 80c0df6af578962b91757d36e8a9de53664acfc3 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 02:49:23 +0300 Subject: [PATCH 17/29] Scroll chat line to channel center We may eventually want that encapsulated within `ScrollIntoView`, as it would also come in handy for `GameplayLeaderboard`. --- .../Online/TestSceneStandAloneChatDisplay.cs | 17 ++++++++++------- .../Overlays/Chat/ChannelScrollContainer.cs | 6 ++++++ osu.Game/Overlays/Chat/DrawableChannel.cs | 3 ++- 3 files changed, 18 insertions(+), 8 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index c9837be934..17c818963e 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -152,7 +152,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("highlight first message", () => { highlighted = testChannel.Messages[0]; - chatDisplay.DrawableChannel.Channel.HighlightedMessage.Value = highlighted; + testChannel.HighlightedMessage.Value = highlighted; }); AddUntilStep("chat scrolled to first message", () => @@ -167,7 +167,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("highlight last message", () => { highlighted = testChannel.Messages[^1]; - chatDisplay.DrawableChannel.Channel.HighlightedMessage.Value = highlighted; + testChannel.HighlightedMessage.Value = highlighted; }); AddUntilStep("chat scrolled to last message", () => @@ -182,19 +182,22 @@ namespace osu.Game.Tests.Visual.Online AddRepeatStep("highlight other random messages", () => { highlighted = testChannel.Messages[RNG.Next(0, testChannel.Messages.Count - 1)]; - chatDisplay.DrawableChannel.Channel.HighlightedMessage.Value = highlighted; + testChannel.HighlightedMessage.Value = highlighted; }, 10); } [Test] public void TestMessageHighlightingOnFilledChat() { + int index = 0; + fillChat(); - AddRepeatStep("highlight random messages", () => - { - chatDisplay.DrawableChannel.Channel.HighlightedMessage.Value = testChannel.Messages[RNG.Next(0, testChannel.Messages.Count - 1)]; - }, 10); + AddStep("highlight first message", () => testChannel.HighlightedMessage.Value = testChannel.Messages[index = 0]); + AddStep("highlight next message", () => testChannel.HighlightedMessage.Value = testChannel.Messages[index = Math.Min(index + 1, testChannel.Messages.Count - 1)]); + AddStep("highlight last message", () => testChannel.HighlightedMessage.Value = testChannel.Messages[index = testChannel.Messages.Count - 1]); + AddStep("highlight previous message", () => testChannel.HighlightedMessage.Value = testChannel.Messages[index = Math.Max(index - 1, 0)]); + AddRepeatStep("highlight random messages", () => testChannel.HighlightedMessage.Value = testChannel.Messages[index = RNG.Next(0, testChannel.Messages.Count - 1)], 10); } /// diff --git a/osu.Game/Overlays/Chat/ChannelScrollContainer.cs b/osu.Game/Overlays/Chat/ChannelScrollContainer.cs index 58b2b9a075..139c091f03 100644 --- a/osu.Game/Overlays/Chat/ChannelScrollContainer.cs +++ b/osu.Game/Overlays/Chat/ChannelScrollContainer.cs @@ -47,6 +47,12 @@ namespace osu.Game.Overlays.Chat updateTrackState(); } + public new void ScrollTo(float value, bool animated = true, double? distanceDecay = null) + { + base.ScrollTo(value, animated, distanceDecay); + updateTrackState(); + } + public new void ScrollIntoView(Drawable d, bool animated = true) { base.ScrollIntoView(d, animated); diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index d5df6102f2..433fb2ced7 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -100,7 +100,8 @@ namespace osu.Game.Overlays.Chat if (chatLine != null) { - scroll.ScrollIntoView(chatLine); + float center = scroll.GetChildPosInContent(chatLine, chatLine.DrawSize / 2) - scroll.DisplayableContent / 2; + scroll.ScrollTo(Math.Clamp(center, 0, scroll.ScrollableExtent)); chatLine.ScheduleHighlight(); } From cf9671cafbe9f85dd8612a5a545360b534906fa8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 02:50:07 +0300 Subject: [PATCH 18/29] Increase highlight delay to 1500ms --- osu.Game/Overlays/Chat/ChatLine.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 97fda80492..9ae80a4e2b 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -220,7 +220,7 @@ namespace osu.Game.Overlays.Chat /// /// 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)); + public void ScheduleHighlight() => Schedule(() => lineHighlightBackground.FadeTo(0.5f).FadeOut(1500, Easing.InQuint)); private void updateMessageContent() { From d4de435eb2f228696d29147d351764114c1745ed Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 02:50:27 +0300 Subject: [PATCH 19/29] Add test case for highlighting while chat overlay is hidden --- .../Visual/Online/TestSceneChatOverlay.cs | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index a91cd53ec1..5ee17f80bd 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -465,6 +465,38 @@ namespace osu.Game.Tests.Visual.Online AddStep("Highlight message", () => chatOverlay.HighlightMessage(message)); } + [Test] + public void TestHighlightWhileChatHidden() + { + Message message = null; + + AddStep("hide chat", () => chatOverlay.Hide()); + + AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); + AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); + + AddStep("Send message in channel 1", () => + { + channel1.AddNewMessages(message = new Message + { + ChannelId = channel1.Id, + Content = "Message to highlight!", + Timestamp = DateTimeOffset.Now, + Sender = new APIUser + { + Id = 2, + Username = "Someone", + } + }); + }); + + AddStep("Highlight message and show chat", () => + { + chatOverlay.HighlightMessage(message); + chatOverlay.Show(); + }); + } + private void pressChannelHotkey(int number) { var channelKey = Key.Number0 + number; From b25c37ce62d96a3cb2640480cb203ff6d7741280 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 03:47:48 +0300 Subject: [PATCH 20/29] Instantiate highlight background container on animation Also removes the necessity of scheduling as it actually never worked as intended, `Scheduler` will still update even when the chat line is masked away, and the animation will never be held anyways. The new duration of the animation should be enough for long scrolls either way. --- osu.Game/Overlays/Chat/ChatLine.cs | 37 +++++++++++++---------- osu.Game/Overlays/Chat/DrawableChannel.cs | 2 +- 2 files changed, 22 insertions(+), 17 deletions(-) diff --git a/osu.Game/Overlays/Chat/ChatLine.cs b/osu.Game/Overlays/Chat/ChatLine.cs index 9ae80a4e2b..a1d8cd5d38 100644 --- a/osu.Game/Overlays/Chat/ChatLine.cs +++ b/osu.Game/Overlays/Chat/ChatLine.cs @@ -42,8 +42,6 @@ namespace osu.Game.Overlays.Chat protected virtual float TextSize => 20; - private Container lineHighlightBackground; - private Color4 usernameColour; private OsuSpriteText timestamp; @@ -146,15 +144,6 @@ namespace osu.Game.Overlays.Chat 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), @@ -214,13 +203,29 @@ namespace osu.Game.Overlays.Chat FinishTransforms(true); } + private Container highlight; + /// - /// Schedules a message highlight animation. + /// Performs a highlight animation on this . /// - /// - /// 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(1500, Easing.InQuint)); + public void Highlight() + { + if (highlight?.IsAlive != true) + { + AddInternal(highlight = new Container + { + CornerRadius = 2f, + Masking = true, + RelativeSizeAxes = Axes.Both, + Colour = usernameColour.Darken(1f), + Depth = float.MaxValue, + Child = new Box { RelativeSizeAxes = Axes.Both } + }); + } + + highlight.FadeTo(0.5f).FadeOut(1500, Easing.InQuint); + highlight.Expire(); + } private void updateMessageContent() { diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 433fb2ced7..5086d08e91 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -102,7 +102,7 @@ namespace osu.Game.Overlays.Chat { float center = scroll.GetChildPosInContent(chatLine, chatLine.DrawSize / 2) - scroll.DisplayableContent / 2; scroll.ScrollTo(Math.Clamp(center, 0, scroll.ScrollableExtent)); - chatLine.ScheduleHighlight(); + chatLine.Highlight(); } highlightedMessage.Value = null; From f229447453821c387da151c73eeee15cb3a47601 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 04:01:07 +0300 Subject: [PATCH 21/29] Add too many messages to better test long scrolls --- .../Visual/Online/TestSceneStandAloneChatDisplay.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs index 17c818963e..860ef5d565 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneStandAloneChatDisplay.cs @@ -191,7 +191,7 @@ namespace osu.Game.Tests.Visual.Online { int index = 0; - fillChat(); + fillChat(100); AddStep("highlight first message", () => testChannel.HighlightedMessage.Value = testChannel.Messages[index = 0]); AddStep("highlight next message", () => testChannel.HighlightedMessage.Value = testChannel.Messages[index = Math.Min(index + 1, testChannel.Messages.Count - 1)]); @@ -304,11 +304,11 @@ namespace osu.Game.Tests.Visual.Online checkScrolledToBottom(); } - private void fillChat() + private void fillChat(int count = 10) { AddStep("fill chat", () => { - for (int i = 0; i < 10; i++) + for (int i = 0; i < count; i++) { testChannel.AddNewMessages(new Message(messageIdSequence++) { From c72e8a8b5e37d3e959a491e926422ded94dd931a Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 21:28:38 +0300 Subject: [PATCH 22/29] Add functionality to switch to successfully joined channel --- osu.Game/Online/Chat/ChannelManager.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 47e45e67d1..1404c0f333 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -420,10 +420,11 @@ namespace osu.Game.Online.Chat /// Joins a channel if it has not already been joined. Must be called from the update thread. /// /// The channel to join. + /// Switch to the joined channel on successful request. /// The joined channel. Note that this may not match the parameter channel as it is a backed object. - public Channel JoinChannel(Channel channel) => joinChannel(channel, true); + public Channel JoinChannel(Channel channel, bool switchToJoined = false) => joinChannel(channel, switchToJoined, true); - private Channel joinChannel(Channel channel, bool fetchInitialMessages = false) + private Channel joinChannel(Channel channel, bool switchToJoined = false, bool fetchInitialMessages = false) { if (channel == null) return null; @@ -439,7 +440,7 @@ namespace osu.Game.Online.Chat case ChannelType.Multiplayer: // join is implicit. happens when you join a multiplayer game. // this will probably change in the future. - joinChannel(channel, fetchInitialMessages); + joinChannel(channel, switchToJoined, fetchInitialMessages); return channel; case ChannelType.PM: @@ -460,7 +461,7 @@ namespace osu.Game.Online.Chat default: var req = new JoinChannelRequest(channel); - req.Success += () => joinChannel(channel, fetchInitialMessages); + req.Success += () => joinChannel(channel, switchToJoined, fetchInitialMessages); req.Failure += ex => LeaveChannel(channel); api.Queue(req); return channel; @@ -472,7 +473,8 @@ namespace osu.Game.Online.Chat this.fetchInitialMessages(channel); } - CurrentChannel.Value ??= channel; + if (switchToJoined || CurrentChannel.Value == null) + CurrentChannel.Value = channel; return channel; } From 5315ff794a30295e5f018d23a8e5c4026782d97c Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 21:29:32 +0300 Subject: [PATCH 23/29] Handle non-existing channels on message highlighting gracefully --- osu.Game/Overlays/ChatOverlay.cs | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index d6a1b23c46..a0b5e55014 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -309,10 +309,21 @@ namespace osu.Game.Overlays /// The message to highlight. public void HighlightMessage(Message message) { - if (channelManager.CurrentChannel.Value.Id != message.ChannelId) - channelManager.CurrentChannel.Value = channelManager.JoinedChannels.Single(c => c.Id == message.ChannelId); + Channel targetChannel; - channelManager.CurrentChannel.Value.HighlightedMessage.Value = message; + if (channelManager.CurrentChannel.Value.Id == message.ChannelId) + targetChannel = channelManager.CurrentChannel.Value; + else + { + targetChannel = channelManager.JoinedChannels.SingleOrDefault(c => c.Id == message.ChannelId); + + if (targetChannel != null) + channelManager.CurrentChannel.Value = targetChannel; + else + targetChannel = channelManager.JoinChannel(channelManager.AvailableChannels.SingleOrDefault(c => c.Id == message.ChannelId), true); + } + + targetChannel.HighlightedMessage.Value = message; } private float startDragChatHeight; From e41937083003a2b9c13ac0abd449c8068e574686 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 21:30:02 +0300 Subject: [PATCH 24/29] Add test coverage for highlighting message on left channel --- .../Visual/Online/TestSceneChatOverlay.cs | 30 +++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 5ee17f80bd..906b3faa98 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -463,6 +463,36 @@ namespace osu.Game.Tests.Visual.Online }); AddStep("Highlight message", () => chatOverlay.HighlightMessage(message)); + AddAssert("Switched to channel 2", () => channelManager.CurrentChannel.Value == channel2); + } + + [Test] + public void TestHighlightOnLeftChannel() + { + Message message = null; + + AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); + AddStep("Select channel 1", () => clickDrawable(chatOverlay.TabMap[channel1])); + + AddStep("Join channel 2", () => channelManager.JoinChannel(channel2)); + AddStep("Send message in channel 2", () => + { + channel2.AddNewMessages(message = new Message + { + ChannelId = channel2.Id, + Content = "Message to highlight!", + Timestamp = DateTimeOffset.Now, + Sender = new APIUser + { + Id = 2, + Username = "Someone", + } + }); + }); + AddStep("Leave channel 2", () => channelManager.LeaveChannel(channel2)); + + AddStep("Highlight message", () => chatOverlay.HighlightMessage(message)); + AddAssert("Switched to channel 2", () => channelManager.CurrentChannel.Value == channel2); } [Test] From 8086f734514ba70ae9997fd2927af2e5dd432a45 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 22:50:02 +0300 Subject: [PATCH 25/29] Revert "Add functionality to switch to successfully joined channel" This reverts commit c72e8a8b5e37d3e959a491e926422ded94dd931a. --- osu.Game/Online/Chat/ChannelManager.cs | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/osu.Game/Online/Chat/ChannelManager.cs b/osu.Game/Online/Chat/ChannelManager.cs index 1404c0f333..47e45e67d1 100644 --- a/osu.Game/Online/Chat/ChannelManager.cs +++ b/osu.Game/Online/Chat/ChannelManager.cs @@ -420,11 +420,10 @@ namespace osu.Game.Online.Chat /// Joins a channel if it has not already been joined. Must be called from the update thread. /// /// The channel to join. - /// Switch to the joined channel on successful request. /// The joined channel. Note that this may not match the parameter channel as it is a backed object. - public Channel JoinChannel(Channel channel, bool switchToJoined = false) => joinChannel(channel, switchToJoined, true); + public Channel JoinChannel(Channel channel) => joinChannel(channel, true); - private Channel joinChannel(Channel channel, bool switchToJoined = false, bool fetchInitialMessages = false) + private Channel joinChannel(Channel channel, bool fetchInitialMessages = false) { if (channel == null) return null; @@ -440,7 +439,7 @@ namespace osu.Game.Online.Chat case ChannelType.Multiplayer: // join is implicit. happens when you join a multiplayer game. // this will probably change in the future. - joinChannel(channel, switchToJoined, fetchInitialMessages); + joinChannel(channel, fetchInitialMessages); return channel; case ChannelType.PM: @@ -461,7 +460,7 @@ namespace osu.Game.Online.Chat default: var req = new JoinChannelRequest(channel); - req.Success += () => joinChannel(channel, switchToJoined, fetchInitialMessages); + req.Success += () => joinChannel(channel, fetchInitialMessages); req.Failure += ex => LeaveChannel(channel); api.Queue(req); return channel; @@ -473,8 +472,7 @@ namespace osu.Game.Online.Chat this.fetchInitialMessages(channel); } - if (switchToJoined || CurrentChannel.Value == null) - CurrentChannel.Value = channel; + CurrentChannel.Value ??= channel; return channel; } From a31611bdec45c0378e9486cfb090733f7d12b2e0 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 22:54:47 +0300 Subject: [PATCH 26/29] Improve channel switching flow in `HighlightMessage` --- .../Visual/Online/TestSceneChatOverlay.cs | 8 +++---- osu.Game/Online/Chat/MessageNotifier.cs | 22 ++++++++++--------- osu.Game/Overlays/ChatOverlay.cs | 20 ++++++++--------- 3 files changed, 25 insertions(+), 25 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs index 906b3faa98..00ff6a9576 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneChatOverlay.cs @@ -435,7 +435,7 @@ namespace osu.Game.Tests.Visual.Online }); }); - AddStep("Highlight message", () => chatOverlay.HighlightMessage(message)); + AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, channel1)); } [Test] @@ -462,7 +462,7 @@ namespace osu.Game.Tests.Visual.Online }); }); - AddStep("Highlight message", () => chatOverlay.HighlightMessage(message)); + AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, channel2)); AddAssert("Switched to channel 2", () => channelManager.CurrentChannel.Value == channel2); } @@ -491,7 +491,7 @@ namespace osu.Game.Tests.Visual.Online }); AddStep("Leave channel 2", () => channelManager.LeaveChannel(channel2)); - AddStep("Highlight message", () => chatOverlay.HighlightMessage(message)); + AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, channel2)); AddAssert("Switched to channel 2", () => channelManager.CurrentChannel.Value == channel2); } @@ -522,7 +522,7 @@ namespace osu.Game.Tests.Visual.Online AddStep("Highlight message and show chat", () => { - chatOverlay.HighlightMessage(message); + chatOverlay.HighlightMessage(message, channel1); chatOverlay.Show(); }); } diff --git a/osu.Game/Online/Chat/MessageNotifier.cs b/osu.Game/Online/Chat/MessageNotifier.cs index 90b6d927c7..bcfec3cc0f 100644 --- a/osu.Game/Online/Chat/MessageNotifier.cs +++ b/osu.Game/Online/Chat/MessageNotifier.cs @@ -99,7 +99,7 @@ namespace osu.Game.Online.Chat if (checkForPMs(channel, message)) continue; - checkForMentions(message); + checkForMentions(channel, message); } } @@ -114,15 +114,15 @@ namespace osu.Game.Online.Chat if (!notifyOnPrivateMessage.Value || channel.Type != ChannelType.PM) return false; - notifications.Post(new PrivateMessageNotification(message)); + notifications.Post(new PrivateMessageNotification(message, channel)); return true; } - private void checkForMentions(Message message) + private void checkForMentions(Channel channel, Message message) { if (!notifyOnUsername.Value || !CheckContainsUsername(message.Content, localUser.Value.Username)) return; - notifications.Post(new MentionNotification(message)); + notifications.Post(new MentionNotification(message, channel)); } /// @@ -138,8 +138,8 @@ namespace osu.Game.Online.Chat public class PrivateMessageNotification : HighlightMessageNotification { - public PrivateMessageNotification(Message message) - : base(message) + public PrivateMessageNotification(Message message, Channel channel) + : base(message, channel) { Icon = FontAwesome.Solid.Envelope; Text = $"You received a private message from '{message.Sender.Username}'. Click to read it!"; @@ -148,8 +148,8 @@ namespace osu.Game.Online.Chat public class MentionNotification : HighlightMessageNotification { - public MentionNotification(Message message) - : base(message) + public MentionNotification(Message message, Channel channel) + : base(message, channel) { Icon = FontAwesome.Solid.At; Text = $"Your name was mentioned in chat by '{message.Sender.Username}'. Click to find out why!"; @@ -158,12 +158,14 @@ namespace osu.Game.Online.Chat public abstract class HighlightMessageNotification : SimpleNotification { - protected HighlightMessageNotification(Message message) + protected HighlightMessageNotification(Message message, Channel channel) { this.message = message; + this.channel = channel; } private readonly Message message; + private readonly Channel channel; public override bool IsImportant => false; @@ -175,7 +177,7 @@ namespace osu.Game.Online.Chat Activated = delegate { notificationOverlay.Hide(); - chatOverlay.HighlightMessage(message); + chatOverlay.HighlightMessage(message, channel); chatOverlay.Show(); return true; diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index a0b5e55014..3764ac42fc 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -3,6 +3,7 @@ using System.Collections.Generic; using System.Collections.Specialized; +using System.Diagnostics; using System.Linq; using osuTK; using osuTK.Graphics; @@ -307,23 +308,20 @@ namespace osu.Game.Overlays /// Highlights a certain message in the specified channel. /// /// The message to highlight. - public void HighlightMessage(Message message) + /// The channel containing the message. + public void HighlightMessage(Message message, Channel channel) { - Channel targetChannel; + Debug.Assert(channel.Id == message.ChannelId); - if (channelManager.CurrentChannel.Value.Id == message.ChannelId) - targetChannel = channelManager.CurrentChannel.Value; - else + if (currentChannel.Value.Id != channel.Id) { - targetChannel = channelManager.JoinedChannels.SingleOrDefault(c => c.Id == message.ChannelId); + if (!channel.Joined.Value) + channel = channelManager.JoinChannel(channel); - if (targetChannel != null) - channelManager.CurrentChannel.Value = targetChannel; - else - targetChannel = channelManager.JoinChannel(channelManager.AvailableChannels.SingleOrDefault(c => c.Id == message.ChannelId), true); + channelManager.CurrentChannel.Value = channel; } - targetChannel.HighlightedMessage.Value = message; + channel.HighlightedMessage.Value = message; } private float startDragChatHeight; From d07e3101ea5a040b00c3fc1f66bf1b131aa2dba8 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Thu, 10 Mar 2022 23:06:58 +0300 Subject: [PATCH 27/29] Improve message highlight handling in `DrawableChannel` --- osu.Game/Overlays/Chat/DrawableChannel.cs | 46 ++++++++++++----------- 1 file changed, 25 insertions(+), 21 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 5086d08e91..1d9f8c7ab7 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -87,29 +87,31 @@ namespace osu.Game.Overlays.Chat base.LoadComplete(); highlightedMessage = Channel.HighlightedMessage.GetBoundCopy(); - highlightedMessage.BindValueChanged(m => - { - if (m.NewValue == null) - return; - - // schedule highlight to ensure it performs after any pending "newMessagesArrived" calls. - // also schedule after children to ensure the scroll flow is updated with any new chat lines. - ScheduleAfterChildren(() => - { - var chatLine = chatLines.SingleOrDefault(c => c.Message.Equals(m.NewValue)); - - if (chatLine != null) - { - float center = scroll.GetChildPosInContent(chatLine, chatLine.DrawSize / 2) - scroll.DisplayableContent / 2; - scroll.ScrollTo(Math.Clamp(center, 0, scroll.ScrollableExtent)); - chatLine.Highlight(); - } - - highlightedMessage.Value = null; - }); - }, true); + highlightedMessage.BindValueChanged(_ => processMessageHighlighting(), true); } + /// + /// Processes any pending message in . + /// + /// + /// is for ensuring the scroll flow has updated with any new chat lines. + /// + private void processMessageHighlighting() => ScheduleAfterChildren(() => + { + if (highlightedMessage.Value == null) + return; + + var chatLine = chatLines.SingleOrDefault(c => c.Message.Equals(highlightedMessage.Value)); + if (chatLine == null) + return; + + float center = scroll.GetChildPosInContent(chatLine, chatLine.DrawSize / 2) - scroll.DisplayableContent / 2; + scroll.ScrollTo(Math.Clamp(center, 0, scroll.ScrollableExtent)); + chatLine.Highlight(); + + highlightedMessage.Value = null; + }); + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); @@ -179,6 +181,8 @@ namespace osu.Game.Overlays.Chat // to avoid making the container think the user has scrolled back up and unwantedly disable auto-scrolling. if (newMessages.Any(m => m is LocalMessage)) scroll.ScrollToEnd(); + + processMessageHighlighting(); }); private void pendingMessageResolved(Message existing, Message updated) => Schedule(() => From 5b70139b333bbe03fa6781423f3bec955a4fa6c1 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Mar 2022 18:32:32 +0300 Subject: [PATCH 28/29] Avoid running message highlight processing more than once --- osu.Game/Overlays/Chat/DrawableChannel.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 1d9f8c7ab7..52878199f0 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -96,7 +96,7 @@ namespace osu.Game.Overlays.Chat /// /// is for ensuring the scroll flow has updated with any new chat lines. /// - private void processMessageHighlighting() => ScheduleAfterChildren(() => + private void processMessageHighlighting() => SchedulerAfterChildren.AddOnce(() => { if (highlightedMessage.Value == null) return; From 53c57661c70a1da9836fd2eb4418ad58a0b45df9 Mon Sep 17 00:00:00 2001 From: Salman Ahmed Date: Fri, 11 Mar 2022 18:34:22 +0300 Subject: [PATCH 29/29] Move implementtaion detail to inline comment --- osu.Game/Overlays/Chat/DrawableChannel.cs | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Chat/DrawableChannel.cs b/osu.Game/Overlays/Chat/DrawableChannel.cs index 52878199f0..632517aa31 100644 --- a/osu.Game/Overlays/Chat/DrawableChannel.cs +++ b/osu.Game/Overlays/Chat/DrawableChannel.cs @@ -93,9 +93,7 @@ namespace osu.Game.Overlays.Chat /// /// Processes any pending message in . /// - /// - /// is for ensuring the scroll flow has updated with any new chat lines. - /// + // ScheduleAfterChildren is for ensuring the scroll flow has updated with any new chat lines. private void processMessageHighlighting() => SchedulerAfterChildren.AddOnce(() => { if (highlightedMessage.Value == null)