From a3477c3841d4c85f81a5fe5546e695c8ebbd836c Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Tue, 8 Mar 2022 22:30:58 +0000 Subject: [PATCH] Implement `ChannelListing` for new chat design Adds components `ChannelListing` and `ChannelListing` item with visual test. Essentially a more simplified version of the existing `ChannelSelectionOverlay` component. Correctly implements `IFilterable` behaviour to filter child channel items. Channel joined state is based on the underlying `Joined` bindable of the `Channel` class. Channel join/leave events are exposed via `OnRequestJoin` and `OnRequestLeave` events which should be handled by parent component. Requires a cached `OverlayColourScheme` instance to be provided by the parent overlay component when added. --- .../Online/NewChat/TestSceneChannelListing.cs | 90 +++++++++ osu.Game/Overlays/NewChat/ChannelListing.cs | 85 +++++++++ .../Overlays/NewChat/ChannelListingItem.cs | 175 ++++++++++++++++++ 3 files changed, 350 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.cs create mode 100644 osu.Game/Overlays/NewChat/ChannelListing.cs create mode 100644 osu.Game/Overlays/NewChat/ChannelListingItem.cs diff --git a/osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.cs b/osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.cs new file mode 100644 index 0000000000..cbec23a305 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/NewChat/TestSceneChannelListing.cs @@ -0,0 +1,90 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Framework.Utils; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Chat; +using osu.Game.Overlays; +using osu.Game.Overlays.NewChat; +using osuTK; + +namespace osu.Game.Tests.Visual.Online.NewChat +{ + [TestFixture] + public class TestSceneChannelListing : OsuTestScene + { + [Cached] + private readonly OverlayColourProvider overlayColours = new OverlayColourProvider(OverlayColourScheme.Pink); + + private SearchTextBox search; + private ChannelListing listing; + + [SetUp] + public void SetUp() + { + Schedule(() => + { + Children = new Drawable[] + { + search = new SearchTextBox + { + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + Width = 300, + Margin = new MarginPadding { Top = 100 }, + }, + listing = new ChannelListing + { + Size = new Vector2(800, 400), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + }; + listing.Show(); + search.Current.ValueChanged += term => listing.SearchTerm = term.NewValue; + }); + } + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("Add Join/Leave callbacks", () => + { + listing.OnRequestJoin += channel => channel.Joined.Value = true; + listing.OnRequestLeave += channel => channel.Joined.Value = false; + }); + } + + [Test] + public void TestAddRandomChannels() + { + AddStep("Add Random Channels", () => + { + listing.UpdateAvailableChannels(createRandomChannels(20)); + }); + } + + private Channel createRandomChannel() + { + var id = RNG.Next(0, 10000); + return new Channel + { + Name = $"#channel-{id}", + Topic = RNG.Next(4) < 3 ? $"We talk about the number {id} here" : null, + Type = ChannelType.Public, + Id = id, + }; + } + + private List createRandomChannels(int num) + => Enumerable.Range(0, num) + .Select(_ => createRandomChannel()) + .ToList(); + } +} diff --git a/osu.Game/Overlays/NewChat/ChannelListing.cs b/osu.Game/Overlays/NewChat/ChannelListing.cs new file mode 100644 index 0000000000..79371d0fed --- /dev/null +++ b/osu.Game/Overlays/NewChat/ChannelListing.cs @@ -0,0 +1,85 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; +using osu.Game.Online.Chat; +using osuTK; + +namespace osu.Game.Overlays.NewChat +{ + public class ChannelListing : VisibilityContainer + { + public event Action? OnRequestJoin; + public event Action? OnRequestLeave; + + public string SearchTerm + { + get => flow.SearchTerm; + set => flow.SearchTerm = value; + } + + private SearchContainer flow = null!; + + [Resolved] + private OverlayColourProvider overlayColours { get; set; } = null!; + + public ChannelListing() + { + Masking = true; + } + + protected override void PopIn() => this.FadeIn(); + protected override void PopOut() => this.FadeOut(); + + public void UpdateAvailableChannels(IEnumerable newChannels) + { + flow.ChildrenEnumerable = newChannels.Where(c => c.Type == ChannelType.Public) + .Select(c => new ChannelListingItem(c)); + + foreach (var item in flow.Children) + { + item.OnRequestJoin += channel => OnRequestJoin?.Invoke(channel); + item.OnRequestLeave += channel => OnRequestLeave?.Invoke(channel); + } + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = overlayColours.Background4, + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + ScrollbarAnchor = Anchor.TopRight, + Child = flow = new SearchContainer + { + Direction = FillDirection.Vertical, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(3), + Padding = new MarginPadding + { + Vertical = 13, + Horizontal = 15, + }, + }, + }, + }; + } + } +} diff --git a/osu.Game/Overlays/NewChat/ChannelListingItem.cs b/osu.Game/Overlays/NewChat/ChannelListingItem.cs new file mode 100644 index 0000000000..845db4e802 --- /dev/null +++ b/osu.Game/Overlays/NewChat/ChannelListingItem.cs @@ -0,0 +1,175 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +#nullable enable + +using System; +using System.Collections.Generic; +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.Graphics.Sprites; +using osu.Framework.Input.Events; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Chat; +using osuTK; + +namespace osu.Game.Overlays.NewChat +{ + public class ChannelListingItem : OsuClickableContainer, IFilterable + { + public event Action? OnRequestJoin; + public event Action? OnRequestLeave; + + public bool FilteringActive { get; set; } + public IEnumerable FilterTerms => new[] { channel.Name, channel.Topic ?? string.Empty }; + public bool MatchingFilter + { + set => this.FadeTo(value ? 1f : 0f, 100); + } + + private readonly float TEXT_SIZE = 18; + private readonly float ICON_SIZE = 14; + private readonly Channel channel; + + private Colour4 selectedColour; + private Colour4 normalColour; + + private Box hoverBox = null!; + private SpriteIcon checkbox = null!; + private OsuSpriteText channelText = null!; + private IBindable channelJoined = null!; + + [Resolved] + private OverlayColourProvider overlayColours { get; set; } = null!; + + public ChannelListingItem(Channel channel) + { + this.channel = channel; + + Masking = true; + CornerRadius = 5; + RelativeSizeAxes = Axes.X; + Height = 20; + } + + protected override bool OnHover(HoverEvent e) + { + hoverBox.Show(); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hoverBox.Hide(); + base.OnHoverLost(e); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + // Set colours + normalColour = overlayColours.Light3; + selectedColour = Colour4.White; + + // Set handlers for state display + channelJoined = channel.Joined.GetBoundCopy(); + channelJoined.BindValueChanged(change => + { + if (change.NewValue) + { + checkbox.Show(); + channelText.Colour = selectedColour; + } + else + { + checkbox.Hide(); + channelText.Colour = normalColour; + } + }, true); + + // Set action on click + Action = () => (channelJoined.Value ? OnRequestLeave : OnRequestJoin)?.Invoke(channel); + } + + [BackgroundDependencyLoader] + private void load() + { + Children = new Drawable[] + { + hoverBox = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = overlayColours.Background3, + Alpha = 0f, + }, + new GridContainer + { + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new Dimension[] + { + new Dimension(GridSizeMode.Absolute, 40), + new Dimension(GridSizeMode.Absolute, 200), + new Dimension(GridSizeMode.Absolute, 400), + new Dimension(GridSizeMode.AutoSize), + new Dimension(), + }, + Content = new[] + { + new Drawable[] + { + checkbox = new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Margin = new MarginPadding { Left = 15 }, + Icon = FontAwesome.Solid.Check, + Size = new Vector2(ICON_SIZE), + }, + channelText = new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = $"# {channel.Name.Substring(1)}", + Font = OsuFont.Torus.With(size: TEXT_SIZE, weight: FontWeight.Medium), + Margin = new MarginPadding { Bottom = 2 }, + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = channel.Topic, + Font = OsuFont.Torus.With(size: TEXT_SIZE), + Margin = new MarginPadding { Bottom = 2 }, + Colour = Colour4.White, + }, + new SpriteIcon + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Icon = FontAwesome.Solid.User, + Size = new Vector2(ICON_SIZE), + Margin = new MarginPadding { Right = 5 }, + Colour = overlayColours.Light3, + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Text = "0", + Font = OsuFont.Numeric.With(size: TEXT_SIZE, weight: FontWeight.Medium), + Margin = new MarginPadding { Bottom = 2 }, + Colour = overlayColours.Light3, + }, + }, + }, + }, + }; + } + } +}