From d879b96d9f70869d001b4dfc62481eab93591390 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 28 Jun 2018 13:43:56 +0900 Subject: [PATCH 01/39] Implement storyboard samples --- .../Drawables/DrawableStoryboardSample.cs | 68 +++++++++++++++++++ osu.Game/Storyboards/StoryboardSample.cs | 9 +-- 2 files changed, 71 insertions(+), 6 deletions(-) create mode 100644 osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs new file mode 100644 index 0000000000..9a539072d0 --- /dev/null +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSample.cs @@ -0,0 +1,68 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.IO; +using osu.Framework.Allocation; +using osu.Framework.Audio.Sample; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; + +namespace osu.Game.Storyboards.Drawables +{ + public class DrawableStoryboardSample : Component + { + /// + /// The amount of time allowable beyond the start time of the sample, for the sample to start. + /// + private const double allowable_late_start = 100; + + private readonly StoryboardSample sample; + private SampleChannel channel; + + public override bool RemoveWhenNotAlive => false; + + public DrawableStoryboardSample(StoryboardSample sample) + { + this.sample = sample; + LifetimeStart = sample.Time; + } + + [BackgroundDependencyLoader] + private void load(IBindableBeatmap beatmap) + { + // Try first with the full name, then attempt with no path + channel = beatmap.Value.Skin.GetSample(sample.Path) ?? beatmap.Value.Skin.GetSample(Path.ChangeExtension(sample.Path, null)); + + if (channel != null) + channel.Volume.Value = sample.Volume / 100; + } + + protected override void Update() + { + base.Update(); + + if (Time.Current < sample.Time) + { + // We've rewound before the start time of the sample + channel?.Stop(); + + // In the case that the user fast-forwards to a point far beyond the start time of the sample, + // we want to be able to fall into the if-conditional below (therefore we must not have a life time end) + LifetimeStart = sample.Time; + LifetimeEnd = double.MaxValue; + } + else if (Time.Current - Time.Elapsed < sample.Time) + { + // We've passed the start time of the sample. We only play the sample if we're within an allowable range + // from the sample's start, to reduce layering if we've been fast-forwarded far into the future + if (Time.Current - sample.Time < allowable_late_start) + channel?.Play(); + + // In the case that the user rewinds to a point far behind the start time of the sample, + // we want to be able to fall into the if-conditional above (therefore we must not have a life time start) + LifetimeStart = double.MinValue; + LifetimeEnd = sample.Time; + } + } + } +} diff --git a/osu.Game/Storyboards/StoryboardSample.cs b/osu.Game/Storyboards/StoryboardSample.cs index d0555493a6..c34a39a7bf 100644 --- a/osu.Game/Storyboards/StoryboardSample.cs +++ b/osu.Game/Storyboards/StoryboardSample.cs @@ -2,14 +2,14 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Graphics; -using System; +using osu.Game.Storyboards.Drawables; namespace osu.Game.Storyboards { public class StoryboardSample : IStoryboardElement { public string Path { get; set; } - public bool IsDrawable => false; + public bool IsDrawable => true; public double Time; public float Volume; @@ -21,9 +21,6 @@ namespace osu.Game.Storyboards Volume = volume; } - public Drawable CreateDrawable() - { - throw new InvalidOperationException(); - } + public Drawable CreateDrawable() => new DrawableStoryboardSample(this); } } From 2aae528e1ccf08e0feb586a83faf02c3205de8d3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Jul 2018 18:18:04 +0900 Subject: [PATCH 02/39] Improve code quality of main menu button system --- osu.Game.Tests/Visual/TestCaseButtonSystem.cs | 13 ++ osu.Game/Screens/Menu/Button.cs | 36 ++- osu.Game/Screens/Menu/ButtonArea.cs | 148 ++++++++++++ osu.Game/Screens/Menu/ButtonSystem.cs | 217 +++++------------- osu.Game/Screens/Menu/MainMenu.cs | 10 +- 5 files changed, 263 insertions(+), 161 deletions(-) create mode 100644 osu.Game/Screens/Menu/ButtonArea.cs diff --git a/osu.Game.Tests/Visual/TestCaseButtonSystem.cs b/osu.Game.Tests/Visual/TestCaseButtonSystem.cs index 5eb81cdf9f..7f8133d638 100644 --- a/osu.Game.Tests/Visual/TestCaseButtonSystem.cs +++ b/osu.Game.Tests/Visual/TestCaseButtonSystem.cs @@ -1,6 +1,9 @@ // Copyright (c) 2007-2018 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 NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -13,6 +16,13 @@ namespace osu.Game.Tests.Visual [TestFixture] public class TestCaseButtonSystem : OsuTestCase { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ButtonSystem), + typeof(ButtonArea), + typeof(Button) + }; + public TestCaseButtonSystem() { OsuLogo logo; @@ -30,6 +40,9 @@ namespace osu.Game.Tests.Visual }; buttons.SetOsuLogo(logo); + + foreach (var s in Enum.GetValues(typeof(ButtonSystemState)).OfType().Skip(1)) + AddStep($"State to {s}", () => buttons.State = s); } } } diff --git a/osu.Game/Screens/Menu/Button.cs b/osu.Game/Screens/Menu/Button.cs index 542ddd2c92..f862905b5e 100644 --- a/osu.Game/Screens/Menu/Button.cs +++ b/osu.Game/Screens/Menu/Button.cs @@ -35,6 +35,12 @@ namespace osu.Game.Screens.Menu private readonly Box boxHoverLayer; private readonly SpriteIcon icon; private readonly string sampleName; + + /// + /// The menu state for which we are visible for. + /// + public ButtonSystemState? VisibleState; + private readonly Action clickAction; private readonly Key triggerKey; private SampleChannel sampleClick; @@ -51,7 +57,7 @@ namespace osu.Game.Screens.Menu AutoSizeAxes = Axes.Both; Alpha = 0; - Vector2 boxSize = new Vector2(ButtonSystem.BUTTON_WIDTH + Math.Abs(extraWidth), ButtonSystem.BUTTON_AREA_HEIGHT); + Vector2 boxSize = new Vector2(ButtonSystem.BUTTON_WIDTH + Math.Abs(extraWidth), ButtonArea.BUTTON_AREA_HEIGHT); Children = new Drawable[] { @@ -260,6 +266,7 @@ namespace osu.Game.Screens.Menu this.FadeOut(800); break; } + break; case ButtonState.Expanded: const int expand_duration = 500; @@ -276,6 +283,33 @@ namespace osu.Game.Screens.Menu StateChanged?.Invoke(State); } } + + public ButtonSystemState ButtonSystemState + { + set + { + ContractStyle = 0; + + switch (value) + { + case ButtonSystemState.Initial: + State = ButtonState.Contracted; + break; + case ButtonSystemState.EnteringMode: + ContractStyle = 1; + State = ButtonState.Contracted; + break; + default: + if (!VisibleState.HasValue || value == VisibleState) + State = ButtonState.Expanded; + else if (value < VisibleState) + State = ButtonState.Contracted; + else + State = ButtonState.Exploded; + break; + } + } + } } public enum ButtonState diff --git a/osu.Game/Screens/Menu/ButtonArea.cs b/osu.Game/Screens/Menu/ButtonArea.cs new file mode 100644 index 0000000000..06004405b6 --- /dev/null +++ b/osu.Game/Screens/Menu/ButtonArea.cs @@ -0,0 +1,148 @@ +// Copyright (c) 2007-2018 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System; +using osu.Framework; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using OpenTK; + +namespace osu.Game.Screens.Menu +{ + public class ButtonArea : Container, IStateful + { + public FlowContainerWithOrigin Flow; + + protected override Container Content => Flow; + + private readonly ButtonAreaBackground buttonAreaBackground; + private Visibility state; + + public const float BUTTON_AREA_HEIGHT = 100; + + public ButtonArea() + { + RelativeSizeAxes = Axes.Both; + InternalChild = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.X, + Size = new Vector2(1, BUTTON_AREA_HEIGHT), + Alpha = 0, + Children = new Drawable[] + { + buttonAreaBackground = new ButtonAreaBackground(), + Flow = new FlowContainerWithOrigin + { + Direction = FillDirection.Horizontal, + Spacing = new Vector2(-ButtonSystem.WEDGE_WIDTH, 0), + Anchor = Anchor.Centre, + AutoSizeAxes = Axes.Both, + } + } + }; + } + + public ButtonSystemState ButtonSystemState + { + set + { + switch (value) + { + case ButtonSystemState.Exit: + case ButtonSystemState.Initial: + case ButtonSystemState.EnteringMode: + State = Visibility.Hidden; + break; + case ButtonSystemState.TopLevel: + case ButtonSystemState.Play: + State = Visibility.Visible; + break; + } + + buttonAreaBackground.ButtonSystemState = value; + } + } + + public Visibility State + { + get => state; + set + { + if (value == state) return; + + state = value; + InternalChild.FadeTo(state == Visibility.Hidden ? 0 : 1, 300); + StateChanged?.Invoke(state); + } + } + + public event Action StateChanged; + + private class ButtonAreaBackground : Box, IStateful + { + private ButtonAreaBackgroundState state; + + public ButtonAreaBackground() + { + RelativeSizeAxes = Axes.Both; + Size = new Vector2(2, 1); + Colour = OsuColour.Gray(50); + Anchor = Anchor.Centre; + Origin = Anchor.Centre; + } + + public ButtonAreaBackgroundState State + { + get => state; + set + { + if (value == state) return; + + state = value; + + switch (state) + { + case ButtonAreaBackgroundState.Flat: + this.ScaleTo(new Vector2(2, 0), 300, Easing.InSine); + break; + case ButtonAreaBackgroundState.Normal: + this.ScaleTo(Vector2.One, 400, Easing.OutQuint); + break; + } + + StateChanged?.Invoke(state); + } + } + + public ButtonSystemState ButtonSystemState + { + set + { + switch (value) + { + default: + State = ButtonAreaBackgroundState.Normal; + break; + case ButtonSystemState.Initial: + case ButtonSystemState.Exit: + case ButtonSystemState.EnteringMode: + State = ButtonAreaBackgroundState.Flat; + break; + } + } + } + + public event Action StateChanged; + } + + public enum ButtonAreaBackgroundState + { + Normal, + Flat + } + } +} diff --git a/osu.Game/Screens/Menu/ButtonSystem.cs b/osu.Game/Screens/Menu/ButtonSystem.cs index 374877673f..4f2437cf01 100644 --- a/osu.Game/Screens/Menu/ButtonSystem.cs +++ b/osu.Game/Screens/Menu/ButtonSystem.cs @@ -10,9 +10,9 @@ using osu.Framework.Audio; using osu.Framework.Audio.Sample; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; using osu.Framework.Input; using osu.Framework.Input.Bindings; +using osu.Framework.Logging; using osu.Framework.Threading; using osu.Game.Graphics; using osu.Game.Input.Bindings; @@ -23,9 +23,9 @@ using OpenTK.Input; namespace osu.Game.Screens.Menu { - public class ButtonSystem : Container, IStateful, IKeyBindingHandler + public class ButtonSystem : Container, IStateful, IKeyBindingHandler { - public event Action StateChanged; + public event Action StateChanged; public Action OnEdit; public Action OnExit; @@ -34,12 +34,6 @@ namespace osu.Game.Screens.Menu public Action OnSettings; public Action OnMulti; public Action OnChart; - public Action OnTest; - - private readonly FlowContainerWithOrigin buttonFlow; - - //todo: make these non-internal somehow. - public const float BUTTON_AREA_HEIGHT = 100; public const float BUTTON_WIDTH = 140f; public const float WEDGE_WIDTH = 20; @@ -55,18 +49,16 @@ namespace osu.Game.Screens.Menu this.logo.Action = onOsuLogo; // osuLogo.SizeForFlow relies on loading to be complete. - buttonFlow.Position = new Vector2(WEDGE_WIDTH * 2 - (BUTTON_WIDTH + this.logo.SizeForFlow / 4), 0); + buttonArea.Flow.Position = new Vector2(WEDGE_WIDTH * 2 - (BUTTON_WIDTH + this.logo.SizeForFlow / 4), 0); updateLogoState(); } } private readonly Drawable iconFacade; - private readonly Container buttonArea; - private readonly Box buttonAreaBackground; + private readonly ButtonArea buttonArea; private readonly Button backButton; - private readonly Button settingsButton; private readonly List