2022-04-29 20:33:32 +00:00
|
|
|
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
|
|
|
// See the LICENCE file in the repository root for full licence text.
|
|
|
|
|
|
|
|
#nullable enable
|
|
|
|
|
|
|
|
using System.Collections;
|
|
|
|
using System.Collections.Generic;
|
|
|
|
using System.Collections.Specialized;
|
|
|
|
using System.Diagnostics;
|
|
|
|
using System.Linq;
|
|
|
|
using osu.Framework.Allocation;
|
|
|
|
using osu.Framework.Bindables;
|
|
|
|
using osu.Framework.Graphics;
|
|
|
|
using osu.Framework.Graphics.Containers;
|
|
|
|
using osu.Framework.Graphics.Shapes;
|
|
|
|
using osu.Framework.Input.Events;
|
|
|
|
using osu.Framework.Localisation;
|
|
|
|
using osu.Game.Configuration;
|
|
|
|
using osu.Game.Graphics.Containers;
|
|
|
|
using osu.Game.Graphics.UserInterface;
|
|
|
|
using osu.Game.Localisation;
|
|
|
|
using osu.Game.Online.Chat;
|
|
|
|
using osu.Game.Overlays.Chat;
|
|
|
|
using osu.Game.Overlays.Chat.ChannelList;
|
|
|
|
using osu.Game.Overlays.Chat.Listing;
|
|
|
|
|
|
|
|
namespace osu.Game.Overlays
|
|
|
|
{
|
|
|
|
public class ChatOverlayV2 : OsuFocusedOverlayContainer, INamedOverlayComponent
|
|
|
|
{
|
|
|
|
public string IconTexture => "Icons/Hexacons/messaging";
|
|
|
|
public LocalisableString Title => ChatStrings.HeaderTitle;
|
|
|
|
public LocalisableString Description => ChatStrings.HeaderDescription;
|
|
|
|
|
|
|
|
private ChatOverlayTopBar topBar = null!;
|
|
|
|
private ChannelList channelList = null!;
|
|
|
|
private LoadingLayer loading = null!;
|
|
|
|
private ChannelListing channelListing = null!;
|
|
|
|
private ChatTextBar textBar = null!;
|
2022-05-06 21:56:35 +00:00
|
|
|
private Container<ChatOverlayDrawableChannel> currentChannelContainer = null!;
|
2022-04-29 20:33:32 +00:00
|
|
|
|
2022-05-12 22:48:58 +00:00
|
|
|
private readonly Dictionary<Channel, ChatOverlayDrawableChannel> loadedChannels = new Dictionary<Channel, ChatOverlayDrawableChannel>();
|
2022-05-05 10:16:19 +00:00
|
|
|
|
2022-05-18 19:58:39 +00:00
|
|
|
protected IEnumerable<DrawableChannel> DrawableChannels => loadedChannels.Values;
|
2022-05-05 10:16:19 +00:00
|
|
|
|
2022-05-12 22:48:58 +00:00
|
|
|
private readonly BindableFloat chatHeight = new BindableFloat();
|
2022-04-29 20:33:32 +00:00
|
|
|
private bool isDraggingTopBar;
|
|
|
|
private float dragStartChatHeight;
|
|
|
|
|
2022-05-05 20:39:31 +00:00
|
|
|
private const int transition_length = 500;
|
2022-05-05 22:13:32 +00:00
|
|
|
private const float default_chat_height = 0.4f;
|
2022-04-29 20:33:32 +00:00
|
|
|
private const float top_bar_height = 40;
|
|
|
|
private const float side_bar_width = 190;
|
|
|
|
private const float chat_bar_height = 60;
|
|
|
|
|
|
|
|
[Resolved]
|
|
|
|
private OsuConfigManager config { get; set; } = null!;
|
|
|
|
|
|
|
|
[Resolved]
|
|
|
|
private ChannelManager channelManager { get; set; } = null!;
|
|
|
|
|
|
|
|
[Cached]
|
|
|
|
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Pink);
|
|
|
|
|
|
|
|
[Cached]
|
|
|
|
private readonly Bindable<Channel> currentChannel = new Bindable<Channel>();
|
|
|
|
|
2022-05-19 10:24:18 +00:00
|
|
|
private readonly IBindableList<Channel> availableChannels = new BindableList<Channel>();
|
|
|
|
private readonly IBindableList<Channel> joinedChannels = new BindableList<Channel>();
|
|
|
|
|
2022-05-05 10:22:30 +00:00
|
|
|
public ChatOverlayV2()
|
2022-04-29 20:33:32 +00:00
|
|
|
{
|
|
|
|
Height = default_chat_height;
|
2022-05-05 10:22:30 +00:00
|
|
|
|
|
|
|
Masking = true;
|
|
|
|
|
|
|
|
const float corner_radius = 7f;
|
|
|
|
|
|
|
|
CornerRadius = corner_radius;
|
|
|
|
|
|
|
|
// Hack to hide the bottom edge corner radius off-screen.
|
|
|
|
Margin = new MarginPadding { Bottom = -corner_radius };
|
|
|
|
Padding = new MarginPadding { Bottom = corner_radius };
|
|
|
|
|
2022-04-29 20:33:32 +00:00
|
|
|
Anchor = Anchor.BottomCentre;
|
|
|
|
Origin = Anchor.BottomCentre;
|
2022-05-05 10:22:30 +00:00
|
|
|
}
|
2022-04-29 20:33:32 +00:00
|
|
|
|
2022-05-05 10:22:30 +00:00
|
|
|
[BackgroundDependencyLoader]
|
|
|
|
private void load()
|
|
|
|
{
|
2022-05-05 13:20:33 +00:00
|
|
|
// Required for the pop in/out animation
|
|
|
|
RelativePositionAxes = Axes.Both;
|
|
|
|
|
2022-04-29 20:33:32 +00:00
|
|
|
Children = new Drawable[]
|
|
|
|
{
|
|
|
|
topBar = new ChatOverlayTopBar
|
|
|
|
{
|
|
|
|
RelativeSizeAxes = Axes.X,
|
|
|
|
Height = top_bar_height,
|
|
|
|
},
|
|
|
|
channelList = new ChannelList
|
|
|
|
{
|
|
|
|
RelativeSizeAxes = Axes.Y,
|
|
|
|
Width = side_bar_width,
|
|
|
|
Padding = new MarginPadding { Top = top_bar_height },
|
|
|
|
},
|
|
|
|
new Container
|
|
|
|
{
|
|
|
|
RelativeSizeAxes = Axes.Both,
|
|
|
|
Anchor = Anchor.TopRight,
|
|
|
|
Origin = Anchor.TopRight,
|
|
|
|
Padding = new MarginPadding
|
|
|
|
{
|
|
|
|
Top = top_bar_height,
|
|
|
|
Left = side_bar_width,
|
|
|
|
Bottom = chat_bar_height,
|
|
|
|
},
|
|
|
|
Children = new Drawable[]
|
|
|
|
{
|
|
|
|
new Box
|
|
|
|
{
|
|
|
|
RelativeSizeAxes = Axes.Both,
|
|
|
|
Colour = colourProvider.Background4,
|
|
|
|
},
|
2022-05-06 21:56:35 +00:00
|
|
|
currentChannelContainer = new Container<ChatOverlayDrawableChannel>
|
2022-04-29 20:33:32 +00:00
|
|
|
{
|
|
|
|
RelativeSizeAxes = Axes.Both,
|
|
|
|
},
|
|
|
|
loading = new LoadingLayer(true),
|
|
|
|
channelListing = new ChannelListing
|
|
|
|
{
|
|
|
|
RelativeSizeAxes = Axes.Both,
|
|
|
|
},
|
|
|
|
},
|
|
|
|
},
|
|
|
|
textBar = new ChatTextBar
|
|
|
|
{
|
|
|
|
RelativeSizeAxes = Axes.X,
|
|
|
|
Anchor = Anchor.BottomRight,
|
|
|
|
Origin = Anchor.BottomRight,
|
|
|
|
Padding = new MarginPadding { Left = side_bar_width },
|
|
|
|
},
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void LoadComplete()
|
|
|
|
{
|
|
|
|
base.LoadComplete();
|
|
|
|
|
2022-05-05 10:16:19 +00:00
|
|
|
config.BindWith(OsuSetting.ChatDisplayHeight, chatHeight);
|
|
|
|
|
2022-04-29 20:33:32 +00:00
|
|
|
chatHeight.BindValueChanged(height => { Height = height.NewValue; }, true);
|
|
|
|
|
|
|
|
currentChannel.BindTo(channelManager.CurrentChannel);
|
2022-05-19 10:24:18 +00:00
|
|
|
currentChannel.BindValueChanged(currentChannelChanged, true);
|
|
|
|
|
|
|
|
joinedChannels.BindTo(channelManager.JoinedChannels);
|
|
|
|
joinedChannels.BindCollectionChanged(joinedChannelsChanged, true);
|
|
|
|
|
|
|
|
availableChannels.BindTo(channelManager.AvailableChannels);
|
|
|
|
availableChannels.BindCollectionChanged(availableChannelsChanged, true);
|
2022-04-29 20:33:32 +00:00
|
|
|
|
2022-05-11 21:52:15 +00:00
|
|
|
channelList.OnRequestSelect += channel => channelManager.CurrentChannel.Value = channel;
|
2022-04-29 20:33:32 +00:00
|
|
|
channelList.OnRequestLeave += channel => channelManager.LeaveChannel(channel);
|
|
|
|
|
2022-05-15 18:38:37 +00:00
|
|
|
channelListing.OnRequestJoin += channel => channelManager.JoinChannel(channel);
|
2022-04-29 20:33:32 +00:00
|
|
|
channelListing.OnRequestLeave += channel => channelManager.LeaveChannel(channel);
|
|
|
|
|
|
|
|
textBar.OnSearchTermsChanged += searchTerms => channelListing.SearchTerm = searchTerms;
|
|
|
|
textBar.OnChatMessageCommitted += handleChatMessage;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// <summary>
|
|
|
|
/// Highlights a certain message in the specified channel.
|
|
|
|
/// </summary>
|
|
|
|
/// <param name="message">The message to highlight.</param>
|
|
|
|
/// <param name="channel">The channel containing the message.</param>
|
|
|
|
public void HighlightMessage(Message message, Channel channel)
|
|
|
|
{
|
|
|
|
Debug.Assert(channel.Id == message.ChannelId);
|
|
|
|
|
|
|
|
if (currentChannel.Value?.Id != channel.Id)
|
|
|
|
{
|
|
|
|
if (!channel.Joined.Value)
|
|
|
|
channel = channelManager.JoinChannel(channel);
|
|
|
|
|
|
|
|
channelManager.CurrentChannel.Value = channel;
|
|
|
|
}
|
|
|
|
|
|
|
|
channel.HighlightedMessage.Value = message;
|
|
|
|
|
|
|
|
Show();
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override bool OnDragStart(DragStartEvent e)
|
|
|
|
{
|
|
|
|
isDraggingTopBar = topBar.IsHovered;
|
|
|
|
|
|
|
|
if (!isDraggingTopBar)
|
|
|
|
return base.OnDragStart(e);
|
|
|
|
|
2022-05-05 10:16:19 +00:00
|
|
|
dragStartChatHeight = chatHeight.Value;
|
2022-04-29 20:33:32 +00:00
|
|
|
return true;
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void OnDrag(DragEvent e)
|
|
|
|
{
|
|
|
|
if (!isDraggingTopBar)
|
|
|
|
return;
|
|
|
|
|
|
|
|
float targetChatHeight = dragStartChatHeight - (e.MousePosition.Y - e.MouseDownPosition.Y) / Parent.DrawSize.Y;
|
2022-05-05 22:13:32 +00:00
|
|
|
chatHeight.Value = targetChatHeight;
|
2022-04-29 20:33:32 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
protected override void OnDragEnd(DragEndEvent e)
|
|
|
|
{
|
|
|
|
isDraggingTopBar = false;
|
|
|
|
base.OnDragEnd(e);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void PopIn()
|
|
|
|
{
|
2022-05-03 21:33:36 +00:00
|
|
|
base.PopIn();
|
|
|
|
|
2022-04-29 20:33:32 +00:00
|
|
|
this.MoveToY(0, transition_length, Easing.OutQuint);
|
|
|
|
this.FadeIn(transition_length, Easing.OutQuint);
|
|
|
|
}
|
|
|
|
|
|
|
|
protected override void PopOut()
|
|
|
|
{
|
2022-05-03 21:33:36 +00:00
|
|
|
base.PopOut();
|
|
|
|
|
2022-04-29 20:33:32 +00:00
|
|
|
this.MoveToY(Height, transition_length, Easing.InSine);
|
|
|
|
this.FadeOut(transition_length, Easing.InSine);
|
2022-05-03 21:33:36 +00:00
|
|
|
|
|
|
|
textBar.TextBoxKillFocus();
|
2022-04-29 20:33:32 +00:00
|
|
|
}
|
|
|
|
|
2022-05-04 13:43:40 +00:00
|
|
|
protected override void OnFocus(FocusEvent e)
|
|
|
|
{
|
|
|
|
textBar.TextBoxTakeFocus();
|
|
|
|
base.OnFocus(e);
|
|
|
|
}
|
|
|
|
|
2022-05-05 10:17:32 +00:00
|
|
|
private void currentChannelChanged(ValueChangedEvent<Channel> channel)
|
2022-04-29 20:33:32 +00:00
|
|
|
{
|
2022-05-05 10:17:32 +00:00
|
|
|
Channel? newChannel = channel.NewValue;
|
2022-04-29 20:33:32 +00:00
|
|
|
|
2022-05-19 10:45:39 +00:00
|
|
|
// null channel denotes that we should be showing the listing.
|
|
|
|
if (newChannel == null)
|
|
|
|
{
|
|
|
|
currentChannel.Value = channelList.ChannelListingChannel;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (newChannel is ChannelListing.ChannelListingChannel)
|
2022-04-29 20:33:32 +00:00
|
|
|
{
|
2022-05-13 23:15:02 +00:00
|
|
|
currentChannelContainer.Clear(false);
|
2022-05-14 11:16:00 +00:00
|
|
|
channelListing.Show();
|
2022-05-12 04:53:57 +00:00
|
|
|
textBar.ShowSearch.Value = true;
|
2022-04-29 20:33:32 +00:00
|
|
|
}
|
2022-05-12 04:53:57 +00:00
|
|
|
else
|
2022-04-29 20:33:32 +00:00
|
|
|
{
|
2022-05-14 11:16:00 +00:00
|
|
|
channelListing.Hide();
|
2022-05-12 04:53:57 +00:00
|
|
|
textBar.ShowSearch.Value = false;
|
|
|
|
|
2022-05-12 22:48:58 +00:00
|
|
|
if (loadedChannels.ContainsKey(newChannel))
|
|
|
|
{
|
|
|
|
currentChannelContainer.Clear(false);
|
|
|
|
currentChannelContainer.Add(loadedChannels[newChannel]);
|
|
|
|
}
|
|
|
|
else
|
2022-05-12 04:53:57 +00:00
|
|
|
{
|
2022-05-12 22:48:58 +00:00
|
|
|
loading.Show();
|
|
|
|
|
|
|
|
// Ensure the drawable channel is stored before async load to prevent double loading
|
2022-05-18 19:58:39 +00:00
|
|
|
ChatOverlayDrawableChannel drawableChannel = CreateDrawableChannel(newChannel);
|
2022-05-12 22:48:58 +00:00
|
|
|
loadedChannels.Add(newChannel, drawableChannel);
|
|
|
|
|
2022-05-18 19:00:42 +00:00
|
|
|
LoadComponentAsync(drawableChannel, loadedDrawable =>
|
2022-05-12 22:48:58 +00:00
|
|
|
{
|
2022-05-13 22:39:09 +00:00
|
|
|
// Ensure the current channel hasn't changed by the time the load completes
|
2022-05-18 19:00:42 +00:00
|
|
|
if (currentChannel.Value != loadedDrawable.Channel)
|
2022-05-13 22:39:09 +00:00
|
|
|
return;
|
|
|
|
|
|
|
|
// Ensure the cached reference hasn't been removed from leaving the channel
|
2022-05-18 19:00:42 +00:00
|
|
|
if (!loadedChannels.ContainsKey(loadedDrawable.Channel))
|
2022-05-13 22:39:09 +00:00
|
|
|
return;
|
|
|
|
|
2022-05-12 22:48:58 +00:00
|
|
|
currentChannelContainer.Clear(false);
|
2022-05-18 19:00:42 +00:00
|
|
|
currentChannelContainer.Add(loadedDrawable);
|
2022-05-12 22:48:58 +00:00
|
|
|
loading.Hide();
|
|
|
|
});
|
|
|
|
}
|
2022-05-12 04:53:57 +00:00
|
|
|
}
|
2022-04-29 20:33:32 +00:00
|
|
|
}
|
|
|
|
|
2022-05-18 19:58:39 +00:00
|
|
|
protected virtual ChatOverlayDrawableChannel CreateDrawableChannel(Channel newChannel) => new ChatOverlayDrawableChannel(newChannel);
|
|
|
|
|
2022-04-29 20:33:32 +00:00
|
|
|
private void joinedChannelsChanged(object sender, NotifyCollectionChangedEventArgs args)
|
|
|
|
{
|
|
|
|
switch (args.Action)
|
|
|
|
{
|
|
|
|
case NotifyCollectionChangedAction.Add:
|
2022-05-19 10:24:18 +00:00
|
|
|
IEnumerable<Channel> newChannels = filterChannels(args.NewItems);
|
2022-05-12 22:48:58 +00:00
|
|
|
|
2022-05-19 10:24:18 +00:00
|
|
|
foreach (var channel in newChannels)
|
2022-04-29 20:33:32 +00:00
|
|
|
channelList.AddChannel(channel);
|
2022-05-12 22:48:58 +00:00
|
|
|
|
2022-04-29 20:33:32 +00:00
|
|
|
break;
|
|
|
|
|
|
|
|
case NotifyCollectionChangedAction.Remove:
|
|
|
|
IEnumerable<Channel> leftChannels = filterChannels(args.OldItems);
|
2022-05-12 22:48:58 +00:00
|
|
|
|
2022-04-29 20:33:32 +00:00
|
|
|
foreach (var channel in leftChannels)
|
2022-05-12 22:48:58 +00:00
|
|
|
{
|
2022-04-29 20:33:32 +00:00
|
|
|
channelList.RemoveChannel(channel);
|
2022-05-14 11:16:00 +00:00
|
|
|
|
2022-05-13 23:15:02 +00:00
|
|
|
if (loadedChannels.ContainsKey(channel))
|
|
|
|
{
|
|
|
|
ChatOverlayDrawableChannel loaded = loadedChannels[channel];
|
|
|
|
loadedChannels.Remove(channel);
|
|
|
|
// DrawableChannel removed from cache must be manually disposed
|
|
|
|
loaded.Dispose();
|
|
|
|
}
|
2022-05-12 22:48:58 +00:00
|
|
|
}
|
|
|
|
|
2022-04-29 20:33:32 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
private void availableChannelsChanged(object sender, NotifyCollectionChangedEventArgs args)
|
|
|
|
=> channelListing.UpdateAvailableChannels(channelManager.AvailableChannels);
|
|
|
|
|
|
|
|
private IEnumerable<Channel> filterChannels(IList channels)
|
|
|
|
=> channels.Cast<Channel>().Where(c => c.Type == ChannelType.Public || c.Type == ChannelType.PM);
|
|
|
|
|
|
|
|
private void handleChatMessage(string message)
|
|
|
|
{
|
|
|
|
if (string.IsNullOrWhiteSpace(message))
|
|
|
|
return;
|
|
|
|
|
|
|
|
if (message[0] == '/')
|
|
|
|
channelManager.PostCommand(message.Substring(1));
|
|
|
|
else
|
|
|
|
channelManager.PostMessage(message);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|