mirror of
https://github.com/ppy/osu.git
synced 2025-02-06 02:33:20 +08:00
Merge pull request #18033 from jai-x/new-chat-overlay
Implement basic layout and behaviour of new chat overlay
This commit is contained in:
commit
14d2159b8c
422
osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs
Normal file
422
osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs
Normal file
@ -0,0 +1,422 @@
|
|||||||
|
// 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.
|
||||||
|
|
||||||
|
using System;
|
||||||
|
using System.Linq;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using System.Net;
|
||||||
|
using NUnit.Framework;
|
||||||
|
using osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Bindables;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Logging;
|
||||||
|
using osu.Framework.Testing;
|
||||||
|
using osu.Framework.Utils;
|
||||||
|
using osu.Game.Configuration;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Online.API;
|
||||||
|
using osu.Game.Online.API.Requests;
|
||||||
|
using osu.Game.Online.API.Requests.Responses;
|
||||||
|
using osu.Game.Online.Chat;
|
||||||
|
using osu.Game.Overlays;
|
||||||
|
using osu.Game.Overlays.Chat;
|
||||||
|
using osu.Game.Overlays.Chat.Listing;
|
||||||
|
using osu.Game.Overlays.Chat.ChannelList;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Input;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Online
|
||||||
|
{
|
||||||
|
[TestFixture]
|
||||||
|
public class TestSceneChatOverlayV2 : OsuManualInputManagerTestScene
|
||||||
|
{
|
||||||
|
private ChatOverlayV2 chatOverlay;
|
||||||
|
private ChannelManager channelManager;
|
||||||
|
|
||||||
|
private APIUser testUser;
|
||||||
|
private Channel testPMChannel;
|
||||||
|
private Channel[] testChannels;
|
||||||
|
|
||||||
|
private Channel testChannel1 => testChannels[0];
|
||||||
|
private Channel testChannel2 => testChannels[1];
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OsuConfigManager config { get; set; } = null!;
|
||||||
|
|
||||||
|
[SetUp]
|
||||||
|
public void SetUp() => Schedule(() =>
|
||||||
|
{
|
||||||
|
testUser = new APIUser { Username = "test user", Id = 5071479 };
|
||||||
|
testPMChannel = new Channel(testUser);
|
||||||
|
testChannels = Enumerable.Range(1, 10).Select(createPublicChannel).ToArray();
|
||||||
|
|
||||||
|
Child = new DependencyProvidingContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
CachedDependencies = new (Type, object)[]
|
||||||
|
{
|
||||||
|
(typeof(ChannelManager), channelManager = new ChannelManager()),
|
||||||
|
},
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
channelManager,
|
||||||
|
chatOverlay = new ChatOverlayV2 { RelativeSizeAxes = Axes.Both },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
[SetUpSteps]
|
||||||
|
public void SetUpSteps()
|
||||||
|
{
|
||||||
|
AddStep("Setup request handler", () =>
|
||||||
|
{
|
||||||
|
((DummyAPIAccess)API).HandleRequest = req =>
|
||||||
|
{
|
||||||
|
switch (req)
|
||||||
|
{
|
||||||
|
case GetUpdatesRequest getUpdates:
|
||||||
|
getUpdates.TriggerFailure(new WebException());
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case JoinChannelRequest joinChannel:
|
||||||
|
joinChannel.TriggerSuccess();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case LeaveChannelRequest leaveChannel:
|
||||||
|
leaveChannel.TriggerSuccess();
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case GetMessagesRequest getMessages:
|
||||||
|
getMessages.TriggerSuccess(createChannelMessages(getMessages.Channel));
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case GetUserRequest getUser:
|
||||||
|
if (getUser.Lookup == testUser.Username)
|
||||||
|
getUser.TriggerSuccess(testUser);
|
||||||
|
else
|
||||||
|
getUser.TriggerFailure(new WebException());
|
||||||
|
return true;
|
||||||
|
|
||||||
|
case PostMessageRequest postMessage:
|
||||||
|
postMessage.TriggerSuccess(new Message(RNG.Next(0, 10000000))
|
||||||
|
{
|
||||||
|
Content = postMessage.Message.Content,
|
||||||
|
ChannelId = postMessage.Message.ChannelId,
|
||||||
|
Sender = postMessage.Message.Sender,
|
||||||
|
Timestamp = new DateTimeOffset(DateTime.Now),
|
||||||
|
});
|
||||||
|
return true;
|
||||||
|
|
||||||
|
default:
|
||||||
|
Logger.Log($"Unhandled Request Type: {req.GetType()}");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
AddStep("Add test channels", () =>
|
||||||
|
{
|
||||||
|
(channelManager.AvailableChannels as BindableList<Channel>)?.AddRange(testChannels);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestShowHide()
|
||||||
|
{
|
||||||
|
AddStep("Show overlay", () => chatOverlay.Show());
|
||||||
|
AddAssert("Overlay is visible", () => chatOverlay.State.Value == Visibility.Visible);
|
||||||
|
AddStep("Hide overlay", () => chatOverlay.Hide());
|
||||||
|
AddAssert("Overlay is hidden", () => chatOverlay.State.Value == Visibility.Hidden);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestChatHeight()
|
||||||
|
{
|
||||||
|
Bindable<float> configChatHeight = null;
|
||||||
|
float newHeight = 0;
|
||||||
|
|
||||||
|
AddStep("Bind config chat height", () => configChatHeight = config.GetBindable<float>(OsuSetting.ChatDisplayHeight).GetBoundCopy());
|
||||||
|
AddStep("Set config chat height", () => configChatHeight.Value = 0.4f);
|
||||||
|
AddStep("Show overlay", () => chatOverlay.Show());
|
||||||
|
AddAssert("Overlay uses config height", () => chatOverlay.Height == 0.4f);
|
||||||
|
AddStep("Drag overlay to new height", () =>
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(chatOverlayTopBar);
|
||||||
|
InputManager.PressButton(MouseButton.Left);
|
||||||
|
InputManager.MoveMouseTo(chatOverlayTopBar, new Vector2(0, -300));
|
||||||
|
InputManager.ReleaseButton(MouseButton.Left);
|
||||||
|
});
|
||||||
|
AddStep("Store new height", () => newHeight = chatOverlay.Height);
|
||||||
|
AddAssert("Config height changed", () => configChatHeight.Value != 0.4f && configChatHeight.Value == newHeight);
|
||||||
|
AddStep("Hide overlay", () => chatOverlay.Hide());
|
||||||
|
AddStep("Show overlay", () => chatOverlay.Show());
|
||||||
|
AddAssert("Overlay uses new height", () => chatOverlay.Height == newHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestChannelSelection()
|
||||||
|
{
|
||||||
|
AddStep("Show overlay", () => chatOverlay.Show());
|
||||||
|
AddAssert("Listing is visible", () => listingVisibility == Visibility.Visible);
|
||||||
|
AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
|
||||||
|
AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
|
||||||
|
AddAssert("Listing is hidden", () => listingVisibility == Visibility.Hidden);
|
||||||
|
AddAssert("Loading is hidden", () => loadingVisibility == Visibility.Hidden);
|
||||||
|
AddAssert("Current channel is correct", () => channelManager.CurrentChannel.Value == testChannel1);
|
||||||
|
AddAssert("DrawableChannel is correct", () => currentDrawableChannel.Channel == testChannel1);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestSearchInListing()
|
||||||
|
{
|
||||||
|
AddStep("Show overlay", () => chatOverlay.Show());
|
||||||
|
AddAssert("Listing is visible", () => listingVisibility == Visibility.Visible);
|
||||||
|
AddStep("Search for 'number 2'", () => chatOverlayTextBox.Text = "number 2");
|
||||||
|
AddUntilStep("Only channel 2 visibile", () =>
|
||||||
|
{
|
||||||
|
IEnumerable<ChannelListingItem> listingItems = chatOverlay.ChildrenOfType<ChannelListingItem>()
|
||||||
|
.Where(item => item.IsPresent);
|
||||||
|
return listingItems.Count() == 1 && listingItems.Single().Channel == testChannel2;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestChannelCloseButton()
|
||||||
|
{
|
||||||
|
AddStep("Show overlay", () => chatOverlay.Show());
|
||||||
|
AddStep("Join PM and public channels", () =>
|
||||||
|
{
|
||||||
|
channelManager.JoinChannel(testChannel1);
|
||||||
|
channelManager.JoinChannel(testPMChannel);
|
||||||
|
});
|
||||||
|
AddStep("Select PM channel", () => clickDrawable(getChannelListItem(testPMChannel)));
|
||||||
|
AddStep("Click close button", () =>
|
||||||
|
{
|
||||||
|
ChannelListItemCloseButton closeButton = getChannelListItem(testPMChannel).ChildrenOfType<ChannelListItemCloseButton>().Single();
|
||||||
|
clickDrawable(closeButton);
|
||||||
|
});
|
||||||
|
AddAssert("PM channel closed", () => !channelManager.JoinedChannels.Contains(testPMChannel));
|
||||||
|
AddStep("Select normal channel", () => clickDrawable(getChannelListItem(testChannel1)));
|
||||||
|
AddStep("Click close button", () =>
|
||||||
|
{
|
||||||
|
ChannelListItemCloseButton closeButton = getChannelListItem(testChannel1).ChildrenOfType<ChannelListItemCloseButton>().Single();
|
||||||
|
clickDrawable(closeButton);
|
||||||
|
});
|
||||||
|
AddAssert("Normal channel closed", () => !channelManager.JoinedChannels.Contains(testChannel1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestChatCommand()
|
||||||
|
{
|
||||||
|
AddStep("Show overlay", () => chatOverlay.Show());
|
||||||
|
AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
|
||||||
|
AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
|
||||||
|
AddStep("Open chat with user", () => channelManager.PostCommand($"chat {testUser.Username}"));
|
||||||
|
AddAssert("PM channel is selected", () =>
|
||||||
|
channelManager.CurrentChannel.Value.Type == ChannelType.PM && channelManager.CurrentChannel.Value.Users.Single() == testUser);
|
||||||
|
AddStep("Open chat with non-existent user", () => channelManager.PostCommand("chat user_doesnt_exist"));
|
||||||
|
AddAssert("Last message is error", () => channelManager.CurrentChannel.Value.Messages.Last() is ErrorMessage);
|
||||||
|
|
||||||
|
// Make sure no unnecessary requests are made when the PM channel is already open.
|
||||||
|
AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
|
||||||
|
AddStep("Unregister request handling", () => ((DummyAPIAccess)API).HandleRequest = null);
|
||||||
|
AddStep("Open chat with user", () => channelManager.PostCommand($"chat {testUser.Username}"));
|
||||||
|
AddAssert("PM channel is selected", () =>
|
||||||
|
channelManager.CurrentChannel.Value.Type == ChannelType.PM && channelManager.CurrentChannel.Value.Users.Single() == testUser);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestMultiplayerChannelIsNotShown()
|
||||||
|
{
|
||||||
|
Channel multiplayerChannel = null;
|
||||||
|
|
||||||
|
AddStep("Show overlay", () => chatOverlay.Show());
|
||||||
|
AddStep("Join multiplayer channel", () => channelManager.JoinChannel(multiplayerChannel = new Channel(new APIUser())
|
||||||
|
{
|
||||||
|
Name = "#mp_1",
|
||||||
|
Type = ChannelType.Multiplayer,
|
||||||
|
}));
|
||||||
|
AddAssert("Channel is joined", () => channelManager.JoinedChannels.Contains(multiplayerChannel));
|
||||||
|
AddUntilStep("Channel not present in listing", () => !chatOverlay.ChildrenOfType<ChannelListingItem>()
|
||||||
|
.Where(item => item.IsPresent)
|
||||||
|
.Select(item => item.Channel)
|
||||||
|
.Contains(multiplayerChannel));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHighlightOnCurrentChannel()
|
||||||
|
{
|
||||||
|
Message message = null;
|
||||||
|
|
||||||
|
AddStep("Show overlay", () => chatOverlay.Show());
|
||||||
|
AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
|
||||||
|
AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
|
||||||
|
AddStep("Send message in channel 1", () =>
|
||||||
|
{
|
||||||
|
testChannel1.AddNewMessages(message = new Message
|
||||||
|
{
|
||||||
|
ChannelId = testChannel1.Id,
|
||||||
|
Content = "Message to highlight!",
|
||||||
|
Timestamp = DateTimeOffset.Now,
|
||||||
|
Sender = testUser,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHighlightOnAnotherChannel()
|
||||||
|
{
|
||||||
|
Message message = null;
|
||||||
|
|
||||||
|
AddStep("Show overlay", () => chatOverlay.Show());
|
||||||
|
AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
|
||||||
|
AddStep("Join channel 2", () => channelManager.JoinChannel(testChannel2));
|
||||||
|
AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
|
||||||
|
AddStep("Send message in channel 2", () =>
|
||||||
|
{
|
||||||
|
testChannel2.AddNewMessages(message = new Message
|
||||||
|
{
|
||||||
|
ChannelId = testChannel2.Id,
|
||||||
|
Content = "Message to highlight!",
|
||||||
|
Timestamp = DateTimeOffset.Now,
|
||||||
|
Sender = testUser,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel2));
|
||||||
|
AddAssert("Channel 2 is selected", () => channelManager.CurrentChannel.Value == testChannel2);
|
||||||
|
AddAssert("Channel 2 is visible", () => currentDrawableChannel.Channel == testChannel2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHighlightOnLeftChannel()
|
||||||
|
{
|
||||||
|
Message message = null;
|
||||||
|
|
||||||
|
AddStep("Show overlay", () => chatOverlay.Show());
|
||||||
|
AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
|
||||||
|
AddStep("Join channel 2", () => channelManager.JoinChannel(testChannel2));
|
||||||
|
AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
|
||||||
|
AddStep("Send message in channel 2", () =>
|
||||||
|
{
|
||||||
|
testChannel2.AddNewMessages(message = new Message
|
||||||
|
{
|
||||||
|
ChannelId = testChannel2.Id,
|
||||||
|
Content = "Message to highlight!",
|
||||||
|
Timestamp = DateTimeOffset.Now,
|
||||||
|
Sender = testUser,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
AddStep("Leave channel 2", () => channelManager.LeaveChannel(testChannel2));
|
||||||
|
AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel2));
|
||||||
|
AddAssert("Channel 2 is selected", () => channelManager.CurrentChannel.Value == testChannel2);
|
||||||
|
AddAssert("Channel 2 is visible", () => currentDrawableChannel.Channel == testChannel2);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHighlightWhileChatNeverOpen()
|
||||||
|
{
|
||||||
|
Message message = null;
|
||||||
|
|
||||||
|
AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
|
||||||
|
AddStep("Send message in channel 1", () =>
|
||||||
|
{
|
||||||
|
testChannel1.AddNewMessages(message = new Message
|
||||||
|
{
|
||||||
|
ChannelId = testChannel1.Id,
|
||||||
|
Content = "Message to highlight!",
|
||||||
|
Timestamp = DateTimeOffset.Now,
|
||||||
|
Sender = testUser,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestHighlightWithNullChannel()
|
||||||
|
{
|
||||||
|
Message message = null;
|
||||||
|
|
||||||
|
AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
|
||||||
|
AddStep("Send message in channel 1", () =>
|
||||||
|
{
|
||||||
|
testChannel1.AddNewMessages(message = new Message
|
||||||
|
{
|
||||||
|
ChannelId = testChannel1.Id,
|
||||||
|
Content = "Message to highlight!",
|
||||||
|
Timestamp = DateTimeOffset.Now,
|
||||||
|
Sender = testUser,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
AddStep("Set null channel", () => channelManager.CurrentChannel.Value = null);
|
||||||
|
AddStep("Highlight message", () => chatOverlay.HighlightMessage(message, testChannel1));
|
||||||
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TextBoxRetainsFocus()
|
||||||
|
{
|
||||||
|
AddStep("Show overlay", () => chatOverlay.Show());
|
||||||
|
AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox);
|
||||||
|
AddStep("Join channel 1", () => channelManager.JoinChannel(testChannel1));
|
||||||
|
AddStep("Select channel 1", () => clickDrawable(getChannelListItem(testChannel1)));
|
||||||
|
AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox);
|
||||||
|
AddStep("Click selector", () => clickDrawable(chatOverlay.ChildrenOfType<ChannelListSelector>().Single()));
|
||||||
|
AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox);
|
||||||
|
AddStep("Click listing", () => clickDrawable(chatOverlay.ChildrenOfType<ChannelListing>().Single()));
|
||||||
|
AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox);
|
||||||
|
AddStep("Click drawable channel", () => clickDrawable(chatOverlay.ChildrenOfType<DrawableChannel>().Single()));
|
||||||
|
AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox);
|
||||||
|
AddStep("Click channel list", () => clickDrawable(chatOverlay.ChildrenOfType<ChannelList>().Single()));
|
||||||
|
AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox);
|
||||||
|
AddStep("Click top bar", () => clickDrawable(chatOverlay.ChildrenOfType<ChatOverlayTopBar>().Single()));
|
||||||
|
AddAssert("TextBox is focused", () => InputManager.FocusedDrawable == chatOverlayTextBox);
|
||||||
|
AddStep("Hide overlay", () => chatOverlay.Hide());
|
||||||
|
AddAssert("TextBox is not focused", () => InputManager.FocusedDrawable == null);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Visibility listingVisibility =>
|
||||||
|
chatOverlay.ChildrenOfType<ChannelListing>().Single().State.Value;
|
||||||
|
|
||||||
|
private Visibility loadingVisibility =>
|
||||||
|
chatOverlay.ChildrenOfType<LoadingLayer>().Single().State.Value;
|
||||||
|
|
||||||
|
private DrawableChannel currentDrawableChannel =>
|
||||||
|
chatOverlay.ChildrenOfType<Container<DrawableChannel>>().Single().Child;
|
||||||
|
|
||||||
|
private ChannelListItem getChannelListItem(Channel channel) =>
|
||||||
|
chatOverlay.ChildrenOfType<ChannelListItem>().Single(item => item.Channel == channel);
|
||||||
|
|
||||||
|
private ChatTextBox chatOverlayTextBox =>
|
||||||
|
chatOverlay.ChildrenOfType<ChatTextBox>().Single();
|
||||||
|
|
||||||
|
private ChatOverlayTopBar chatOverlayTopBar =>
|
||||||
|
chatOverlay.ChildrenOfType<ChatOverlayTopBar>().Single();
|
||||||
|
|
||||||
|
private void clickDrawable(Drawable d)
|
||||||
|
{
|
||||||
|
InputManager.MoveMouseTo(d);
|
||||||
|
InputManager.Click(MouseButton.Left);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Message> createChannelMessages(Channel channel)
|
||||||
|
{
|
||||||
|
var message = new Message
|
||||||
|
{
|
||||||
|
ChannelId = channel.Id,
|
||||||
|
Content = $"Hello, this is a message in {channel.Name}",
|
||||||
|
Sender = testUser,
|
||||||
|
Timestamp = new DateTimeOffset(DateTime.Now),
|
||||||
|
};
|
||||||
|
return new List<Message> { message };
|
||||||
|
}
|
||||||
|
|
||||||
|
private Channel createPublicChannel(int id) => new Channel
|
||||||
|
{
|
||||||
|
Id = id,
|
||||||
|
Name = $"#channel-{id}",
|
||||||
|
Topic = $"We talk about the number {id} here",
|
||||||
|
Type = ChannelType.Public,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -25,14 +25,14 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
|||||||
public event Action<Channel>? OnRequestSelect;
|
public event Action<Channel>? OnRequestSelect;
|
||||||
public event Action<Channel>? OnRequestLeave;
|
public event Action<Channel>? OnRequestLeave;
|
||||||
|
|
||||||
|
public readonly Channel Channel;
|
||||||
|
|
||||||
public readonly BindableInt Mentions = new BindableInt();
|
public readonly BindableInt Mentions = new BindableInt();
|
||||||
|
|
||||||
public readonly BindableBool Unread = new BindableBool();
|
public readonly BindableBool Unread = new BindableBool();
|
||||||
|
|
||||||
public readonly BindableBool SelectorActive = new BindableBool();
|
public readonly BindableBool SelectorActive = new BindableBool();
|
||||||
|
|
||||||
private readonly Channel channel;
|
|
||||||
|
|
||||||
private Box hoverBox = null!;
|
private Box hoverBox = null!;
|
||||||
private Box selectBox = null!;
|
private Box selectBox = null!;
|
||||||
private OsuSpriteText text = null!;
|
private OsuSpriteText text = null!;
|
||||||
@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
|||||||
|
|
||||||
public ChannelListItem(Channel channel)
|
public ChannelListItem(Channel channel)
|
||||||
{
|
{
|
||||||
this.channel = channel;
|
Channel = channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -92,7 +92,7 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Text = channel.Name,
|
Text = Channel.Name,
|
||||||
Font = OsuFont.Torus.With(size: 17, weight: FontWeight.SemiBold),
|
Font = OsuFont.Torus.With(size: 17, weight: FontWeight.SemiBold),
|
||||||
Colour = colourProvider.Light3,
|
Colour = colourProvider.Light3,
|
||||||
Margin = new MarginPadding { Bottom = 2 },
|
Margin = new MarginPadding { Bottom = 2 },
|
||||||
@ -111,7 +111,7 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
|||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Margin = new MarginPadding { Right = 3 },
|
Margin = new MarginPadding { Right = 3 },
|
||||||
Action = () => OnRequestLeave?.Invoke(channel),
|
Action = () => OnRequestLeave?.Invoke(Channel),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -119,20 +119,16 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
Action = () => OnRequestSelect?.Invoke(channel);
|
Action = () => OnRequestSelect?.Invoke(Channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override void LoadComplete()
|
protected override void LoadComplete()
|
||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
selectedChannel.BindValueChanged(_ => updateSelectState(), true);
|
selectedChannel.BindValueChanged(_ => updateState(), true);
|
||||||
SelectorActive.BindValueChanged(_ => updateSelectState(), true);
|
SelectorActive.BindValueChanged(_ => updateState(), true);
|
||||||
|
Unread.BindValueChanged(_ => updateState(), true);
|
||||||
Unread.BindValueChanged(change =>
|
|
||||||
{
|
|
||||||
text.FadeColour(change.NewValue ? colourProvider.Content1 : colourProvider.Light3, 300, Easing.OutQuint);
|
|
||||||
}, true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
@ -151,10 +147,10 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
|||||||
|
|
||||||
private Drawable createIcon()
|
private Drawable createIcon()
|
||||||
{
|
{
|
||||||
if (channel.Type != ChannelType.PM)
|
if (Channel.Type != ChannelType.PM)
|
||||||
return Drawable.Empty();
|
return Drawable.Empty();
|
||||||
|
|
||||||
return new UpdateableAvatar(channel.Users.First(), isInteractive: false)
|
return new UpdateableAvatar(Channel.Users.First(), isInteractive: false)
|
||||||
{
|
{
|
||||||
Size = new Vector2(20),
|
Size = new Vector2(20),
|
||||||
Margin = new MarginPadding { Right = 5 },
|
Margin = new MarginPadding { Right = 5 },
|
||||||
@ -165,12 +161,19 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
private void updateSelectState()
|
private void updateState()
|
||||||
{
|
{
|
||||||
if (selectedChannel.Value == channel && !SelectorActive.Value)
|
bool selected = selectedChannel.Value == Channel && !SelectorActive.Value;
|
||||||
|
|
||||||
|
if (selected)
|
||||||
selectBox.FadeIn(300, Easing.OutQuint);
|
selectBox.FadeIn(300, Easing.OutQuint);
|
||||||
else
|
else
|
||||||
selectBox.FadeOut(200, Easing.OutQuint);
|
selectBox.FadeOut(200, Easing.OutQuint);
|
||||||
|
|
||||||
|
if (Unread.Value || selected)
|
||||||
|
text.FadeColour(colourProvider.Content1, 300, Easing.OutQuint);
|
||||||
|
else
|
||||||
|
text.FadeColour(colourProvider.Light3, 200, Easing.OutQuint);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,10 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
|||||||
|
|
||||||
private Box hoverBox = null!;
|
private Box hoverBox = null!;
|
||||||
private Box selectBox = null!;
|
private Box selectBox = null!;
|
||||||
|
private OsuSpriteText text = null!;
|
||||||
|
|
||||||
|
[Resolved]
|
||||||
|
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(OverlayColourProvider colourProvider)
|
private void load(OverlayColourProvider colourProvider)
|
||||||
@ -46,11 +50,11 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
|||||||
{
|
{
|
||||||
RelativeSizeAxes = Axes.Both,
|
RelativeSizeAxes = Axes.Both,
|
||||||
Padding = new MarginPadding { Left = 18, Right = 10 },
|
Padding = new MarginPadding { Left = 18, Right = 10 },
|
||||||
Child = new OsuSpriteText
|
Child = text = new OsuSpriteText
|
||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Text = "Add More Channels",
|
Text = "Add more channels",
|
||||||
Font = OsuFont.Torus.With(size: 17, weight: FontWeight.SemiBold),
|
Font = OsuFont.Torus.With(size: 17, weight: FontWeight.SemiBold),
|
||||||
Colour = colourProvider.Light3,
|
Colour = colourProvider.Light3,
|
||||||
Margin = new MarginPadding { Bottom = 2 },
|
Margin = new MarginPadding { Bottom = 2 },
|
||||||
@ -68,9 +72,15 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
|||||||
SelectorActive.BindValueChanged(selector =>
|
SelectorActive.BindValueChanged(selector =>
|
||||||
{
|
{
|
||||||
if (selector.NewValue)
|
if (selector.NewValue)
|
||||||
|
{
|
||||||
|
text.FadeColour(colourProvider.Content1, 300, Easing.OutQuint);
|
||||||
selectBox.FadeIn(300, Easing.OutQuint);
|
selectBox.FadeIn(300, Easing.OutQuint);
|
||||||
|
}
|
||||||
else
|
else
|
||||||
|
{
|
||||||
|
text.FadeColour(colourProvider.Light3, 200, Easing.OutQuint);
|
||||||
selectBox.FadeOut(200, Easing.OutQuint);
|
selectBox.FadeOut(200, Easing.OutQuint);
|
||||||
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
Action = () => SelectorActive.Value = true;
|
Action = () => SelectorActive.Value = true;
|
||||||
|
83
osu.Game/Overlays/Chat/ChatOverlayTopBar.cs
Normal file
83
osu.Game/Overlays/Chat/ChatOverlayTopBar.cs
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
// 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 osu.Framework.Allocation;
|
||||||
|
using osu.Framework.Extensions.Color4Extensions;
|
||||||
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
|
using osu.Framework.Graphics.Shapes;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
|
using osu.Framework.Graphics.Textures;
|
||||||
|
using osu.Framework.Input.Events;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
|
using osuTK;
|
||||||
|
using osuTK.Graphics;
|
||||||
|
|
||||||
|
namespace osu.Game.Overlays.Chat
|
||||||
|
{
|
||||||
|
public class ChatOverlayTopBar : Container
|
||||||
|
{
|
||||||
|
private Box background = null!;
|
||||||
|
|
||||||
|
private Color4 backgroundColour;
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load(OverlayColourProvider colourProvider, TextureStore textures)
|
||||||
|
{
|
||||||
|
Children = new Drawable[]
|
||||||
|
{
|
||||||
|
background = new Box
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
Colour = backgroundColour = colourProvider.Background3,
|
||||||
|
},
|
||||||
|
new GridContainer
|
||||||
|
{
|
||||||
|
RelativeSizeAxes = Axes.Both,
|
||||||
|
ColumnDimensions = new[]
|
||||||
|
{
|
||||||
|
new Dimension(GridSizeMode.Absolute, 50),
|
||||||
|
new Dimension(),
|
||||||
|
},
|
||||||
|
Content = new[]
|
||||||
|
{
|
||||||
|
new Drawable[]
|
||||||
|
{
|
||||||
|
new Sprite
|
||||||
|
{
|
||||||
|
Anchor = Anchor.Centre,
|
||||||
|
Origin = Anchor.Centre,
|
||||||
|
Texture = textures.Get("Icons/Hexacons/messaging"),
|
||||||
|
Size = new Vector2(18),
|
||||||
|
},
|
||||||
|
// Placeholder text
|
||||||
|
new OsuSpriteText
|
||||||
|
{
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Text = "osu!chat",
|
||||||
|
Font = OsuFont.Torus.With(size: 16, weight: FontWeight.SemiBold),
|
||||||
|
Margin = new MarginPadding { Bottom = 2f },
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
{
|
||||||
|
background.FadeColour(backgroundColour.Lighten(0.1f), 300, Easing.OutQuint);
|
||||||
|
return base.OnHover(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnHoverLost(HoverLostEvent e)
|
||||||
|
{
|
||||||
|
background.FadeColour(backgroundColour, 300, Easing.OutQuint);
|
||||||
|
base.OnHoverLost(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -11,7 +11,8 @@ using osu.Framework.Graphics.Containers;
|
|||||||
using osu.Framework.Graphics.Shapes;
|
using osu.Framework.Graphics.Shapes;
|
||||||
using osu.Framework.Graphics.Sprites;
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Online.Chat;
|
using osu.Game.Online.Chat;
|
||||||
using osuTK;
|
using osuTK;
|
||||||
|
|
||||||
@ -25,14 +26,19 @@ namespace osu.Game.Overlays.Chat
|
|||||||
|
|
||||||
public event Action<string>? OnSearchTermsChanged;
|
public event Action<string>? OnSearchTermsChanged;
|
||||||
|
|
||||||
|
public void TextBoxTakeFocus() => chatTextBox.TakeFocus();
|
||||||
|
|
||||||
|
public void TextBoxKillFocus() => chatTextBox.KillFocus();
|
||||||
|
|
||||||
[Resolved]
|
[Resolved]
|
||||||
private Bindable<Channel> currentChannel { get; set; } = null!;
|
private Bindable<Channel> currentChannel { get; set; } = null!;
|
||||||
|
|
||||||
private OsuTextFlowContainer chattingTextContainer = null!;
|
private Container chattingTextContainer = null!;
|
||||||
|
private OsuSpriteText chattingText = null!;
|
||||||
private Container searchIconContainer = null!;
|
private Container searchIconContainer = null!;
|
||||||
private ChatTextBox chatTextBox = null!;
|
private ChatTextBox chatTextBox = null!;
|
||||||
|
|
||||||
private const float chatting_text_width = 180;
|
private const float chatting_text_width = 240;
|
||||||
private const float search_icon_width = 40;
|
private const float search_icon_width = 40;
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -61,16 +67,20 @@ namespace osu.Game.Overlays.Chat
|
|||||||
{
|
{
|
||||||
new Drawable[]
|
new Drawable[]
|
||||||
{
|
{
|
||||||
chattingTextContainer = new OsuTextFlowContainer(t => t.Font = t.Font.With(size: 20))
|
chattingTextContainer = new Container
|
||||||
{
|
{
|
||||||
Masking = true,
|
|
||||||
Width = chatting_text_width,
|
|
||||||
Padding = new MarginPadding { Left = 10 },
|
|
||||||
RelativeSizeAxes = Axes.Y,
|
RelativeSizeAxes = Axes.Y,
|
||||||
TextAnchor = Anchor.CentreRight,
|
Width = chatting_text_width,
|
||||||
Anchor = Anchor.CentreLeft,
|
Masking = true,
|
||||||
Origin = Anchor.CentreLeft,
|
Padding = new MarginPadding { Right = 5 },
|
||||||
Colour = colourProvider.Background1,
|
Child = chattingText = new OsuSpriteText
|
||||||
|
{
|
||||||
|
Font = OsuFont.Torus.With(size: 20),
|
||||||
|
Colour = colourProvider.Background1,
|
||||||
|
Anchor = Anchor.CentreRight,
|
||||||
|
Origin = Anchor.CentreRight,
|
||||||
|
Truncate = true,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
searchIconContainer = new Container
|
searchIconContainer = new Container
|
||||||
{
|
{
|
||||||
@ -131,15 +141,15 @@ namespace osu.Game.Overlays.Chat
|
|||||||
switch (newChannel?.Type)
|
switch (newChannel?.Type)
|
||||||
{
|
{
|
||||||
case ChannelType.Public:
|
case ChannelType.Public:
|
||||||
chattingTextContainer.Text = $"chatting in {newChannel.Name}";
|
chattingText.Text = $"chatting in {newChannel.Name}";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case ChannelType.PM:
|
case ChannelType.PM:
|
||||||
chattingTextContainer.Text = $"chatting with {newChannel.Name}";
|
chattingText.Text = $"chatting with {newChannel.Name}";
|
||||||
break;
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
chattingTextContainer.Text = string.Empty;
|
chattingText.Text = string.Empty;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
@ -25,11 +25,11 @@ namespace osu.Game.Overlays.Chat.Listing
|
|||||||
public event Action<Channel>? OnRequestJoin;
|
public event Action<Channel>? OnRequestJoin;
|
||||||
public event Action<Channel>? OnRequestLeave;
|
public event Action<Channel>? OnRequestLeave;
|
||||||
|
|
||||||
public bool FilteringActive { get; set; }
|
public readonly Channel Channel;
|
||||||
public IEnumerable<string> FilterTerms => new[] { channel.Name, channel.Topic ?? string.Empty };
|
|
||||||
public bool MatchingFilter { set => this.FadeTo(value ? 1f : 0f, 100); }
|
|
||||||
|
|
||||||
private readonly Channel channel;
|
public bool FilteringActive { get; set; }
|
||||||
|
public IEnumerable<string> FilterTerms => new[] { Channel.Name, Channel.Topic ?? string.Empty };
|
||||||
|
public bool MatchingFilter { set => this.FadeTo(value ? 1f : 0f, 100); }
|
||||||
|
|
||||||
private Box hoverBox = null!;
|
private Box hoverBox = null!;
|
||||||
private SpriteIcon checkbox = null!;
|
private SpriteIcon checkbox = null!;
|
||||||
@ -47,7 +47,7 @@ namespace osu.Game.Overlays.Chat.Listing
|
|||||||
|
|
||||||
public ChannelListingItem(Channel channel)
|
public ChannelListingItem(Channel channel)
|
||||||
{
|
{
|
||||||
this.channel = channel;
|
Channel = channel;
|
||||||
}
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
@ -94,7 +94,7 @@ namespace osu.Game.Overlays.Chat.Listing
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Text = channel.Name,
|
Text = Channel.Name,
|
||||||
Font = OsuFont.Torus.With(size: text_size, weight: FontWeight.SemiBold),
|
Font = OsuFont.Torus.With(size: text_size, weight: FontWeight.SemiBold),
|
||||||
Margin = new MarginPadding { Bottom = 2 },
|
Margin = new MarginPadding { Bottom = 2 },
|
||||||
},
|
},
|
||||||
@ -102,7 +102,7 @@ namespace osu.Game.Overlays.Chat.Listing
|
|||||||
{
|
{
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Text = channel.Topic,
|
Text = Channel.Topic,
|
||||||
Font = OsuFont.Torus.With(size: text_size),
|
Font = OsuFont.Torus.With(size: text_size),
|
||||||
Margin = new MarginPadding { Bottom = 2 },
|
Margin = new MarginPadding { Bottom = 2 },
|
||||||
},
|
},
|
||||||
@ -134,7 +134,7 @@ namespace osu.Game.Overlays.Chat.Listing
|
|||||||
{
|
{
|
||||||
base.LoadComplete();
|
base.LoadComplete();
|
||||||
|
|
||||||
channelJoined = channel.Joined.GetBoundCopy();
|
channelJoined = Channel.Joined.GetBoundCopy();
|
||||||
channelJoined.BindValueChanged(change =>
|
channelJoined.BindValueChanged(change =>
|
||||||
{
|
{
|
||||||
const double duration = 500;
|
const double duration = 500;
|
||||||
@ -155,7 +155,7 @@ namespace osu.Game.Overlays.Chat.Listing
|
|||||||
}
|
}
|
||||||
}, true);
|
}, true);
|
||||||
|
|
||||||
Action = () => (channelJoined.Value ? OnRequestLeave : OnRequestJoin)?.Invoke(channel);
|
Action = () => (channelJoined.Value ? OnRequestLeave : OnRequestJoin)?.Invoke(Channel);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected override bool OnHover(HoverEvent e)
|
protected override bool OnHover(HoverEvent e)
|
||||||
|
314
osu.Game/Overlays/ChatOverlayV2.cs
Normal file
314
osu.Game/Overlays/ChatOverlayV2.cs
Normal file
@ -0,0 +1,314 @@
|
|||||||
|
// 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!;
|
||||||
|
private Container<DrawableChannel> currentChannelContainer = null!;
|
||||||
|
|
||||||
|
private readonly Bindable<float> chatHeight = new Bindable<float>();
|
||||||
|
|
||||||
|
private bool isDraggingTopBar;
|
||||||
|
private float dragStartChatHeight;
|
||||||
|
|
||||||
|
private const int transition_length = 500;
|
||||||
|
private const float default_chat_height = 0.4f;
|
||||||
|
private const float top_bar_height = 40;
|
||||||
|
private const float side_bar_width = 190;
|
||||||
|
private const float chat_bar_height = 60;
|
||||||
|
|
||||||
|
private readonly BindableBool selectorActive = new BindableBool();
|
||||||
|
|
||||||
|
[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>();
|
||||||
|
|
||||||
|
public ChatOverlayV2()
|
||||||
|
{
|
||||||
|
Height = default_chat_height;
|
||||||
|
|
||||||
|
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 };
|
||||||
|
|
||||||
|
Anchor = Anchor.BottomCentre;
|
||||||
|
Origin = Anchor.BottomCentre;
|
||||||
|
}
|
||||||
|
|
||||||
|
[BackgroundDependencyLoader]
|
||||||
|
private void load()
|
||||||
|
{
|
||||||
|
// Required for the pop in/out animation
|
||||||
|
RelativePositionAxes = Axes.Both;
|
||||||
|
|
||||||
|
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 },
|
||||||
|
SelectorActive = { BindTarget = selectorActive },
|
||||||
|
},
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
currentChannelContainer = new Container<DrawableChannel>
|
||||||
|
{
|
||||||
|
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 },
|
||||||
|
ShowSearch = { BindTarget = selectorActive },
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void LoadComplete()
|
||||||
|
{
|
||||||
|
base.LoadComplete();
|
||||||
|
|
||||||
|
loading.Show();
|
||||||
|
|
||||||
|
config.BindWith(OsuSetting.ChatDisplayHeight, chatHeight);
|
||||||
|
|
||||||
|
chatHeight.BindValueChanged(height => { Height = height.NewValue; }, true);
|
||||||
|
|
||||||
|
currentChannel.BindTo(channelManager.CurrentChannel);
|
||||||
|
channelManager.CurrentChannel.BindValueChanged(currentChannelChanged, true);
|
||||||
|
channelManager.JoinedChannels.BindCollectionChanged(joinedChannelsChanged, true);
|
||||||
|
channelManager.AvailableChannels.BindCollectionChanged(availableChannelsChanged, true);
|
||||||
|
|
||||||
|
channelList.OnRequestSelect += channel =>
|
||||||
|
{
|
||||||
|
// Manually selecting a channel should dismiss the selector
|
||||||
|
selectorActive.Value = false;
|
||||||
|
channelManager.CurrentChannel.Value = channel;
|
||||||
|
};
|
||||||
|
channelList.OnRequestLeave += channel => channelManager.LeaveChannel(channel);
|
||||||
|
|
||||||
|
channelListing.OnRequestJoin += channel => channelManager.JoinChannel(channel);
|
||||||
|
channelListing.OnRequestLeave += channel => channelManager.LeaveChannel(channel);
|
||||||
|
|
||||||
|
textBar.OnSearchTermsChanged += searchTerms => channelListing.SearchTerm = searchTerms;
|
||||||
|
textBar.OnChatMessageCommitted += handleChatMessage;
|
||||||
|
|
||||||
|
selectorActive.BindValueChanged(v => channelListing.State.Value = v.NewValue ? Visibility.Visible : Visibility.Hidden, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <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;
|
||||||
|
}
|
||||||
|
|
||||||
|
selectorActive.Value = false;
|
||||||
|
|
||||||
|
channel.HighlightedMessage.Value = message;
|
||||||
|
|
||||||
|
Show();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override bool OnDragStart(DragStartEvent e)
|
||||||
|
{
|
||||||
|
isDraggingTopBar = topBar.IsHovered;
|
||||||
|
|
||||||
|
if (!isDraggingTopBar)
|
||||||
|
return base.OnDragStart(e);
|
||||||
|
|
||||||
|
dragStartChatHeight = chatHeight.Value;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDrag(DragEvent e)
|
||||||
|
{
|
||||||
|
if (!isDraggingTopBar)
|
||||||
|
return;
|
||||||
|
|
||||||
|
float targetChatHeight = dragStartChatHeight - (e.MousePosition.Y - e.MouseDownPosition.Y) / Parent.DrawSize.Y;
|
||||||
|
chatHeight.Value = targetChatHeight;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnDragEnd(DragEndEvent e)
|
||||||
|
{
|
||||||
|
isDraggingTopBar = false;
|
||||||
|
base.OnDragEnd(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PopIn()
|
||||||
|
{
|
||||||
|
base.PopIn();
|
||||||
|
|
||||||
|
this.MoveToY(0, transition_length, Easing.OutQuint);
|
||||||
|
this.FadeIn(transition_length, Easing.OutQuint);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void PopOut()
|
||||||
|
{
|
||||||
|
base.PopOut();
|
||||||
|
|
||||||
|
this.MoveToY(Height, transition_length, Easing.InSine);
|
||||||
|
this.FadeOut(transition_length, Easing.InSine);
|
||||||
|
|
||||||
|
textBar.TextBoxKillFocus();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnFocus(FocusEvent e)
|
||||||
|
{
|
||||||
|
textBar.TextBoxTakeFocus();
|
||||||
|
base.OnFocus(e);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void currentChannelChanged(ValueChangedEvent<Channel> channel)
|
||||||
|
{
|
||||||
|
Channel? newChannel = channel.NewValue;
|
||||||
|
|
||||||
|
loading.Show();
|
||||||
|
|
||||||
|
// Channel is null when leaving the currently selected channel
|
||||||
|
if (newChannel == null)
|
||||||
|
{
|
||||||
|
// Find another channel to switch to
|
||||||
|
newChannel = channelManager.JoinedChannels.FirstOrDefault(c => c != channel.OldValue);
|
||||||
|
|
||||||
|
if (newChannel == null)
|
||||||
|
selectorActive.Value = true;
|
||||||
|
else
|
||||||
|
currentChannel.Value = newChannel;
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
LoadComponentAsync(new DrawableChannel(newChannel), loaded =>
|
||||||
|
{
|
||||||
|
currentChannelContainer.Clear();
|
||||||
|
currentChannelContainer.Add(loaded);
|
||||||
|
loading.Hide();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void joinedChannelsChanged(object sender, NotifyCollectionChangedEventArgs args)
|
||||||
|
{
|
||||||
|
switch (args.Action)
|
||||||
|
{
|
||||||
|
case NotifyCollectionChangedAction.Add:
|
||||||
|
IEnumerable<Channel> joinedChannels = filterChannels(args.NewItems);
|
||||||
|
foreach (var channel in joinedChannels)
|
||||||
|
channelList.AddChannel(channel);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case NotifyCollectionChangedAction.Remove:
|
||||||
|
IEnumerable<Channel> leftChannels = filterChannels(args.OldItems);
|
||||||
|
foreach (var channel in leftChannels)
|
||||||
|
channelList.RemoveChannel(channel);
|
||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user