From 6c8cc9728f6f7adcd02e71063c2c9f9a38dd9f15 Mon Sep 17 00:00:00 2001 From: David Zhao Date: Tue, 25 Jun 2019 19:52:31 +0900 Subject: [PATCH] fix channel selector not being closed --- .../TestSceneChatOverlayScenarios.cs | 190 ++++++++++++++++++ .../Overlays/Chat/Tabs/ChannelTabControl.cs | 6 +- osu.Game/Overlays/ChatOverlay.cs | 74 +++---- 3 files changed, 231 insertions(+), 39 deletions(-) create mode 100644 osu.Game.Tests/Visual/UserInterface/TestSceneChatOverlayScenarios.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneChatOverlayScenarios.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneChatOverlayScenarios.cs new file mode 100644 index 0000000000..b605f5f519 --- /dev/null +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneChatOverlayScenarios.cs @@ -0,0 +1,190 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Testing; +using osu.Game.Online.Chat; +using osu.Game.Overlays; +using osu.Game.Overlays.Chat.Tabs; +using osu.Game.Users; +using osuTK; +using osuTK.Input; + +namespace osu.Game.Tests.Visual.UserInterface +{ + public class TestSceneChatOverlayScenarios : ManualInputManagerTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ChannelTabControl), + typeof(ChannelTabItem), + typeof(ChatOverlay), + }; + + private TestChatOverlay chatOverlay; + + [Cached] + private ChannelManager channelManager = new ChannelManager(); + + private Channel channel1; + private Channel channel2; + + [BackgroundDependencyLoader] + private void load() + { + var availableChannels = (BindableList)channelManager.AvailableChannels; + + availableChannels.Add(channel1 = new Channel(new User()) { Name = "test1" }); + availableChannels.Add(channel2 = new Channel(new User()) { Name = "test2" }); + + Add(chatOverlay = new TestChatOverlay + { + RelativeSizeAxes = Axes.Both, + Size = new Vector2(1) + }); + } + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Leave channels", () => + { + channelManager.LeaveChannel(channel1); + channelManager.LeaveChannel(channel2); + }); + AddStep("Hide chat", () => chatOverlay.Hide()); + } + + /// + /// Test that if no maps are added, the channel selector is also toggled when is toggled. + /// Also check that both are properly closed when toggling again. + /// + [Test] + public void TestToggleChatWithNoChannelsJoined() + { + AddStep("Toggle chat overlay", () => chatOverlay.Show()); + AddAssert("Channel selection overlay was toggled", () => chatOverlay.SelectionOverlayState == Visibility.Visible); + AddAssert("Chat overlay was shown", () => chatOverlay.State.Value == Visibility.Visible); + AddStep("Close chat overlay", () => chatOverlay.Hide()); + AddAssert("Channel selection overlay was hidden", () => chatOverlay.SelectionOverlayState == Visibility.Hidden); + AddAssert("Chat overlay was hidden", () => chatOverlay.State.Value == Visibility.Hidden); + } + + [Test] + public void TestToggleChatWithChannelJoined() + { + AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); + AddStep("Toggle chat overlay", () => chatOverlay.Show()); + AddAssert("Channel selection overlay was not toggled", () => chatOverlay.SelectionOverlayState == Visibility.Hidden); + AddAssert("Chat overlay was shown", () => chatOverlay.State.Value == Visibility.Visible); + AddStep("Close chat overlay", () => chatOverlay.Hide()); + AddAssert("Channel selection overlay was hidden", () => chatOverlay.SelectionOverlayState == Visibility.Hidden); + AddAssert("Chat overlay was hidden", () => chatOverlay.State.Value == Visibility.Hidden); + } + + /// + /// When a channel is joined and no previous channels are joined, the channel that was joined will be selected. + /// Channel selector closes when a new channel is selected. This is blocked for this scenario. + /// This test expects that the channel selection overlay remains open for this reason. + /// + [Test] + public void TestJoinChannelWhileOpen() + { + AddStep("Toggle chat overlay", () => chatOverlay.Show()); + AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); + AddAssert("Current channel is channel 1", () => channelManager.CurrentChannel.Value == channel1); + AddAssert("Channel selection overlay remained open", () => chatOverlay.SelectionOverlayState == Visibility.Visible); + } + + [Test] + public void TestTabbingAwayClosesSelector() + { + AddStep("Toggle chat overlay", () => chatOverlay.Show()); + AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); + AddStep("Join channel 2", () => channelManager.JoinChannel(channel2)); + + // There is currently no way to map a tab drawable to its respective value at this level, so this test relies on the tab's location in AvailableTabs + AddStep("Switch to channel 2", () => clickDrawable(chatOverlay.AvailableTabs.First())); + AddAssert("Current channel is channel 2", () => channelManager.CurrentChannel.Value == channel2); + AddAssert("Channel selector was closed", () => chatOverlay.SelectionOverlayState == Visibility.Hidden); + } + + /// + /// When the current channel is closed, the next available channel will be selected. + /// Channel selector closes when a new channel is selected. This is blocked for this scenario. + /// This test expects that the channel selection overlay remains open for this reason. + /// + [Test] + public void TestCloseChannelWhileSelectorOpen() + { + AddStep("Toggle chat overlay", () => chatOverlay.Show()); + AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); + AddStep("Join channel 2", () => channelManager.JoinChannel(channel2)); + AddAssert("Current channel is channel 1", () => channelManager.CurrentChannel.Value == channel1); + AddStep("Close channel 1", () => clickDrawable(chatOverlay.AvailableTabs.Last().CloseButton.Child)); + AddAssert("Current channel is channel 2", () => channelManager.CurrentChannel.Value == channel2); + AddAssert("Channel selection overlay remained open", () => chatOverlay.SelectionOverlayState == Visibility.Visible); + } + + [Test] + public void TestCloseChannelWhileSelectorClosed() + { + AddStep("Toggle chat overlay", () => chatOverlay.Show()); + AddStep("Join channel 1", () => channelManager.JoinChannel(channel1)); + AddStep("Join channel 2", () => channelManager.JoinChannel(channel2)); + AddStep("Switch to channel 2", () => clickDrawable(chatOverlay.AvailableTabs.First())); + AddStep("Close channel 2", () => clickDrawable(chatOverlay.AvailableTabs.First().CloseButton.Child)); + AddAssert("Selector remained closed", () => chatOverlay.SelectionOverlayState == Visibility.Hidden); + AddAssert("Current channel is channel 2", () => channelManager.CurrentChannel.Value == channel1); + AddStep("Close channel 1", () => clickDrawable(chatOverlay.AvailableTabs.First().CloseButton.Child)); + AddAssert("Channel selection overlay was toggled", () => chatOverlay.SelectionOverlayState == Visibility.Visible); + } + + private void clickDrawable(Drawable d) + { + InputManager.MoveMouseTo(d); + InputManager.Click(MouseButton.Left); + } + + private class TestChatOverlay : ChatOverlay + { + public Visibility SelectionOverlayState => ChannelSelectionOverlay.State.Value; + + protected override ChannelTabControl CreateChannelTabControl() => new TestTabControl(); + + public IEnumerable AvailableTabs => ((TestTabControl)ChannelTabControl).AvailableTabs(); + } + + private class TestTabControl : ChannelTabControl + { + protected override TabItem CreateTabItem(Channel value) => new TestChannelTabItem(value) { OnRequestClose = TabCloseRequested }; + + public IEnumerable AvailableTabs() + { + foreach (var tab in TabContainer) + { + if (!(tab is ChannelSelectorTabItem)) + yield return (TestChannelTabItem)tab; + } + } + } + + private class TestChannelTabItem : PrivateChannelTabItem + { + public TestChannelTabItem(Channel channel) + : base(channel) + { + } + + public new ClickableContainer CloseButton => base.CloseButton; + } + } +} diff --git a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs index fafcb0a72d..b96cb27767 100644 --- a/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs +++ b/osu.Game/Overlays/Chat/Tabs/ChannelTabControl.cs @@ -57,10 +57,10 @@ namespace osu.Game.Overlays.Chat.Tabs switch (value.Type) { default: - return new ChannelTabItem(value) { OnRequestClose = tabCloseRequested }; + return new ChannelTabItem(value) { OnRequestClose = TabCloseRequested }; case ChannelType.PM: - return new PrivateChannelTabItem(value) { OnRequestClose = tabCloseRequested }; + return new PrivateChannelTabItem(value) { OnRequestClose = TabCloseRequested }; } } @@ -103,7 +103,7 @@ namespace osu.Game.Overlays.Chat.Tabs selectorTab.Active.Value = false; } - private void tabCloseRequested(TabItem tab) + protected void TabCloseRequested(TabItem tab) { int totalTabs = TabContainer.Count - 1; // account for selectorTab int currentIndex = MathHelper.Clamp(TabContainer.IndexOf(tab), 1, totalTabs); diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index fce9862e8e..475d691e7c 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -45,7 +45,9 @@ namespace osu.Game.Overlays public const float TAB_AREA_HEIGHT = 50; - private ChannelTabControl channelTabControl; + protected ChannelTabControl ChannelTabControl; + + protected virtual ChannelTabControl CreateChannelTabControl() => new ChannelTabControl(); private Container chatContainer; private TabsArea tabsArea; @@ -55,9 +57,10 @@ namespace osu.Game.Overlays public Bindable ChatHeight { get; set; } private Container channelSelectionContainer; - private ChannelSelectionOverlay channelSelectionOverlay; + protected ChannelSelectionOverlay ChannelSelectionOverlay; - public override bool Contains(Vector2 screenSpacePos) => chatContainer.ReceivePositionalInputAt(screenSpacePos) || (channelSelectionOverlay.State.Value == Visibility.Visible && channelSelectionOverlay.ReceivePositionalInputAt(screenSpacePos)); + public override bool Contains(Vector2 screenSpacePos) => chatContainer.ReceivePositionalInputAt(screenSpacePos) + || (ChannelSelectionOverlay.State.Value == Visibility.Visible && ChannelSelectionOverlay.ReceivePositionalInputAt(screenSpacePos)); public ChatOverlay() { @@ -81,7 +84,7 @@ namespace osu.Game.Overlays Masking = true, Children = new[] { - channelSelectionOverlay = new ChannelSelectionOverlay + ChannelSelectionOverlay = new ChannelSelectionOverlay { RelativeSizeAxes = Axes.Both, }, @@ -154,31 +157,26 @@ namespace osu.Game.Overlays RelativeSizeAxes = Axes.Both, Colour = Color4.Black, }, - channelTabControl = new ChannelTabControl - { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - RelativeSizeAxes = Axes.Both, - OnRequestLeave = channelManager.LeaveChannel - }, + ChannelTabControl = CreateChannelTabControl().With(d => + { + d.Anchor = Anchor.BottomLeft; + d.Origin = Anchor.BottomLeft; + d.RelativeSizeAxes = Axes.Both; + d.OnRequestLeave = channelManager.LeaveChannel; + } + ), } }, }, }, }; - channelTabControl.Current.ValueChanged += current => channelManager.CurrentChannel.Value = current.NewValue; - channelTabControl.ChannelSelectorActive.ValueChanged += active => channelSelectionOverlay.State.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden; - channelSelectionOverlay.State.ValueChanged += state => + ChannelTabControl.Current.ValueChanged += current => channelManager.CurrentChannel.Value = current.NewValue; + ChannelTabControl.ChannelSelectorActive.ValueChanged += active => ChannelSelectionOverlay.State.Value = active.NewValue ? Visibility.Visible : Visibility.Hidden; + ChannelSelectionOverlay.State.ValueChanged += state => { - if (state.NewValue == Visibility.Hidden && channelManager.JoinedChannels.Count == 0) - { - channelSelectionOverlay.Show(); - Hide(); - return; - } - - channelTabControl.ChannelSelectorActive.Value = state.NewValue == Visibility.Visible; + // Propagate the visibility state to ChannelSelectorActive + ChannelTabControl.ChannelSelectorActive.Value = state.NewValue == Visibility.Visible; if (state.NewValue == Visibility.Visible) { @@ -190,8 +188,8 @@ namespace osu.Game.Overlays textbox.HoldFocus = true; }; - channelSelectionOverlay.OnRequestJoin = channel => channelManager.JoinChannel(channel); - channelSelectionOverlay.OnRequestLeave = channelManager.LeaveChannel; + ChannelSelectionOverlay.OnRequestJoin = channel => channelManager.JoinChannel(channel); + ChannelSelectionOverlay.OnRequestLeave = channelManager.LeaveChannel; ChatHeight = config.GetBindable(OsuSetting.ChatDisplayHeight); ChatHeight.ValueChanged += height => @@ -217,11 +215,11 @@ namespace osu.Game.Overlays channelManager.JoinedChannels.ItemsAdded += onChannelAddedToJoinedChannels; channelManager.JoinedChannels.ItemsRemoved += onChannelRemovedFromJoinedChannels; foreach (Channel channel in channelManager.JoinedChannels) - channelTabControl.AddChannel(channel); + ChannelTabControl.AddChannel(channel); channelManager.AvailableChannels.ItemsAdded += availableChannelsChanged; channelManager.AvailableChannels.ItemsRemoved += availableChannelsChanged; - channelSelectionOverlay.UpdateAvailableChannels(channelManager.AvailableChannels); + ChannelSelectionOverlay.UpdateAvailableChannels(channelManager.AvailableChannels); currentChannel = channelManager.CurrentChannel.GetBoundCopy(); currentChannel.BindValueChanged(currentChannelChanged, true); @@ -236,7 +234,7 @@ namespace osu.Game.Overlays { textbox.Current.Disabled = true; currentChannelContainer.Clear(false); - channelSelectionOverlay.Show(); + ChannelSelectionOverlay.Show(); return; } @@ -245,8 +243,8 @@ namespace osu.Game.Overlays textbox.Current.Disabled = e.NewValue.ReadOnly; - if (channelTabControl.Current.Value != e.NewValue) - Scheduler.Add(() => channelTabControl.Current.Value = e.NewValue); + if (ChannelTabControl.Current.Value != e.NewValue) + Scheduler.Add(() => ChannelTabControl.Current.Value = e.NewValue); var loaded = loadedChannels.Find(d => d.Channel == e.NewValue); @@ -294,7 +292,7 @@ namespace osu.Game.Overlays double targetChatHeight = startDragChatHeight - (e.MousePosition.Y - e.MouseDownPosition.Y) / Parent.DrawSize.Y; // If the channel selection screen is shown, mind its minimum height - if (channelSelectionOverlay.State.Value == Visibility.Visible && targetChatHeight > 1f - channel_selection_min_height) + if (ChannelSelectionOverlay.State.Value == Visibility.Visible && targetChatHeight > 1f - channel_selection_min_height) targetChatHeight = 1f - channel_selection_min_height; ChatHeight.Value = targetChatHeight; @@ -311,9 +309,9 @@ namespace osu.Game.Overlays private void selectTab(int index) { - var channel = channelTabControl.Items.Skip(index).FirstOrDefault(); + var channel = ChannelTabControl.Items.Skip(index).FirstOrDefault(); if (channel != null && !(channel is ChannelSelectorTabItem.ChannelSelectorTabChannel)) - channelTabControl.Current.Value = channel; + ChannelTabControl.Current.Value = channel; } protected override bool OnKeyDown(KeyDownEvent e) @@ -358,6 +356,10 @@ namespace osu.Game.Overlays this.FadeIn(transition_length, Easing.OutQuint); textbox.HoldFocus = true; + + if (channelManager.CurrentChannel.Value == null || channelManager.CurrentChannel.Value is ChannelSelectorTabItem.ChannelSelectorTabChannel) + ChannelSelectionOverlay.Show(); + base.PopIn(); } @@ -366,7 +368,7 @@ namespace osu.Game.Overlays this.MoveToY(Height, transition_length, Easing.InSine); this.FadeOut(transition_length, Easing.InSine); - channelSelectionOverlay.Hide(); + ChannelSelectionOverlay.Hide(); textbox.HoldFocus = false; base.PopOut(); @@ -375,20 +377,20 @@ namespace osu.Game.Overlays private void onChannelAddedToJoinedChannels(IEnumerable channels) { foreach (Channel channel in channels) - channelTabControl.AddChannel(channel); + ChannelTabControl.AddChannel(channel); } private void onChannelRemovedFromJoinedChannels(IEnumerable channels) { foreach (Channel channel in channels) { - channelTabControl.RemoveChannel(channel); + ChannelTabControl.RemoveChannel(channel); loadedChannels.Remove(loadedChannels.Find(c => c.Channel == channel)); } } private void availableChannelsChanged(IEnumerable channels) - => channelSelectionOverlay.UpdateAvailableChannels(channelManager.AvailableChannels); + => ChannelSelectionOverlay.UpdateAvailableChannels(channelManager.AvailableChannels); protected override void Dispose(bool isDisposing) {