mirror of
https://github.com/ppy/osu.git
synced 2024-12-15 06:52:56 +08:00
Implement basic layout and behaviour of new chat overlay
Provides initial implementation of new chat overlay in component `ChatOverlayV2`. Contains only the basic functionality required for a functioning chat overlay according to the new design with the intent of added the rest of the functionality in subsequent PRs. Backports existing tests for the current chat overlay except for ones testing keyboard shortcuts (since they haven't been added) and tab closing behaviour (since no tabs).
This commit is contained in:
parent
9fb10ca852
commit
de393f735f
365
osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs
Normal file
365
osu.Game.Tests/Visual/Online/TestSceneChatOverlayV2.cs
Normal file
@ -0,0 +1,365 @@
|
||||
// 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.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.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.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Online
|
||||
{
|
||||
[TestFixture]
|
||||
public class TestSceneChatOverlayV2 : OsuManualInputManagerTestScene
|
||||
{
|
||||
private ChatOverlayV2 chatOverlay;
|
||||
private ChannelManager channelManager;
|
||||
|
||||
private readonly APIUser testUser;
|
||||
private readonly Channel testPMChannel;
|
||||
private readonly Channel[] testChannels;
|
||||
private Channel testChannel1 => testChannels[0];
|
||||
private Channel testChannel2 => testChannels[1];
|
||||
|
||||
public TestSceneChatOverlayV2()
|
||||
{
|
||||
testUser = new APIUser { Username = "test user", Id = 5071479 };
|
||||
testPMChannel = new Channel(testUser);
|
||||
testChannels = Enumerable.Range(1, 10).Select(createPublicChannel).ToArray();
|
||||
}
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
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 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'", () => chatOverlay.ChildrenOfType<ChatTextBox>().Single().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 == testPMChannel);
|
||||
AddAssert("PM channel is visibile", () => currentDrawableChannel.Channel == testPMChannel);
|
||||
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 == testPMChannel);
|
||||
AddAssert("PM channel is visibile", () => currentDrawableChannel.Channel == testPMChannel);
|
||||
}
|
||||
|
||||
[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));
|
||||
}
|
||||
|
||||
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 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>? OnRequestLeave;
|
||||
|
||||
public readonly Channel Channel;
|
||||
|
||||
public readonly BindableInt Mentions = new BindableInt();
|
||||
|
||||
public readonly BindableBool Unread = new BindableBool();
|
||||
|
||||
public readonly BindableBool SelectorActive = new BindableBool();
|
||||
|
||||
private readonly Channel channel;
|
||||
|
||||
private Box hoverBox = null!;
|
||||
private Box selectBox = null!;
|
||||
private OsuSpriteText text = null!;
|
||||
@ -46,7 +46,7 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
|
||||
public ChannelListItem(Channel channel)
|
||||
{
|
||||
this.channel = channel;
|
||||
Channel = channel;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -92,7 +92,7 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Text = channel.Name,
|
||||
Text = Channel.Name,
|
||||
Font = OsuFont.Torus.With(size: 17, weight: FontWeight.SemiBold),
|
||||
Colour = colourProvider.Light3,
|
||||
Margin = new MarginPadding { Bottom = 2 },
|
||||
@ -111,7 +111,7 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Margin = new MarginPadding { Right = 3 },
|
||||
Action = () => OnRequestLeave?.Invoke(channel),
|
||||
Action = () => OnRequestLeave?.Invoke(Channel),
|
||||
}
|
||||
}
|
||||
},
|
||||
@ -119,7 +119,7 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
},
|
||||
};
|
||||
|
||||
Action = () => OnRequestSelect?.Invoke(channel);
|
||||
Action = () => OnRequestSelect?.Invoke(Channel);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -151,10 +151,10 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
|
||||
private Drawable createIcon()
|
||||
{
|
||||
if (channel.Type != ChannelType.PM)
|
||||
if (Channel.Type != ChannelType.PM)
|
||||
return Drawable.Empty();
|
||||
|
||||
return new UpdateableAvatar(channel.Users.First(), isInteractive: false)
|
||||
return new UpdateableAvatar(Channel.Users.First(), isInteractive: false)
|
||||
{
|
||||
Size = new Vector2(20),
|
||||
Margin = new MarginPadding { Right = 5 },
|
||||
@ -167,7 +167,7 @@ namespace osu.Game.Overlays.Chat.ChannelList
|
||||
|
||||
private void updateSelectState()
|
||||
{
|
||||
if (selectedChannel.Value == channel && !SelectorActive.Value)
|
||||
if (selectedChannel.Value == Channel && !SelectorActive.Value)
|
||||
selectBox.FadeIn(300, Easing.OutQuint);
|
||||
else
|
||||
selectBox.FadeOut(200, Easing.OutQuint);
|
||||
|
67
osu.Game/Overlays/Chat/ChatOverlayTopBar.cs
Normal file
67
osu.Game/Overlays/Chat/ChatOverlayTopBar.cs
Normal file
@ -0,0 +1,67 @@
|
||||
// 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.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.Textures;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Chat
|
||||
{
|
||||
public class ChatOverlayTopBar : Container
|
||||
{
|
||||
// IsHovered is used by overlay
|
||||
public override bool HandlePositionalInput => true;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider, TextureStore textures)
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = 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 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -25,11 +25,11 @@ namespace osu.Game.Overlays.Chat.Listing
|
||||
public event Action<Channel>? OnRequestJoin;
|
||||
public event Action<Channel>? OnRequestLeave;
|
||||
|
||||
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); }
|
||||
public readonly Channel Channel;
|
||||
|
||||
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 SpriteIcon checkbox = null!;
|
||||
@ -47,7 +47,7 @@ namespace osu.Game.Overlays.Chat.Listing
|
||||
|
||||
public ChannelListingItem(Channel channel)
|
||||
{
|
||||
this.channel = channel;
|
||||
Channel = channel;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -94,7 +94,7 @@ namespace osu.Game.Overlays.Chat.Listing
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Text = channel.Name,
|
||||
Text = Channel.Name,
|
||||
Font = OsuFont.Torus.With(size: text_size, weight: FontWeight.SemiBold),
|
||||
Margin = new MarginPadding { Bottom = 2 },
|
||||
},
|
||||
@ -102,7 +102,7 @@ namespace osu.Game.Overlays.Chat.Listing
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Text = channel.Topic,
|
||||
Text = Channel.Topic,
|
||||
Font = OsuFont.Torus.With(size: text_size),
|
||||
Margin = new MarginPadding { Bottom = 2 },
|
||||
},
|
||||
@ -134,7 +134,7 @@ namespace osu.Game.Overlays.Chat.Listing
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
channelJoined = channel.Joined.GetBoundCopy();
|
||||
channelJoined = Channel.Joined.GetBoundCopy();
|
||||
channelJoined.BindValueChanged(change =>
|
||||
{
|
||||
const double duration = 500;
|
||||
@ -155,7 +155,7 @@ namespace osu.Game.Overlays.Chat.Listing
|
||||
}
|
||||
}, true);
|
||||
|
||||
Action = () => (channelJoined.Value ? OnRequestLeave : OnRequestJoin)?.Invoke(channel);
|
||||
Action = () => (channelJoined.Value ? OnRequestLeave : OnRequestJoin)?.Invoke(Channel);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
|
293
osu.Game/Overlays/ChatOverlayV2.cs
Normal file
293
osu.Game/Overlays/ChatOverlayV2.cs
Normal file
@ -0,0 +1,293 @@
|
||||
// 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 Bindable<float>? chatHeight;
|
||||
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>();
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
// Width = 0.85f; // Matches OnlineOverlay
|
||||
Height = default_chat_height;
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
RelativePositionAxes = Axes.Both;
|
||||
Anchor = Anchor.BottomCentre;
|
||||
Origin = Anchor.BottomCentre;
|
||||
Masking = true;
|
||||
CornerRadius = 7f;
|
||||
Margin = new MarginPadding { Bottom = -10 };
|
||||
Padding = new MarginPadding { Bottom = 10 };
|
||||
|
||||
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();
|
||||
|
||||
chatHeight = config.GetBindable<float>(OsuSetting.ChatDisplayHeight).GetBoundCopy();
|
||||
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()
|
||||
{
|
||||
this.MoveToY(0, transition_length, Easing.OutQuint);
|
||||
this.FadeIn(transition_length, Easing.OutQuint);
|
||||
base.PopIn();
|
||||
}
|
||||
|
||||
protected override void PopOut()
|
||||
{
|
||||
this.MoveToY(Height, transition_length, Easing.InSine);
|
||||
this.FadeOut(transition_length, Easing.InSine);
|
||||
base.PopOut();
|
||||
}
|
||||
|
||||
private void currentChannelChanged(ValueChangedEvent<Channel> e)
|
||||
{
|
||||
Channel? newChannel = e.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(chan => chan != e.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