From 3067c890ce278e68625d67c87ebc761abe3a5d2a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 7 Oct 2016 22:51:10 +0900 Subject: [PATCH 01/15] General improvements to chat querying and logic organisation. --- .../Tests/TestCaseChatDisplay.cs | 101 ++++++++++-------- osu.Game/Online/Chat/Channel.cs | 27 +++++ .../Online/Chat/Display/ChannelDisplay.cs | 78 ++++++++++++++ osu.Game/Online/Chat/Display/ChatLine.cs | 27 ++--- osu.Game/Online/Chat/Message.cs | 4 +- osu.Game/osu.Game.csproj | 1 + 6 files changed, 173 insertions(+), 65 deletions(-) create mode 100644 osu.Game/Online/Chat/Display/ChannelDisplay.cs diff --git a/osu.Desktop.VisualTests/Tests/TestCaseChatDisplay.cs b/osu.Desktop.VisualTests/Tests/TestCaseChatDisplay.cs index 353ad4f9f1..98265877a3 100644 --- a/osu.Desktop.VisualTests/Tests/TestCaseChatDisplay.cs +++ b/osu.Desktop.VisualTests/Tests/TestCaseChatDisplay.cs @@ -5,7 +5,6 @@ using OpenTK; using osu.Framework.GameModes.Testing; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Transformations; using osu.Framework.Threading; using osu.Game; using osu.Game.Online.API; @@ -15,7 +14,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.Linq; using osu.Framework.Graphics.Sprites; -using osu.Game.Online.Chat.Display.osu.Online.Social; +using osu.Game.Online.Chat.Display; namespace osu.Desktop.Tests { @@ -26,41 +25,22 @@ namespace osu.Desktop.Tests public override string Name => @"Chat"; public override string Description => @"Testing API polling"; - private List channels = new List(); - private FlowContainer flow; + FlowContainer flow; private Scheduler scheduler = new Scheduler(); private APIAccess api => ((OsuGameBase)Game).API; - private long? lastMessageId; + private ChannelDisplay channelDisplay; public override void Reset() { base.Reset(); - lastMessageId = null; - if (api.State != APIAccess.APIState.Online) api.OnStateChange += delegate { initializeChannels(); }; else initializeChannels(); - - Add(new ScrollContainer() - { - Size = new Vector2(1, 0.5f), - Children = new Drawable[] - { - flow = new FlowContainer - { - Direction = FlowDirection.VerticalOnly, - RelativeSizeAxes = Axes.X, - LayoutDuration = 100, - LayoutEasing = EasingTypes.Out, - Padding = new Vector2(1, 1) - } - } - }); } protected override void Update() @@ -69,60 +49,87 @@ namespace osu.Desktop.Tests base.Update(); } + private long? lastMessageId; + + List careChannels; + private void initializeChannels() { + careChannels = new List(); + if (api.State != APIAccess.APIState.Online) return; + Add(flow = new FlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FlowDirection.VerticalOnly + }); + + SpriteText loading; + Add(loading = new SpriteText + { + Text = @"Loading available channels...", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + TextSize = 40, + }); + messageRequest?.Cancel(); ListChannelsRequest req = new ListChannelsRequest(); req.Success += delegate (List channels) { - this.channels = channels; - messageRequest = scheduler.AddDelayed(requestNewMessages, 1000, true); + Game.Scheduler.Add(delegate + { + loading.FadeOut(100); + }); + + addChannel(channels.Find(c => c.Name == @"#osu")); + addChannel(channels.Find(c => c.Name == @"#lobby")); + addChannel(channels.Find(c => c.Name == @"#english")); + + messageRequest = scheduler.AddDelayed(() => FetchNewMessages(api), 1000, true); }; api.Queue(req); } - private void requestNewMessages() + private void addChannel(Channel channel) { - messageRequest.Wait(); + flow.Add(channelDisplay = new ChannelDisplay(channel) + { + Size = new Vector2(1, 0.3f) + }); - Channel channel = channels.Find(c => c.Name == "#osu"); + careChannels.Add(channel); + } - GetMessagesRequest gm = new GetMessagesRequest(new List { channel }, lastMessageId); - gm.Success += delegate (List messages) + GetMessagesRequest fetchReq; + + public void FetchNewMessages(APIAccess api) + { + if (fetchReq != null) return; + + fetchReq = new GetMessagesRequest(careChannels, lastMessageId); + fetchReq.Success += delegate (List messages) { foreach (Message m in messages) { - //m.LineWidth = this.Size.X; //this is kinda ugly. - //m.Drawable.Depth = m.Id; - //m.Drawable.FadeInFromZero(800); - - //flow.Add(m.Drawable); - - //if (osu.Messages.Count > 50) - //{ - // osu.Messages[0].Drawable.Expire(); - // osu.Messages.RemoveAt(0); - //} - flow.Add(new ChatLine(m)); - channel.Messages.Add(m); + careChannels.Find(c => c.Id == m.ChannelId).AddNewMessages(m); } lastMessageId = messages.LastOrDefault()?.Id ?? lastMessageId; Debug.Write("success!"); - messageRequest.Continue(); + fetchReq = null; }; - gm.Failure += delegate + fetchReq.Failure += delegate { Debug.Write("failure!"); - messageRequest.Continue(); + fetchReq = null; }; - api.Queue(gm); + api.Queue(fetchReq); } } } diff --git a/osu.Game/Online/Chat/Channel.cs b/osu.Game/Online/Chat/Channel.cs index 96904337cf..59d0cd8cc1 100644 --- a/osu.Game/Online/Chat/Channel.cs +++ b/osu.Game/Online/Chat/Channel.cs @@ -1,8 +1,14 @@ //Copyright (c) 2007-2016 ppy Pty Ltd . //Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; using Newtonsoft.Json; +using osu.Framework.Configuration; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; namespace osu.Game.Online.Chat { @@ -28,5 +34,26 @@ namespace osu.Game.Online.Chat public Channel() { } + + public event Action NewMessagesArrived; + + public void AddNewMessages(params Message[] messages) + { + Messages.AddRange(messages); + purgeOldMessages(); + + NewMessagesArrived?.Invoke(messages); + } + + private void purgeOldMessages() + { + const int max_history = 50; + + int messageCount = Messages.Count; + if (messageCount > 50) + { + Messages.RemoveRange(0, messageCount - max_history); + } + } } } diff --git a/osu.Game/Online/Chat/Display/ChannelDisplay.cs b/osu.Game/Online/Chat/Display/ChannelDisplay.cs new file mode 100644 index 0000000000..aacdd4c003 --- /dev/null +++ b/osu.Game/Online/Chat/Display/ChannelDisplay.cs @@ -0,0 +1,78 @@ +//Copyright (c) 2007-2016 ppy Pty Ltd . +//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Transformations; +using osu.Game.Online.Chat.Display.osu.Online.Social; +using OpenTK; + +namespace osu.Game.Online.Chat.Display +{ + public class ChannelDisplay : FlowContainer + { + private readonly Channel channel; + private FlowContainer flow; + + public ChannelDisplay(Channel channel) + { + this.channel = channel; + newMessages(channel.Messages); + channel.NewMessagesArrived += newMessages; + + RelativeSizeAxes = Axes.X; + Direction = FlowDirection.VerticalOnly; + + Add(new SpriteText + { + Text = channel.Name + }); + + Add(new ScrollContainer + { + RelativeSizeAxes = Axes.X, + Size = new Vector2(1, 200), + Children = new Drawable[] + { + flow = new FlowContainer + { + Direction = FlowDirection.VerticalOnly, + RelativeSizeAxes = Axes.X, + LayoutEasing = EasingTypes.Out, + Padding = new Vector2(1, 1) + } + } + }); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + channel.NewMessagesArrived -= newMessages; + } + + public override void Load() + { + base.Load(); + newMessages(channel.Messages); + } + + private void newMessages(IEnumerable newMessages) + { + if (!IsLoaded) return; + + var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - 20)); + + //up to last 20 messages + foreach (Message m in displayMessages) + flow.Add(new ChatLine(m)); + + while (flow.Children.Count() > 20) + flow.Remove(flow.Children.First()); + } + } +} \ No newline at end of file diff --git a/osu.Game/Online/Chat/Display/ChatLine.cs b/osu.Game/Online/Chat/Display/ChatLine.cs index aa75107c09..fe43ebe66b 100644 --- a/osu.Game/Online/Chat/Display/ChatLine.cs +++ b/osu.Game/Online/Chat/Display/ChatLine.cs @@ -1,6 +1,8 @@ //Copyright (c) 2007-2016 ppy Pty Ltd . //Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using System; +using osu.Framework.Configuration; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Drawables; @@ -14,11 +16,11 @@ namespace osu.Game.Online.Chat.Display { public class ChatLine : AutoSizeContainer { - private readonly Message msg; + public readonly Message Message; - public ChatLine(Message msg) + public ChatLine(Message message) { - this.msg = msg; + this.Message = message; } public override void Load() @@ -27,34 +29,27 @@ namespace osu.Game.Online.Chat.Display RelativeSizeAxes = Axes.X; - Add(new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Aqua, - Alpha = 0.2f - }); - Add(new SpriteText { - Text = msg.Timestamp.ToLocalTime().ToLongTimeString(), + Text = Message.Timestamp.ToLocalTime().ToLongTimeString(), Colour = new Color4(128, 128, 128, 255) }); Add(new SpriteText { - Text = msg.User.Name, + Text = Message.User.Name, Origin = Anchor.TopRight, RelativePositionAxes = Axes.X, - Position = new Vector2(0.14f,0), + Position = new Vector2(0.2f,0), }); Add(new SpriteText { - Text = msg.Content, + Text = Message.Content, RelativePositionAxes = Axes.X, - Position = new Vector2(0.15f, 0), + Position = new Vector2(0.22f, 0), RelativeSizeAxes = Axes.X, - Size = new Vector2(0.85f, 1), + Size = new Vector2(0.78f, 1), }); } } diff --git a/osu.Game/Online/Chat/Message.cs b/osu.Game/Online/Chat/Message.cs index 7f9f6ece2c..5b344da22c 100644 --- a/osu.Game/Online/Chat/Message.cs +++ b/osu.Game/Online/Chat/Message.cs @@ -12,10 +12,10 @@ namespace osu.Game.Online.Chat public long Id; [JsonProperty(@"user_id")] - public string UserId; + public int UserId; [JsonProperty(@"channel_id")] - public string ChannelId; + public int ChannelId; [JsonProperty(@"timestamp")] public DateTime Timestamp; diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 7852d68e33..45d79632e5 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -120,6 +120,7 @@ + From adba72d29386d1cdc415af016754d6db1d9af0db Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 8 Oct 2016 00:41:31 +0900 Subject: [PATCH 02/15] Toolbar stores its current state locally (just for conformity). --- osu.Game/Overlays/Toolbar.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Overlays/Toolbar.cs b/osu.Game/Overlays/Toolbar.cs index 46bc2be0d6..049b6f66aa 100644 --- a/osu.Game/Overlays/Toolbar.cs +++ b/osu.Game/Overlays/Toolbar.cs @@ -25,8 +25,12 @@ namespace osu.Game.Overlays private ToolbarModeSelector modeSelector; + public ToolbarState State { get; private set; } + public void SetState(ToolbarState state, bool instant = false) { + State = state; + int time = instant ? 0 : 200; switch (state) From 1e9e52aecc8a0dd541b0df50f51a649304712bf4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 8 Oct 2016 00:41:44 +0900 Subject: [PATCH 03/15] osuLogo handles less keys. --- osu.Game/GameModes/Menu/ButtonSystem.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/osu.Game/GameModes/Menu/ButtonSystem.cs b/osu.Game/GameModes/Menu/ButtonSystem.cs index 87a78c335d..7cc2c85ffb 100644 --- a/osu.Game/GameModes/Menu/ButtonSystem.cs +++ b/osu.Game/GameModes/Menu/ButtonSystem.cs @@ -125,16 +125,19 @@ namespace osu.Game.GameModes.Menu protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) { - if (args.Key == Key.Escape) + switch (args.Key) { - if (State == MenuState.Initial) - return false; + case Key.Space: + osuLogo.TriggerClick(state); + return true; + case Key.Escape: + if (State == MenuState.Initial) + return false; - State = MenuState.Initial; - return true; + State = MenuState.Initial; + return true; } - - osuLogo.TriggerClick(state); + return true; } From 47faf8f40d3d6252e2ecd22f8fa316802455fa0f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 8 Oct 2016 00:43:06 +0900 Subject: [PATCH 04/15] Add in-game chat display, along with a global hotkey handling method. --- osu.Game/Input/GlobalHotkeys.cs | 21 +++ .../Online/Chat/Display/ChannelDisplay.cs | 37 ++-- osu.Game/OsuGame.cs | 29 ++- osu.Game/Overlays/ChatConsole.cs | 171 ++++++++++++++++++ osu.Game/osu.Game.csproj | 2 + 5 files changed, 243 insertions(+), 17 deletions(-) create mode 100644 osu.Game/Input/GlobalHotkeys.cs create mode 100644 osu.Game/Overlays/ChatConsole.cs diff --git a/osu.Game/Input/GlobalHotkeys.cs b/osu.Game/Input/GlobalHotkeys.cs new file mode 100644 index 0000000000..2a0fa48adf --- /dev/null +++ b/osu.Game/Input/GlobalHotkeys.cs @@ -0,0 +1,21 @@ +//Copyright (c) 2007-2016 ppy Pty Ltd . +//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework.Graphics; +using osu.Framework.Input; + +namespace osu.Game.Input +{ + public class GlobalHotkeys : Drawable + { + public Func Handler; + + public override bool HandleInput => true; + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + return Handler(state, args); + } + } +} \ No newline at end of file diff --git a/osu.Game/Online/Chat/Display/ChannelDisplay.cs b/osu.Game/Online/Chat/Display/ChannelDisplay.cs index aacdd4c003..1b8c4e743e 100644 --- a/osu.Game/Online/Chat/Display/ChannelDisplay.cs +++ b/osu.Game/Online/Chat/Display/ChannelDisplay.cs @@ -24,29 +24,34 @@ namespace osu.Game.Online.Chat.Display newMessages(channel.Messages); channel.NewMessagesArrived += newMessages; - RelativeSizeAxes = Axes.X; + RelativeSizeAxes = Axes.Both; Direction = FlowDirection.VerticalOnly; - Add(new SpriteText + Children = new Drawable[] { - Text = channel.Name - }); - - Add(new ScrollContainer - { - RelativeSizeAxes = Axes.X, - Size = new Vector2(1, 200), - Children = new Drawable[] + new SpriteText { - flow = new FlowContainer + Text = channel.Name, + TextSize = 50, + Alpha = 0.3f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre + }, + new ScrollContainer + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] { - Direction = FlowDirection.VerticalOnly, - RelativeSizeAxes = Axes.X, - LayoutEasing = EasingTypes.Out, - Padding = new Vector2(1, 1) + flow = new FlowContainer + { + Direction = FlowDirection.VerticalOnly, + RelativeSizeAxes = Axes.X, + LayoutEasing = EasingTypes.Out, + Padding = new Vector2(1, 1) + } } } - }); + }; } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 87c6c30c51..d7b8ea30ff 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -8,6 +8,7 @@ using osu.Game.Configuration; using osu.Game.GameModes.Menu; using OpenTK; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Sprites; using osu.Framework.Platform; using osu.Game.GameModes; @@ -15,12 +16,16 @@ using osu.Game.Graphics.Background; using osu.Game.GameModes.Play; using osu.Game.Graphics.Containers; using osu.Game.Overlays; +using osu.Framework.Input; +using osu.Game.Input; +using OpenTK.Input; namespace osu.Game { public class OsuGame : OsuGameBase { public Toolbar Toolbar; + public ChatConsole Chat; public MainMenu MainMenu => intro?.ChildGameMode as MainMenu; private Intro intro; @@ -51,15 +56,21 @@ namespace osu.Game OnPlayModeChange = delegate (PlayMode m) { PlayMode.Value = m; }, Alpha = 0.001f //fixes invalidation fuckup }, + Chat = new ChatConsole(), new VolumeControl { VolumeGlobal = Audio.Volume, VolumeSample = Audio.VolumeSample, VolumeTrack = Audio.VolumeTrack + }, + new GlobalHotkeys //exists because UserInputManager is at a level below us. + { + Handler = globalHotkeyPressed } }); Toolbar.SetState(ToolbarState.Hidden, true); + Chat.SetState(ChatConsoleState.Hidden, true); intro.ModePushed += modeAdded; intro.Exited += modeRemoved; @@ -71,6 +82,18 @@ namespace osu.Game Cursor.Alpha = 0; } + private bool globalHotkeyPressed(InputState state, KeyDownEventArgs args) + { + switch (args.Key) + { + case Key.F8: + Chat.SetState(Chat.State == ChatConsoleState.Hidden ? ChatConsoleState.Visible : ChatConsoleState.Hidden); + return true; + } + + return base.OnKeyDown(state, args); + } + public Action ModeChanged; private void modeChanged(GameMode newMode) @@ -83,6 +106,7 @@ namespace osu.Game if (newMode is Player || newMode is Intro) { Toolbar.SetState(ToolbarState.Hidden); + Chat.SetState(ChatConsoleState.Hidden); } else { @@ -101,7 +125,10 @@ namespace osu.Game { if (!intro.DidLoadMenu || intro.ChildGameMode != null) { - intro.MakeCurrent(); + Scheduler.Add(delegate + { + intro.MakeCurrent(); + }); return true; } diff --git a/osu.Game/Overlays/ChatConsole.cs b/osu.Game/Overlays/ChatConsole.cs new file mode 100644 index 0000000000..ca7cb040d3 --- /dev/null +++ b/osu.Game/Overlays/ChatConsole.cs @@ -0,0 +1,171 @@ +//Copyright (c) 2007-2016 ppy Pty Ltd . +//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Drawables; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Transformations; +using osu.Framework.Threading; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.Chat; +using osu.Game.Online.Chat.Display; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.Input; +using OpenTK.Input; + +namespace osu.Game.Overlays +{ + public class ChatConsole : Container + { + private APIAccess api => ((OsuGameBase)Game).API; + + private ChannelDisplay channelDisplay; + + private ScheduledDelegate messageRequest; + + private Container content; + + protected override Container AddTarget => content; + + public ChatConsole() + { + RelativeSizeAxes = Axes.X; + Size = new Vector2(1, 300); + Anchor = Anchor.BottomLeft; + Origin = Anchor.BottomLeft; + + AddTopLevel(new Box + { + Depth = float.MinValue, + RelativeSizeAxes = Axes.Both, + Colour = new Color4(0.1f, 0.1f, 0.1f, 0.4f) + }); + + AddTopLevel(content = new Container + { + RelativeSizeAxes = Axes.Both, + }); + } + + public override void Load() + { + base.Load(); + initializeChannels(); + } + + private long? lastMessageId; + + List careChannels; + + private void initializeChannels() + { + careChannels = new List(); + + //if (api.State != APIAccess.APIState.Online) + // return; + + Add(new FlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FlowDirection.VerticalOnly + }); + + SpriteText loading; + Add(loading = new SpriteText + { + Text = @"Loading available channels...", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + TextSize = 40, + }); + + messageRequest?.Cancel(); + + ListChannelsRequest req = new ListChannelsRequest(); + req.Success += delegate (List channels) + { + Game.Scheduler.Add(delegate + { + loading.FadeOut(100); + }); + + addChannel(channels.Find(c => c.Name == @"#osu")); + //addChannel(channels.Find(c => c.Name == @"#lobby")); + //addChannel(channels.Find(c => c.Name == @"#english")); + + messageRequest = Game.Scheduler.AddDelayed(() => FetchNewMessages(api), 1000, true); + }; + api.Queue(req); + } + + private void addChannel(Channel channel) + { + Add(channelDisplay = new ChannelDisplay(channel)); + careChannels.Add(channel); + } + + GetMessagesRequest fetchReq; + + public void FetchNewMessages(APIAccess api) + { + if (fetchReq != null) return; + + fetchReq = new GetMessagesRequest(careChannels, lastMessageId); + fetchReq.Success += delegate (List messages) + { + foreach (Message m in messages) + { + careChannels.Find(c => c.Id == m.ChannelId).AddNewMessages(m); + } + + lastMessageId = messages.LastOrDefault()?.Id ?? lastMessageId; + + Debug.Write("success!"); + fetchReq = null; + }; + fetchReq.Failure += delegate + { + Debug.Write("failure!"); + fetchReq = null; + }; + + api.Queue(fetchReq); + } + + public ChatConsoleState State { get; private set; } + + public void SetState(ChatConsoleState state, bool instant = false) + { + State = state; + + int time = instant ? 0 : 500; + + switch (state) + { + case ChatConsoleState.Hidden: + MoveToY(-Size.Y, time, EasingTypes.InQuint); + FadeOut(time, EasingTypes.InQuint); + break; + case ChatConsoleState.Visible: + MoveToY(0, time, EasingTypes.OutQuint); + FadeIn(time, EasingTypes.OutQuint); + break; + } + } + } + + public enum ChatConsoleState + { + Visible, + Hidden, + } +} diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 45d79632e5..2d242954aa 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -103,6 +103,7 @@ + @@ -126,6 +127,7 @@ + From 97c2dcf590e30afac920801befe85e22e2fad862 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 8 Oct 2016 23:25:38 +0900 Subject: [PATCH 05/15] Use PaddingContainer for better layout. --- osu.Game/Online/Chat/Display/ChatLine.cs | 60 +++++++++++++++--------- 1 file changed, 39 insertions(+), 21 deletions(-) diff --git a/osu.Game/Online/Chat/Display/ChatLine.cs b/osu.Game/Online/Chat/Display/ChatLine.cs index fe43ebe66b..125463e666 100644 --- a/osu.Game/Online/Chat/Display/ChatLine.cs +++ b/osu.Game/Online/Chat/Display/ChatLine.cs @@ -23,34 +23,52 @@ namespace osu.Game.Online.Chat.Display this.Message = message; } + const float padding = 200; + const float text_size = 20; + public override void Load() { base.Load(); RelativeSizeAxes = Axes.X; - Add(new SpriteText + Children = new Drawable[] { - Text = Message.Timestamp.ToLocalTime().ToLongTimeString(), - Colour = new Color4(128, 128, 128, 255) - }); - - Add(new SpriteText - { - Text = Message.User.Name, - Origin = Anchor.TopRight, - RelativePositionAxes = Axes.X, - Position = new Vector2(0.2f,0), - }); - - Add(new SpriteText - { - Text = Message.Content, - RelativePositionAxes = Axes.X, - Position = new Vector2(0.22f, 0), - RelativeSizeAxes = Axes.X, - Size = new Vector2(0.78f, 1), - }); + new Container + { + Size = new Vector2(padding, text_size), + Children = new Drawable[] + { + new SpriteText + { + Text = Message.Timestamp.ToLocalTime().ToLongTimeString(), + TextSize = text_size, + Colour = new Color4(128, 128, 128, 255) + }, + new SpriteText + { + Text = Message.User.Name, + TextSize = text_size, + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + } + } + }, + new PaddingContainer + { + RelativeSizeAxes = Axes.X, + Padding = new Padding { Left = padding + 10 }, + Children = new Drawable[] + { + new SpriteText + { + Text = Message.Content, + TextSize = text_size, + RelativeSizeAxes = Axes.X, + } + } + } + }; } } } From 9594b7193c24e6419eb3974bcd7d74f593c14de3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 8 Oct 2016 12:53:46 +0900 Subject: [PATCH 06/15] Standardise drawable state access and split large nested classes out of MainMenu.ButtonSystem --- osu.Game/GameModes/Menu/Button.cs | 314 ++++++++++++++ osu.Game/GameModes/Menu/ButtonSystem.cs | 382 ++---------------- .../GameModes/Menu/FlowContainerWithOrigin.cs | 36 ++ osu.Game/GameModes/Menu/MainMenu.cs | 4 +- osu.Game/GameModes/Menu/MenuVisualisation.cs | 13 + osu.Game/GameModes/Menu/OsuLogo.cs | 4 +- osu.Game/OsuGame.cs | 15 +- osu.Game/Overlays/ChatConsole.cs | 43 +- osu.Game/Overlays/Toolbar.cs | 37 +- osu.Game/osu.Game.csproj | 3 + 10 files changed, 452 insertions(+), 399 deletions(-) create mode 100644 osu.Game/GameModes/Menu/Button.cs create mode 100644 osu.Game/GameModes/Menu/FlowContainerWithOrigin.cs create mode 100644 osu.Game/GameModes/Menu/MenuVisualisation.cs diff --git a/osu.Game/GameModes/Menu/Button.cs b/osu.Game/GameModes/Menu/Button.cs new file mode 100644 index 0000000000..c151a95422 --- /dev/null +++ b/osu.Game/GameModes/Menu/Button.cs @@ -0,0 +1,314 @@ +using OpenTK; +using OpenTK.Graphics; +using OpenTK.Input; +using osu.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Drawables; +using osu.Framework.Graphics.Primitives; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Graphics.Transformations; +using osu.Framework.Input; +using osu.Game.Graphics; +using System; + +namespace osu.Game.GameModes.Menu +{ + /// + /// Button designed specifically for the osu!next main menu. + /// In order to correctly flow, we have to use a negative margin on the parent container (due to the parallelogram shape). + /// + public class Button : AutoSizeContainer, IStateful + { + private Container iconText; + private WedgedBox box; + private Color4 colour; + private TextAwesome icon; + private string internalName; + private readonly FontAwesome symbol; + private Action clickAction; + private readonly float extraWidth; + private Key triggerKey; + private string text; + + public override Quad ScreenSpaceInputQuad => box.ScreenSpaceInputQuad; + + public Button(string text, string internalName, FontAwesome symbol, Color4 colour, Action clickAction = null, float extraWidth = 0, Key triggerKey = Key.Unknown) + { + this.internalName = internalName; + this.symbol = symbol; + this.colour = colour; + this.clickAction = clickAction; + this.extraWidth = extraWidth; + this.triggerKey = triggerKey; + this.text = text; + } + + public override void Load() + { + base.Load(); + Alpha = 0; + + Children = new Drawable[] + { + box = new WedgedBox(new Vector2(ButtonSystem.button_width + Math.Abs(extraWidth), ButtonSystem.button_area_height), ButtonSystem.wedge_width) + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Colour = colour, + Scale = new Vector2(0, 1) + }, + iconText = new AutoSizeContainer + { + Position = new Vector2(extraWidth / 2, 0), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + icon = new TextAwesome + { + Anchor = Anchor.Centre, + TextSize = 30, + Position = new Vector2(0, 0), + Icon = symbol + }, + new SpriteText + { + Direction = FlowDirection.HorizontalOnly, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + TextSize = 16, + Position = new Vector2(0, 35), + Text = text + } + } + } + }; + } + + protected override bool OnHover(InputState state) + { + if (State != ButtonState.Expanded) return true; + + //if (OsuGame.Instance.IsActive) + // Game.Audio.PlaySamplePositional($@"menu-{internalName}-hover", @"menuclick"); + + box.ScaleTo(new Vector2(1.5f, 1), 500, EasingTypes.OutElastic); + + int duration = 0; //(int)(Game.Audio.BeatLength / 2); + if (duration == 0) duration = 250; + + icon.ClearTransformations(); + + icon.ScaleTo(1, 500, EasingTypes.OutElasticHalf); + + double offset = 0; //(1 - Game.Audio.SyncBeatProgress) * duration; + double startTime = Time + offset; + + icon.RotateTo(10, offset, EasingTypes.InOutSine); + icon.ScaleTo(new Vector2(1, 0.9f), offset, EasingTypes.Out); + + icon.Transforms.Add(new TransformRotation(Clock) + { + StartValue = -10, + EndValue = 10, + StartTime = startTime, + EndTime = startTime + duration * 2, + Easing = EasingTypes.InOutSine, + LoopCount = -1, + LoopDelay = duration * 2 + }); + + icon.Transforms.Add(new TransformPosition(Clock) + { + StartValue = Vector2.Zero, + EndValue = new Vector2(0, -10), + StartTime = startTime, + EndTime = startTime + duration, + Easing = EasingTypes.Out, + LoopCount = -1, + LoopDelay = duration + }); + + icon.Transforms.Add(new TransformScaleVector(Clock) + { + StartValue = new Vector2(1, 0.9f), + EndValue = Vector2.One, + StartTime = startTime, + EndTime = startTime + duration, + Easing = EasingTypes.Out, + LoopCount = -1, + LoopDelay = duration + }); + + icon.Transforms.Add(new TransformPosition(Clock) + { + StartValue = new Vector2(0, -10), + EndValue = Vector2.Zero, + StartTime = startTime + duration, + EndTime = startTime + duration * 2, + Easing = EasingTypes.In, + LoopCount = -1, + LoopDelay = duration + }); + + icon.Transforms.Add(new TransformScaleVector(Clock) + { + StartValue = Vector2.One, + EndValue = new Vector2(1, 0.9f), + StartTime = startTime + duration, + EndTime = startTime + duration * 2, + Easing = EasingTypes.In, + LoopCount = -1, + LoopDelay = duration + }); + + icon.Transforms.Add(new TransformRotation(Clock) + { + StartValue = 10, + EndValue = -10, + StartTime = startTime + duration * 2, + EndTime = startTime + duration * 4, + Easing = EasingTypes.InOutSine, + LoopCount = -1, + LoopDelay = duration * 2 + }); + + return true; + } + + protected override void OnHoverLost(InputState state) + { + icon.ClearTransformations(); + icon.RotateTo(0, 500, EasingTypes.Out); + icon.MoveTo(Vector2.Zero, 500, EasingTypes.Out); + icon.ScaleTo(0.7f, 500, EasingTypes.OutElasticHalf); + icon.ScaleTo(Vector2.One, 200, EasingTypes.Out); + + if (State == ButtonState.Expanded) + box.ScaleTo(new Vector2(1, 1), 500, EasingTypes.OutElastic); + } + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + trigger(); + return true; + } + + protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) + { + base.OnKeyDown(state, args); + + if (triggerKey == args.Key && triggerKey != Key.Unknown) + { + trigger(); + return true; + } + + return false; + } + + private void trigger() + { + //Game.Audio.PlaySamplePositional($@"menu-{internalName}-click", internalName.Contains(@"back") ? @"menuback" : @"menuhit"); + + clickAction?.Invoke(); + + //box.FlashColour(ColourHelper.Lighten2(colour, 0.7f), 200); + } + + public override bool HandleInput => state != ButtonState.Exploded && box.Scale.X >= 0.8f; + + protected override void Update() + { + iconText.Alpha = MathHelper.Clamp((box.Scale.X - 0.5f) / 0.3f, 0, 1); + base.Update(); + } + + public int ContractStyle; + + ButtonState state; + public ButtonState State + { + get { return state; } + set + { + + if (state == value) + return; + + state = value; + + switch (state) + { + case ButtonState.Contracted: + switch (ContractStyle) + { + default: + box.ScaleTo(new Vector2(0, 1), 500, EasingTypes.OutExpo); + FadeOut(500); + break; + case 1: + box.ScaleTo(new Vector2(0, 1), 400, EasingTypes.InSine); + FadeOut(800); + break; + } + break; + case ButtonState.Expanded: + const int expand_duration = 500; + box.ScaleTo(new Vector2(1, 1), expand_duration, EasingTypes.OutExpo); + FadeIn(expand_duration / 6); + break; + case ButtonState.Exploded: + const int explode_duration = 200; + box.ScaleTo(new Vector2(2, 1), explode_duration, EasingTypes.OutExpo); + FadeOut(explode_duration / 4 * 3); + break; + } + } + } + + /// + /// ________ + /// / / + /// / / + /// /_______/ + /// + class WedgedBox : Box + { + float wedgeWidth; + + public WedgedBox(Vector2 boxSize, float wedgeWidth) + { + Size = boxSize; + this.wedgeWidth = wedgeWidth; + } + + /// + /// Custom DrawQuad used to create the slanted effect. + /// + protected override Quad DrawQuad + { + get + { + Quad q = base.DrawQuad; + + //Will become infinite if we don't limit its maximum size. + float wedge = Math.Min(q.Width, wedgeWidth / Scale.X); + + q.TopLeft.X += wedge; + q.BottomRight.X -= wedge; + + return q; + } + } + } + } + + public enum ButtonState + { + Contracted, + Expanded, + Exploded + } +} diff --git a/osu.Game/GameModes/Menu/ButtonSystem.cs b/osu.Game/GameModes/Menu/ButtonSystem.cs index 7cc2c85ffb..4624142d6f 100644 --- a/osu.Game/GameModes/Menu/ButtonSystem.cs +++ b/osu.Game/GameModes/Menu/ButtonSystem.cs @@ -16,10 +16,11 @@ using osu.Game.Graphics.Containers; using OpenTK; using OpenTK.Graphics; using OpenTK.Input; +using osu.Framework; namespace osu.Game.GameModes.Menu { - public partial class ButtonSystem : Container + public partial class ButtonSystem : Container, IStateful { public Action OnEdit; public Action OnExit; @@ -32,9 +33,10 @@ namespace osu.Game.GameModes.Menu private FlowContainerWithOrigin buttonFlow; - const float button_area_height = 100; - const float button_width = 140f; - const float wedge_width = 20; + //todo: make these non-internal somehow. + internal const float button_area_height = 100; + internal const float button_width = 140f; + internal const float wedge_width = 20; public const int EXIT_DELAY = 3000; @@ -49,15 +51,6 @@ namespace osu.Game.GameModes.Menu List