diff --git a/osu-resources b/osu-resources index a5199500cc..b348c1e540 160000 --- a/osu-resources +++ b/osu-resources @@ -1 +1 @@ -Subproject commit a5199500cc3ba96101fd858e0f78f36e538697b1 +Subproject commit b348c1e540edbb3325a8da9bca452c9dce2938d6 diff --git a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs index 46addf5ac2..815d820a47 100644 --- a/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs +++ b/osu.Game/Graphics/Cursor/OsuTooltipContainer.cs @@ -1,6 +1,7 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using OpenTK; using OpenTK.Graphics; using osu.Framework.Allocation; using osu.Framework.Extensions.Color4Extensions; @@ -24,6 +25,7 @@ namespace osu.Game.Graphics.Cursor { private readonly Box background; private readonly OsuSpriteText text; + private bool instantMovement = true; public override string TooltipText { @@ -32,7 +34,7 @@ namespace osu.Game.Graphics.Cursor if (value == text.Text) return; text.Text = value; - if (Alpha > 0) + if (IsPresent) { AutoSizeDuration = 250; background.FlashColour(OsuColour.Gray(0.4f), 1000, EasingTypes.OutQuint); @@ -80,6 +82,7 @@ namespace osu.Game.Graphics.Cursor protected override void PopIn() { + instantMovement |= !IsPresent; FadeIn(500, EasingTypes.OutQuint); } @@ -88,6 +91,19 @@ namespace osu.Game.Graphics.Cursor using (BeginDelayedSequence(150)) FadeOut(500, EasingTypes.OutQuint); } + + public override void Move(Vector2 pos) + { + if (instantMovement) + { + Position = pos; + instantMovement = false; + } + else + { + MoveTo(pos, 200, EasingTypes.OutQuint); + } + } } } } diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index 93fd0a8956..01685cc7dc 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; +using osu.Framework.Configuration; using osu.Framework.Lists; namespace osu.Game.Online.Chat @@ -25,7 +26,7 @@ namespace osu.Game.Online.Chat public readonly SortedList Messages = new SortedList(Comparer.Default); - //internal bool Joined; + public Bindable Joined = new Bindable(); public bool ReadOnly => Name != "#lazer"; diff --git a/osu.Game/Overlays/Chat/ChannelListItem.cs b/osu.Game/Overlays/Chat/ChannelListItem.cs new file mode 100644 index 0000000000..9aa11cdd4f --- /dev/null +++ b/osu.Game/Overlays/Chat/ChannelListItem.cs @@ -0,0 +1,188 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Chat; + +namespace osu.Game.Overlays.Chat +{ + public class ChannelListItem : ClickableContainer, IFilterable + { + private const float width_padding = 5; + private const float channel_width = 150; + private const float text_size = 15; + private const float transition_duration = 100; + + private readonly Channel channel; + + private readonly Bindable joinedBind = new Bindable(); + private readonly OsuSpriteText name; + private readonly OsuSpriteText topic; + private readonly TextAwesome joinedCheckmark; + + private Color4 joinedColour; + private Color4 topicColour; + private Color4 hoverColour; + + public string[] FilterTerms => new[] { channel.Name }; + public bool MatchingFilter + { + set + { + FadeTo(value ? 1f : 0f, 100); + } + } + + public Action OnRequestJoin; + public Action OnRequestLeave; + + public ChannelListItem(Channel channel) + { + this.channel = channel; + + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Action = () => { (channel.Joined ? OnRequestLeave : OnRequestJoin)?.Invoke(channel); }; + + Children = new Drawable[] + { + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new Container + { + Children = new[] + { + joinedCheckmark = new TextAwesome + { + Anchor = Anchor.TopRight, + Origin = Anchor.TopRight, + Icon = FontAwesome.fa_check_circle, + TextSize = text_size, + Shadow = false, + Margin = new MarginPadding { Right = 10f }, + Alpha = 0f, + }, + }, + }, + new Container + { + Width = channel_width, + AutoSizeAxes = Axes.Y, + Children = new[] + { + name = new OsuSpriteText + { + Text = channel.ToString(), + TextSize = text_size, + Font = @"Exo2.0-Bold", + Shadow = false, + }, + }, + }, + new Container + { + RelativeSizeAxes = Axes.X, + Width = 0.7f, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Left = width_padding }, + Children = new[] + { + topic = new OsuSpriteText + { + Text = channel.Topic, + TextSize = text_size, + Font = @"Exo2.0-SemiBold", + Shadow = false, + Alpha = 0.8f, + }, + }, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { Left = width_padding }, + Spacing = new Vector2(3f, 0f), + Children = new Drawable[] + { + new TextAwesome + { + Icon = FontAwesome.fa_user, + TextSize = text_size - 2, + Shadow = false, + Margin = new MarginPadding { Top = 1 }, + }, + new OsuSpriteText + { + Text = @"0", + TextSize = text_size, + Font = @"Exo2.0-SemiBold", + Shadow = false, + }, + }, + }, + }, + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + topicColour = colours.Gray9; + joinedColour = colours.Blue; + hoverColour = colours.Yellow; + + joinedBind.ValueChanged += updateColour; + joinedBind.BindTo(channel.Joined); + } + + protected override bool OnHover(InputState state) + { + if (!channel.Joined.Value) + name.FadeColour(hoverColour, 50, EasingTypes.OutQuint); + + return base.OnHover(state); + } + + protected override void OnHoverLost(InputState state) + { + if (!channel.Joined.Value) + name.FadeColour(Color4.White, transition_duration); + } + + private void updateColour(bool joined) + { + if (joined) + { + name.FadeColour(Color4.White, transition_duration); + joinedCheckmark.FadeTo(1f, transition_duration); + topic.FadeTo(0.8f, transition_duration); + topic.FadeColour(Color4.White, transition_duration); + FadeColour(joinedColour, transition_duration); + } + else + { + joinedCheckmark.FadeTo(0f, transition_duration); + topic.FadeTo(1f, transition_duration); + topic.FadeColour(topicColour, transition_duration); + FadeColour(Color4.White, transition_duration); + } + } + } +} diff --git a/osu.Game/Overlays/Chat/ChannelSection.cs b/osu.Game/Overlays/Chat/ChannelSection.cs new file mode 100644 index 0000000000..f12ec53605 --- /dev/null +++ b/osu.Game/Overlays/Chat/ChannelSection.cs @@ -0,0 +1,63 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Collections.Generic; +using System.Linq; +using OpenTK; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Online.Chat; + +namespace osu.Game.Overlays.Chat +{ + public class ChannelSection : Container, IHasFilterableChildren + { + private readonly OsuSpriteText header; + + public readonly FillFlowContainer ChannelFlow; + + public IEnumerable FilterableChildren => ChannelFlow.Children; + public string[] FilterTerms => new[] { Header }; + public bool MatchingFilter + { + set + { + FadeTo(value ? 1f : 0f, 100); + } + } + + public string Header + { + get { return header.Text; } + set { header.Text = value.ToUpper(); } + } + + public IEnumerable Channels + { + set { ChannelFlow.Children = value.Select(c => new ChannelListItem(c)); } + } + + public ChannelSection() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Children = new Drawable[] + { + header = new OsuSpriteText + { + TextSize = 15, + Font = @"Exo2.0-Bold", + }, + ChannelFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Margin = new MarginPadding { Top = 25 }, + Spacing = new Vector2(0f, 5f), + }, + }; + } + } +} diff --git a/osu.Game/Overlays/Chat/ChannelSelectionOverlay.cs b/osu.Game/Overlays/Chat/ChannelSelectionOverlay.cs new file mode 100644 index 0000000000..cd736a5fe9 --- /dev/null +++ b/osu.Game/Overlays/Chat/ChannelSelectionOverlay.cs @@ -0,0 +1,184 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Input; +using osu.Game.Graphics; +using osu.Game.Graphics.Backgrounds; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Online.Chat; + +namespace osu.Game.Overlays.Chat +{ + public class ChannelSelectionOverlay : FocusedOverlayContainer + { + public static readonly float WIDTH_PADDING = 170; + + private const float transition_duration = 500; + + private readonly Box bg; + private readonly Triangles triangles; + private readonly Box headerBg; + private readonly SearchTextBox search; + private readonly SearchContainer sectionsFlow; + + public Action OnRequestJoin; + public Action OnRequestLeave; + + public IEnumerable Sections + { + set + { + sectionsFlow.Children = value; + + foreach (ChannelSection s in sectionsFlow.Children) + { + foreach (ChannelListItem c in s.ChannelFlow.Children) + { + c.OnRequestJoin = channel => { OnRequestJoin?.Invoke(channel); }; + c.OnRequestLeave = channel => { OnRequestLeave?.Invoke(channel); }; + } + } + } + } + + public ChannelSelectionOverlay() + { + RelativeSizeAxes = Axes.X; + + Children = new Drawable[] + { + new Container + { + RelativeSizeAxes = Axes.Both, + Masking = true, + Children = new Drawable[] + { + bg = new Box + { + RelativeSizeAxes = Axes.Both, + }, + triangles = new Triangles + { + RelativeSizeAxes = Axes.Both, + TriangleScale = 5, + }, + }, + }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 85, Right = WIDTH_PADDING }, + Children = new[] + { + new ScrollContainer + { + RelativeSizeAxes = Axes.Both, + Children = new[] + { + sectionsFlow = new SearchContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + LayoutDuration = 200, + LayoutEasing = EasingTypes.OutQuint, + Spacing = new Vector2(0f, 20f), + Padding = new MarginPadding { Vertical = 20, Left = WIDTH_PADDING }, + }, + }, + }, + }, + }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + headerBg = new Box + { + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(0f, 10f), + Padding = new MarginPadding { Top = 10f, Bottom = 10f, Left = WIDTH_PADDING, Right = WIDTH_PADDING }, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = @"Chat Channels", + TextSize = 20, + Shadow = false, + }, + search = new HeaderSearchTextBox + { + RelativeSizeAxes = Axes.X, + PlaceholderText = @"Search", + Exit = Hide, + }, + }, + }, + }, + }, + }; + + search.Current.ValueChanged += newValue => sectionsFlow.SearchTerm = newValue; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + bg.Colour = colours.Gray3; + triangles.ColourDark = colours.Gray3; + triangles.ColourLight = OsuColour.FromHex(@"353535"); + + headerBg.Colour = colours.Gray2.Opacity(0.75f); + } + + protected override void OnFocus(InputState state) + { + InputManager.ChangeFocus(search); + base.OnFocus(state); + } + + protected override void PopIn() + { + if (Alpha == 0) MoveToY(DrawHeight); + + FadeIn(transition_duration, EasingTypes.OutQuint); + MoveToY(0, transition_duration, EasingTypes.OutQuint); + + search.HoldFocus = true; + base.PopIn(); + } + + protected override void PopOut() + { + FadeOut(transition_duration, EasingTypes.InSine); + MoveToY(DrawHeight, transition_duration, EasingTypes.InSine); + + search.HoldFocus = false; + base.PopOut(); + } + + private class HeaderSearchTextBox : SearchTextBox + { + protected override Color4 BackgroundFocused => Color4.Black.Opacity(0.2f); + protected override Color4 BackgroundUnfocused => Color4.Black.Opacity(0.2f); + } + } +} diff --git a/osu.Game/Overlays/Chat/ChatTabControl.cs b/osu.Game/Overlays/Chat/ChatTabControl.cs index a281cff7db..23ddff9381 100644 --- a/osu.Game/Overlays/Chat/ChatTabControl.cs +++ b/osu.Game/Overlays/Chat/ChatTabControl.cs @@ -207,11 +207,15 @@ namespace osu.Game.Overlays.Chat { public override bool Active { - get { return base.Active; } + get { return false; } + // ReSharper disable once ValueParameterNotUsed set { - activeBindable.Value = value; - base.Active = value; + // we basically never want this tab to become active. + // this allows us to become a "toggle" tab. + // is a bit hacky, to say the least. + activeBindable.Value = !activeBindable.Value; + base.Active = false; } } @@ -220,6 +224,9 @@ namespace osu.Game.Overlays.Chat public ChannelSelectorTabItem(Channel value, Bindable active) : base(value) { activeBindable = active; + activeBindable.ValueChanged += v => selectorUpdateState(); + + Depth = float.MaxValue; Width = 45; @@ -235,6 +242,26 @@ namespace osu.Game.Overlays.Chat backgroundInactive = colour.Gray2; backgroundActive = colour.Gray3; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + selectorUpdateState(); + } + + protected override void OnHoverLost(InputState state) + { + selectorUpdateState(); + } + + private void selectorUpdateState() + { + if (activeBindable.Value) + fadeActive(); + else + fadeInactive(); + } } } } diff --git a/osu.Game/Overlays/ChatOverlay.cs b/osu.Game/Overlays/ChatOverlay.cs index f81c0ea922..2e6f143e74 100644 --- a/osu.Game/Overlays/ChatOverlay.cs +++ b/osu.Game/Overlays/ChatOverlay.cs @@ -29,6 +29,7 @@ namespace osu.Game.Overlays public class ChatOverlay : FocusedOverlayContainer, IOnlineComponent { private const float textbox_height = 60; + private const float channel_selection_min_height = 0.3f; private ScheduledDelegate messageRequest; @@ -48,16 +49,21 @@ namespace osu.Game.Overlays private readonly ChatTabControl channelTabs; + private readonly Container chatContainer; private readonly Box chatBackground; private readonly Box tabBackground; private Bindable chatHeight; + private readonly Container channelSelectionContainer; + private readonly ChannelSelectionOverlay channelSelection; + + protected override bool InternalContains(Vector2 screenSpacePos) => chatContainer.Contains(screenSpacePos) || channelSelection.State == Visibility.Visible && channelSelection.Contains(screenSpacePos); + public ChatOverlay() { RelativeSizeAxes = Axes.Both; RelativePositionAxes = Axes.Both; - Size = new Vector2(1, DEFAULT_HEIGHT); Anchor = Anchor.BottomLeft; Origin = Anchor.BottomLeft; @@ -65,74 +71,119 @@ namespace osu.Game.Overlays Children = new Drawable[] { - new Container + channelSelectionContainer = new Container { - Name = @"chat area", RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding { Top = TAB_AREA_HEIGHT }, - Children = new Drawable[] + Height = 1f - DEFAULT_HEIGHT, + Masking = true, + Children = new[] { - chatBackground = new Box + channelSelection = new ChannelSelectionOverlay { RelativeSizeAxes = Axes.Both, }, - currentChannelContainer = new Container + }, + }, + chatContainer = new Container + { + Name = @"chat container", + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.Both, + Height = DEFAULT_HEIGHT, + Children = new[] + { + new Container { + Name = @"chat area", RelativeSizeAxes = Axes.Both, - Padding = new MarginPadding + Padding = new MarginPadding { Top = TAB_AREA_HEIGHT }, + Children = new Drawable[] { - Bottom = textbox_height + padding - }, + chatBackground = new Box + { + RelativeSizeAxes = Axes.Both, + }, + currentChannelContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding + { + Bottom = textbox_height + padding + }, + }, + new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = textbox_height, + Padding = new MarginPadding + { + Top = padding * 2, + Bottom = padding * 2, + Left = ChatLine.LEFT_PADDING + padding * 2, + Right = padding * 2, + }, + Children = new Drawable[] + { + inputTextBox = new FocusedTextBox + { + RelativeSizeAxes = Axes.Both, + Height = 1, + PlaceholderText = "type your message", + Exit = () => State = Visibility.Hidden, + OnCommit = postMessage, + HoldFocus = true, + } + } + } + } }, new Container { - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, + Name = @"tabs area", RelativeSizeAxes = Axes.X, - Height = textbox_height, - Padding = new MarginPadding - { - Top = padding * 2, - Bottom = padding * 2, - Left = ChatLine.LEFT_PADDING + padding * 2, - Right = padding * 2, - }, + Height = TAB_AREA_HEIGHT, Children = new Drawable[] { - inputTextBox = new FocusedTextBox + tabBackground = new Box { RelativeSizeAxes = Axes.Both, - Height = 1, - PlaceholderText = "type your message", - Exit = () => State = Visibility.Hidden, - OnCommit = postMessage, - HoldFocus = true, - } + Colour = Color4.Black, + }, + channelTabs = new ChatTabControl + { + RelativeSizeAxes = Axes.Both, + }, } - } - } - }, - new Container - { - Name = @"tabs area", - RelativeSizeAxes = Axes.X, - Height = TAB_AREA_HEIGHT, - Children = new Drawable[] - { - tabBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, }, - channelTabs = new ChatTabControl - { - RelativeSizeAxes = Axes.Both, - }, - } + }, }, }; channelTabs.Current.ValueChanged += newChannel => CurrentChannel = newChannel; + channelTabs.ChannelSelectorActive.ValueChanged += value => channelSelection.State = value ? Visibility.Visible : Visibility.Hidden; + channelSelection.StateChanged += (overlay, state) => + { + channelTabs.ChannelSelectorActive.Value = state == Visibility.Visible; + + if (state == Visibility.Visible) + { + inputTextBox.HoldFocus = false; + if (1f - chatHeight.Value < channel_selection_min_height) + { + chatContainer.ResizeHeightTo(1f - channel_selection_min_height, 800, EasingTypes.OutQuint); + channelSelectionContainer.ResizeHeightTo(channel_selection_min_height, 800, EasingTypes.OutQuint); + channelSelection.Show(); + chatHeight.Value = 1f - channel_selection_min_height; + } + } + else + { + inputTextBox.HoldFocus = true; + } + }; } private double startDragChatHeight; @@ -205,8 +256,9 @@ namespace osu.Game.Overlays chatHeight = config.GetBindable(OsuSetting.ChatDisplayHeight); chatHeight.ValueChanged += h => { - Height = (float)h; - tabBackground.FadeTo(Height == 1 ? 1 : 0.8f, 200); + chatContainer.Height = (float)h; + channelSelectionContainer.Height = 1f - (float)h; + tabBackground.FadeTo(h == 1 ? 1 : 0.8f, 200); }; chatHeight.TriggerChange(); @@ -243,6 +295,16 @@ namespace osu.Game.Overlays addChannel(channels.Find(c => c.Name == @"#lazer")); addChannel(channels.Find(c => c.Name == @"#osu")); addChannel(channels.Find(c => c.Name == @"#lobby")); + + channelSelection.OnRequestJoin = addChannel; + channelSelection.Sections = new[] + { + new ChannelSection + { + Header = "All Channels", + Channels = channels, + }, + }; }); messageRequest = Scheduler.AddDelayed(fetchNewMessages, 1000, true); @@ -262,9 +324,7 @@ namespace osu.Game.Overlays set { - if (currentChannel == value) return; - - if (channelTabs.ChannelSelectorActive) return; + if (currentChannel == value || value == null) return; currentChannel = value; @@ -316,6 +376,8 @@ namespace osu.Game.Overlays if (CurrentChannel == null) CurrentChannel = channel; + + channel.Joined.Value = true; } private void fetchInitialMessages(Channel channel) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index e6dd7ba1c4..759bd2d6f0 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -442,6 +442,9 @@ + + +