diff --git a/osu-framework b/osu-framework index 1f770847b2..7439250a63 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 1f770847b245e6d250e63e60c24e9e84131d15e7 +Subproject commit 7439250a63dd451f34dbc08ecf68a196cf8e479f diff --git a/osu.Desktop.VisualTests/Program.cs b/osu.Desktop.VisualTests/Program.cs index 1cfa440dd9..4dcfaff987 100644 --- a/osu.Desktop.VisualTests/Program.cs +++ b/osu.Desktop.VisualTests/Program.cs @@ -12,7 +12,7 @@ namespace osu.Framework.VisualTests [STAThread] public static void Main(string[] args) { - BasicGameHost host = Host.GetSuitableHost(); + BasicGameHost host = Host.GetSuitableHost(@"osu-visual-tests"); host.Add(new VisualTestGame()); host.Run(); } diff --git a/osu.Desktop.VisualTests/Tests/TestCaseChatDisplay.cs b/osu.Desktop.VisualTests/Tests/TestCaseChatDisplay.cs index 353ad4f9f1..32135a06f2 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,8 @@ 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; +using osu.Framework; namespace osu.Desktop.Tests { @@ -26,41 +26,29 @@ 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 APIAccess api; - private long? lastMessageId; + private ChannelDisplay channelDisplay; + + public override void Load(BaseGame game) + { + base.Load(game); + + api = ((OsuGameBase)game).API; + } 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 +57,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); + 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.Desktop.VisualTests/Tests/TestCaseScoreCounter.cs b/osu.Desktop.VisualTests/Tests/TestCaseScoreCounter.cs new file mode 100644 index 0000000000..144adf9098 --- /dev/null +++ b/osu.Desktop.VisualTests/Tests/TestCaseScoreCounter.cs @@ -0,0 +1,159 @@ +//Copyright (c) 2007-2016 ppy Pty Ltd . +//Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using OpenTK.Input; +using osu.Framework.GameModes.Testing; +using osu.Framework.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Graphics.Transformations; +using OpenTK; +using OpenTK.Graphics; +using osu.Framework.MathUtils; +using osu.Framework.Graphics.Sprites; + +namespace osu.Desktop.Tests +{ + class TestCaseScoreCounter : TestCase + { + public override string Name => @"ScoreCounter"; + + public override string Description => @"Tests multiple counters"; + + public override void Reset() + { + base.Reset(); + + ScoreCounter uc = new ScoreCounter + { + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + TextSize = 40, + RollingDuration = 1000, + RollingEasing = EasingTypes.Out, + Count = 0, + Position = new Vector2(20, 20), + LeadingZeroes = 7, + }; + Add(uc); + + StandardComboCounter sc = new StandardComboCounter + { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Position = new Vector2(20, 20), + IsRollingProportional = true, + RollingDuration = 20, + PopOutDuration = 250, + Count = 0, + TextSize = 40, + }; + Add(sc); + + CatchComboCounter cc = new CatchComboCounter + { + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + IsRollingProportional = true, + RollingDuration = 20, + PopOutDuration = 250, + Count = 0, + TextSize = 40, + }; + Add(cc); + + AlternativeComboCounter ac = new AlternativeComboCounter + { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Position = new Vector2(20, 80), + IsRollingProportional = true, + RollingDuration = 20, + ScaleFactor = 2, + Count = 0, + TextSize = 40, + }; + Add(ac); + + + AccuracyCounter pc = new AccuracyCounter + { + Origin = Anchor.TopRight, + Anchor = Anchor.TopRight, + RollingDuration = 1000, + RollingEasing = EasingTypes.Out, + Count = 100.0f, + Position = new Vector2(20, 60), + }; + Add(pc); + + SpriteText text = new SpriteText + { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Position = new Vector2(20, 190), + Text = @"- unset -", + }; + Add(text); + + StarCounter tc = new StarCounter + { + Origin = Anchor.BottomLeft, + Anchor = Anchor.BottomLeft, + Position = new Vector2(20, 160), + }; + Add(tc); + + AddButton(@"Reset all", delegate + { + uc.Count = 0; + sc.Count = 0; + ac.Count = 0; + cc.Count = 0; + pc.SetCount(0, 0); + tc.Count = 0; + text.Text = tc.Count.ToString("0.00"); + }); + + AddButton(@"Hit! :D", delegate + { + uc.Count += 300 + (ulong)(300.0 * (sc.Count > 0 ? sc.Count - 1 : 0) / 25.0); + sc.Count++; + ac.Count++; + cc.CatchFruit(new Color4( + Math.Max(0.5f, RNG.NextSingle()), + Math.Max(0.5f, RNG.NextSingle()), + Math.Max(0.5f, RNG.NextSingle()), + 1) + ); + pc.Numerator++; + pc.Denominator++; + }); + + AddButton(@"miss...", delegate + { + sc.Count = 0; + ac.Count = 0; + cc.Count = 0; + pc.Denominator++; + }); + + AddButton(@"Alter stars", delegate + { + tc.Count = RNG.NextSingle() * (tc.MaxStars + 1); + text.Text = tc.Count.ToString("0.00"); + }); + + AddButton(@"Stop counters", delegate + { + uc.StopRolling(); + sc.StopRolling(); + cc.StopRolling(); + ac.StopRolling(); + pc.StopRolling(); + tc.StopRolling(); + }); + } + } +} diff --git a/osu.Desktop.VisualTests/VisualTestGame.cs b/osu.Desktop.VisualTests/VisualTestGame.cs index ecbaf7ac8f..bc4c59c4e3 100644 --- a/osu.Desktop.VisualTests/VisualTestGame.cs +++ b/osu.Desktop.VisualTests/VisualTestGame.cs @@ -9,9 +9,9 @@ namespace osu.Framework.VisualTests { class VisualTestGame : OsuGameBase { - public override void Load() + public override void Load(BaseGame game) { - base.Load(); + base.Load(game); Add(new TestBrowser()); diff --git a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj index c54140b267..44a1aef6de 100644 --- a/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj +++ b/osu.Desktop.VisualTests/osu.Desktop.VisualTests.csproj @@ -140,6 +140,7 @@ + diff --git a/osu.Desktop/Program.cs b/osu.Desktop/Program.cs index fe7423924c..1421086054 100644 --- a/osu.Desktop/Program.cs +++ b/osu.Desktop/Program.cs @@ -13,7 +13,7 @@ namespace osu.Desktop [STAThread] public static void Main() { - BasicGameHost host = Host.GetSuitableHost(); + BasicGameHost host = Host.GetSuitableHost(@"osu"); host.Add(new OsuGame()); host.Run(); } diff --git a/osu.Game/GameModes/BackgroundMode.cs b/osu.Game/GameModes/BackgroundMode.cs index 1f90448e55..d9b1c1b18f 100644 --- a/osu.Game/GameModes/BackgroundMode.cs +++ b/osu.Game/GameModes/BackgroundMode.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Transformations; using OpenTK; using osu.Framework.Graphics; using osu.Framework.Input; +using osu.Framework; namespace osu.Game.GameModes { @@ -30,9 +31,9 @@ namespace osu.Game.GameModes return false; } - public override void Load() + public override void Load(BaseGame game) { - base.Load(); + base.Load(game); Content.Scale *= 1 + (x_movement_amount / Size.X) * 2; } diff --git a/osu.Game/GameModes/Backgrounds/BackgroundModeCustom.cs b/osu.Game/GameModes/Backgrounds/BackgroundModeCustom.cs index ae8e2d916e..7b1756436f 100644 --- a/osu.Game/GameModes/Backgrounds/BackgroundModeCustom.cs +++ b/osu.Game/GameModes/Backgrounds/BackgroundModeCustom.cs @@ -1,6 +1,7 @@ //Copyright (c) 2007-2016 ppy Pty Ltd . //Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework; using osu.Game.Graphics.Background; namespace osu.Game.GameModes.Backgrounds @@ -14,9 +15,9 @@ namespace osu.Game.GameModes.Backgrounds this.textureName = textureName; } - public override void Load() + public override void Load(BaseGame game) { - base.Load(); + base.Load(game); Add(new Background(textureName)); } diff --git a/osu.Game/GameModes/Backgrounds/BackgroundModeDefault.cs b/osu.Game/GameModes/Backgrounds/BackgroundModeDefault.cs index 0683a267a5..542e9b58b7 100644 --- a/osu.Game/GameModes/Backgrounds/BackgroundModeDefault.cs +++ b/osu.Game/GameModes/Backgrounds/BackgroundModeDefault.cs @@ -1,15 +1,16 @@ //Copyright (c) 2007-2016 ppy Pty Ltd . //Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE +using osu.Framework; using osu.Game.Graphics.Background; namespace osu.Game.GameModes.Backgrounds { public class BackgroundModeDefault : BackgroundMode { - public override void Load() + public override void Load(BaseGame game) { - base.Load(); + base.Load(game); Add(new Background()); } diff --git a/osu.Game/GameModes/GameModeWhiteBox.cs b/osu.Game/GameModes/GameModeWhiteBox.cs index 46250d9a91..121cb20d12 100644 --- a/osu.Game/GameModes/GameModeWhiteBox.cs +++ b/osu.Game/GameModes/GameModeWhiteBox.cs @@ -13,6 +13,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Game.GameModes.Backgrounds; using OpenTK; using OpenTK.Graphics; +using osu.Framework; namespace osu.Game.GameModes { @@ -77,9 +78,9 @@ namespace osu.Game.GameModes Content.FadeIn(transition_time, EasingTypes.OutExpo); } - public override void Load() + public override void Load(BaseGame game) { - base.Load(); + base.Load(game); Children = new Drawable[] { diff --git a/osu.Game/GameModes/Menu/Button.cs b/osu.Game/GameModes/Menu/Button.cs new file mode 100644 index 0000000000..261af9e78b --- /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(BaseGame game) + { + base.Load(game); + 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 93701e6c18..b4911928a4 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,23 +51,14 @@ namespace osu.Game.GameModes.Menu List