diff --git a/osu-framework b/osu-framework index 5f3a7fe4d0..cdb031c3a8 160000 --- a/osu-framework +++ b/osu-framework @@ -1 +1 @@ -Subproject commit 5f3a7fe4d0537820a33b817a41623b4b22a3ec59 +Subproject commit cdb031c3a8ef693cd71458c5e19c68127ab72938 diff --git a/osu.Game.Rulesets.Catch/Mods/CatchMod.cs b/osu.Game.Rulesets.Catch/Mods/CatchMod.cs index b0880d7e1d..66261b0f0f 100644 --- a/osu.Game.Rulesets.Catch/Mods/CatchMod.cs +++ b/osu.Game.Rulesets.Catch/Mods/CatchMod.cs @@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Catch.Mods public class CatchModHidden : ModHidden { - public override string Description => @"Play with no approach circles and fading notes for a slight score advantage."; + public override string Description => @"Play with fading notes for a slight score advantage."; public override double ScoreMultiplier => 1.06; } diff --git a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs index be48c997ea..35bebf2d4f 100644 --- a/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs +++ b/osu.Game.Tests/Beatmaps/IO/ImportBeatmapTest.cs @@ -24,7 +24,7 @@ namespace osu.Game.Tests.Beatmaps.IO public void TestImportWhenClosed() { //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new HeadlessGameHost()) + using (HeadlessGameHost host = new HeadlessGameHost("TestImportWhenClosed")) { var osu = loadOsu(host); @@ -36,7 +36,7 @@ namespace osu.Game.Tests.Beatmaps.IO ensureLoaded(osu); - Assert.IsFalse(File.Exists(temp)); + waitForOrAssert(() => !File.Exists(temp), "Temporary file still exists after standard import", 5000); } } @@ -61,15 +61,14 @@ namespace osu.Game.Tests.Beatmaps.IO ensureLoaded(osu); - Assert.IsFalse(File.Exists(temp)); + waitForOrAssert(() => !File.Exists(temp), "Temporary still exists after IPC import", 5000); } } [Test] public void TestImportWhenFileOpen() { - //unfortunately for the time being we need to reference osu.Framework.Desktop for a game host here. - using (HeadlessGameHost host = new HeadlessGameHost()) + using (HeadlessGameHost host = new HeadlessGameHost("TestImportWhenFileOpen")) { var osu = loadOsu(host); @@ -101,8 +100,7 @@ namespace osu.Game.Tests.Beatmaps.IO var osu = new OsuGameBase(); Task.Run(() => host.Run(osu)); - while (!osu.IsLoaded) - Thread.Sleep(1); + waitForOrAssert(() => osu.IsLoaded, @"osu! failed to start in a reasonable amount of time"); return osu; } @@ -113,30 +111,17 @@ namespace osu.Game.Tests.Beatmaps.IO var store = osu.Dependencies.Get(); - Action waitAction = () => - { - while (!(resultSets = store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526)).Any()) - Thread.Sleep(50); - }; - - Assert.IsTrue(waitAction.BeginInvoke(null, null).AsyncWaitHandle.WaitOne(timeout), - @"BeatmapSet did not import to the database in allocated time."); + waitForOrAssert(() => (resultSets = store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526)).Any(), + @"BeatmapSet did not import to the database in allocated time.", timeout); //ensure we were stored to beatmap database backing... - Assert.IsTrue(resultSets.Count() == 1, $@"Incorrect result count found ({resultSets.Count()} but should be 1)."); IEnumerable resultBeatmaps = null; //if we don't re-check here, the set will be inserted but the beatmaps won't be present yet. - waitAction = () => - { - while ((resultBeatmaps = store.QueryBeatmaps(s => s.OnlineBeatmapSetID == 241526 && s.BaseDifficultyID > 0)).Count() != 12) - Thread.Sleep(50); - }; - - Assert.IsTrue(waitAction.BeginInvoke(null, null).AsyncWaitHandle.WaitOne(timeout), - @"Beatmaps did not import to the database in allocated time"); + waitForOrAssert(() => (resultBeatmaps = store.QueryBeatmaps(s => s.OnlineBeatmapSetID == 241526 && s.BaseDifficultyID > 0)).Count() == 12, + @"Beatmaps did not import to the database in allocated time", timeout); var set = store.QueryBeatmapSets(s => s.OnlineBeatmapSetID == 241526).First(); @@ -160,5 +145,11 @@ namespace osu.Game.Tests.Beatmaps.IO beatmap = store.GetWorkingBeatmap(set.Beatmaps.First(b => b.RulesetID == 3))?.Beatmap; Assert.IsTrue(beatmap?.HitObjects.Count > 0); } + + private void waitForOrAssert(Func result, string failureMessage, int timeout = 60000) + { + Action waitAction = () => { while (!result()) Thread.Sleep(20); }; + Assert.IsTrue(waitAction.BeginInvoke(null, null).AsyncWaitHandle.WaitOne(timeout), failureMessage); + } } } diff --git a/osu.Game/Beatmaps/BeatmapInfo.cs b/osu.Game/Beatmaps/BeatmapInfo.cs index 0776669811..5e4e122fb5 100644 --- a/osu.Game/Beatmaps/BeatmapInfo.cs +++ b/osu.Game/Beatmaps/BeatmapInfo.cs @@ -78,15 +78,29 @@ namespace osu.Game.Beatmaps // Editor // This bookmarks stuff is necessary because DB doesn't know how to store int[] - public string StoredBookmarks { get; set; } + [JsonIgnore] + public string StoredBookmarks + { + get { return string.Join(",", Bookmarks); } + set + { + if (string.IsNullOrEmpty(value)) + { + Bookmarks = new int[0]; + return; + } + + Bookmarks = value.Split(',').Select(v => + { + int val; + bool result = int.TryParse(v, out val); + return new { result, val }; + }).Where(p => p.result).Select(p => p.val).ToArray(); + } + } [Ignore] - [JsonIgnore] - public int[] Bookmarks - { - get { return StoredBookmarks.Split(',').Select(int.Parse).ToArray(); } - set { StoredBookmarks = string.Join(",", value); } - } + public int[] Bookmarks { get; set; } = new int[0]; public double DistanceSpacing { get; set; } public int BeatDivisor { get; set; } diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 42474ce80b..b000f08369 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -54,6 +54,7 @@ namespace osu.Game.Configuration // Graphics Set(OsuSetting.ShowFpsDisplay, false); + Set(OsuSetting.ShowStoryboard, true); Set(OsuSetting.CursorRotation, true); Set(OsuSetting.MenuParallax, true); @@ -89,6 +90,7 @@ namespace osu.Game.Configuration GameplayCursorSize, AutoCursorSize, DimLevel, + ShowStoryboard, KeyOverlay, FloatingComments, PlaybackSpeed, diff --git a/osu.Game/Online/API/APIAccess.cs b/osu.Game/Online/API/APIAccess.cs index 57f5c54a18..bb72efb750 100644 --- a/osu.Game/Online/API/APIAccess.cs +++ b/osu.Game/Online/API/APIAccess.cs @@ -101,18 +101,16 @@ namespace osu.Game.Online.API } break; case APIState.Offline: + case APIState.Connecting: //work to restore a connection... if (!HasLogin) { - //OsuGame.Scheduler.Add(() => { OsuGame.ShowLogin(); }); - State = APIState.Offline; - Thread.Sleep(500); + Thread.Sleep(50); continue; } - if (State < APIState.Connecting) - State = APIState.Connecting; + State = APIState.Connecting; if (!authentication.HasValidAccessToken && !authentication.AuthenticateWithLogin(Username, Password)) { @@ -125,7 +123,8 @@ namespace osu.Game.Online.API var userReq = new GetUserRequest(); - userReq.Success += u => { + userReq.Success += u => + { LocalUser.Value = u; //we're connected! State = APIState.Online; @@ -133,16 +132,14 @@ namespace osu.Game.Online.API }; if (!handleRequest(userReq)) - { - State = APIState.Failing; continue; - } break; } //hard bail if we can't get a valid access token. if (authentication.RequestAccessToken() == null) { + Logout(false); State = APIState.Offline; continue; } @@ -162,20 +159,12 @@ namespace osu.Game.Online.API } } - private void clearCredentials() - { - Username = null; - Password = null; - } - public void Login(string username, string password) { Debug.Assert(State == APIState.Offline); Username = username; Password = password; - - State = APIState.Connecting; } /// @@ -204,7 +193,7 @@ namespace osu.Game.Online.API switch (statusCode) { case HttpStatusCode.Unauthorized: - State = APIState.Offline; + Logout(false); return true; case HttpStatusCode.RequestTimeout: failureCount++; @@ -215,6 +204,7 @@ namespace osu.Game.Online.API return false; State = APIState.Failing; + flushQueue(); return true; } @@ -235,33 +225,21 @@ namespace osu.Game.Online.API public APIState State { get { return state; } - set + private set { APIState oldState = state; APIState newState = value; state = value; - switch (state) - { - case APIState.Failing: - case APIState.Offline: - flushQueue(); - break; - } - if (oldState != newState) { - //OsuGame.Scheduler.Add(delegate + log.Add($@"We just went {newState}!"); + Scheduler.Add(delegate { - //NotificationOverlay.ShowMessage($@"We just went {newState}!", newState == APIState.Online ? Color4.YellowGreen : Color4.OrangeRed, 5000); - log.Add($@"We just went {newState}!"); - Scheduler.Add(delegate - { - components.ForEach(c => c.APIStateChanged(this, newState)); - OnStateChange?.Invoke(oldState, newState); - }); - } + components.ForEach(c => c.APIStateChanged(this, newState)); + OnStateChange?.Invoke(oldState, newState); + }); } } } @@ -292,11 +270,12 @@ namespace osu.Game.Online.API } } - public void Logout() + public void Logout(bool clearUsername = true) { - clearCredentials(); + flushQueue(); + if (clearUsername) Username = null; + Password = null; authentication.Clear(); - State = APIState.Offline; LocalUser.Value = createGuestUser(); } diff --git a/osu.Game/Online/API/Requests/GetScoresRequest.cs b/osu.Game/Online/API/Requests/GetScoresRequest.cs index 13bd8d288d..537fce2548 100644 --- a/osu.Game/Online/API/Requests/GetScoresRequest.cs +++ b/osu.Game/Online/API/Requests/GetScoresRequest.cs @@ -80,7 +80,7 @@ namespace osu.Game.Online.API.Requests } [JsonProperty(@"statistics")] - private Dictionary jsonStats + private Dictionary jsonStats { set { diff --git a/osu.Game/Overlays/BeatmapSetOverlay.cs b/osu.Game/Overlays/BeatmapSetOverlay.cs index 8e28ad33c5..7a4c6338a1 100644 --- a/osu.Game/Overlays/BeatmapSetOverlay.cs +++ b/osu.Game/Overlays/BeatmapSetOverlay.cs @@ -23,6 +23,9 @@ namespace osu.Game.Overlays private readonly Header header; private readonly Info info; + // receive input outside our bounds so we can trigger a close event on ourselves. + public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; + public BeatmapSetOverlay() { FirstWaveColour = OsuColour.Gray(0.4f); diff --git a/osu.Game/Overlays/Direct/Header.cs b/osu.Game/Overlays/Direct/Header.cs index 2c50fb453f..77743a3a4b 100644 --- a/osu.Game/Overlays/Direct/Header.cs +++ b/osu.Game/Overlays/Direct/Header.cs @@ -21,7 +21,7 @@ namespace osu.Game.Overlays.Direct public Header() { - Tabs.Current.Value = DirectTab.Search; + Tabs.Current.Value = DirectTab.NewestMaps; Tabs.Current.TriggerChange(); } } diff --git a/osu.Game/Overlays/DirectOverlay.cs b/osu.Game/Overlays/DirectOverlay.cs index 9bb2afe127..9d79e87b1d 100644 --- a/osu.Game/Overlays/DirectOverlay.cs +++ b/osu.Game/Overlays/DirectOverlay.cs @@ -269,7 +269,7 @@ namespace osu.Game.Overlays if (Header.Tabs.Current.Value == DirectTab.Search && (Filter.Search.Text == string.Empty || currentQuery == string.Empty)) return; - getSetsRequest = new GetBeatmapSetsRequest(currentQuery, + getSetsRequest = new GetBeatmapSetsRequest(currentQuery.Value ?? string.Empty, ((FilterControl)Filter).Ruleset.Value, Filter.DisplayStyleControl.Dropdown.Current.Value, Filter.Tabs.Current.Value); //todo: sort direction (?) diff --git a/osu.Game/Overlays/LoginOverlay.cs b/osu.Game/Overlays/LoginOverlay.cs index 58b259fcbb..0a47637589 100644 --- a/osu.Game/Overlays/LoginOverlay.cs +++ b/osu.Game/Overlays/LoginOverlay.cs @@ -3,6 +3,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Graphics; using osu.Game.Overlays.Settings.Sections.General; using OpenTK.Graphics; @@ -28,35 +29,43 @@ namespace osu.Game.Overlays { Children = new Drawable[] { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - Alpha = 0.6f, - }, new OsuContextMenuContainer { Width = 360, AutoSizeAxes = Axes.Y, - Masking = true, - AutoSizeDuration = transition_time, - AutoSizeEasing = Easing.OutQuint, Children = new Drawable[] { - settingsSection = new LoginSettings - { - Padding = new MarginPadding(10), - RequestHide = Hide, - }, new Box { - RelativeSizeAxes = Axes.X, - Anchor = Anchor.BottomLeft, - Origin = Anchor.BottomLeft, - Height = 3, - Colour = colours.Yellow, - Alpha = 1, + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, + Alpha = 0.6f, }, + new Container + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Masking = true, + AutoSizeDuration = transition_time, + AutoSizeEasing = Easing.OutQuint, + Children = new Drawable[] + { + settingsSection = new LoginSettings + { + Padding = new MarginPadding(10), + RequestHide = Hide, + }, + new Box + { + RelativeSizeAxes = Axes.X, + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + Height = 3, + Colour = colours.Yellow, + Alpha = 1, + }, + } + } } } }; diff --git a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs index 0e348f3791..00e6b8b722 100644 --- a/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Graphics/DetailSettings.cs @@ -14,6 +14,11 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics { Children = new[] { + new SettingsCheckbox + { + LabelText = "Storyboards", + Bindable = config.GetBindable(OsuSetting.ShowStoryboard) + }, new SettingsCheckbox { LabelText = "Rotate cursor when dragging", diff --git a/osu.Game/Overlays/UserProfileOverlay.cs b/osu.Game/Overlays/UserProfileOverlay.cs index f03ef3f1ed..088b0a1335 100644 --- a/osu.Game/Overlays/UserProfileOverlay.cs +++ b/osu.Game/Overlays/UserProfileOverlay.cs @@ -34,6 +34,7 @@ namespace osu.Game.Overlays public const float CONTENT_X_MARGIN = 50; + // receive input outside our bounds so we can trigger a close event on ourselves. public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true; protected override bool OnClick(InputState state) diff --git a/osu.Game/Overlays/WaveOverlayContainer.cs b/osu.Game/Overlays/WaveOverlayContainer.cs index 4f9783a762..77c532350b 100644 --- a/osu.Game/Overlays/WaveOverlayContainer.cs +++ b/osu.Game/Overlays/WaveOverlayContainer.cs @@ -1,7 +1,6 @@ // Copyright (c) 2007-2017 ppy Pty Ltd . // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE -using osu.Framework; using OpenTK.Graphics; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics.Containers; @@ -165,10 +164,8 @@ namespace osu.Game.Overlays wavesContainer.Height = Math.Max(0, DrawHeight - (contentContainer.DrawHeight - contentContainer.Y)); } - private class Wave : Container, IStateful + private class Wave : VisibilityContainer { - public event Action StateChanged; - public float FinalPosition; public Wave() @@ -183,13 +180,7 @@ namespace osu.Game.Overlays Radius = 20f, }; - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - }, - }; + Child = new Box { RelativeSizeAxes = Axes.Both }; } protected override void Update() @@ -201,28 +192,8 @@ namespace osu.Game.Overlays Height = Parent.Parent.DrawSize.Y * 1.5f; } - private Visibility state; - - public Visibility State - { - get { return state; } - set - { - state = value; - - switch (value) - { - case Visibility.Hidden: - this.MoveToY(Parent.Parent.DrawSize.Y, DISAPPEAR_DURATION, easing_hide); - break; - case Visibility.Visible: - this.MoveToY(FinalPosition, APPEAR_DURATION, easing_show); - break; - } - - StateChanged?.Invoke(State); - } - } + protected override void PopIn() => this.MoveToY(FinalPosition, APPEAR_DURATION, easing_show); + protected override void PopOut() => this.MoveToY(Parent.Parent.DrawSize.Y, DISAPPEAR_DURATION, easing_hide); } } } diff --git a/osu.Game/Rulesets/Scoring/Score.cs b/osu.Game/Rulesets/Scoring/Score.cs index 7f053ec1c5..c4ffa4e93c 100644 --- a/osu.Game/Rulesets/Scoring/Score.cs +++ b/osu.Game/Rulesets/Scoring/Score.cs @@ -38,6 +38,6 @@ namespace osu.Game.Rulesets.Scoring public DateTimeOffset Date; - public Dictionary Statistics = new Dictionary(); + public Dictionary Statistics = new Dictionary(); } } diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs new file mode 100644 index 0000000000..1793cb4334 --- /dev/null +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BookmarkPart.cs @@ -0,0 +1,34 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; + +namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts +{ + /// + /// The part of the timeline that displays bookmarks. + /// + internal class BookmarkPart : TimelinePart + { + protected override void LoadBeatmap(WorkingBeatmap beatmap) + { + base.LoadBeatmap(beatmap); + foreach (int bookmark in beatmap.BeatmapInfo.Bookmarks) + Add(new BookmarkVisualisation(bookmark)); + } + + private class BookmarkVisualisation : PointVisualisation + { + public BookmarkVisualisation(double startTime) + : base(startTime) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) => Colour = colours.Blue; + } + } +} diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs new file mode 100644 index 0000000000..004491d489 --- /dev/null +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/BreakPart.cs @@ -0,0 +1,35 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Allocation; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.Timing; +using osu.Game.Graphics; +using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; + +namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts +{ + /// + /// The part of the timeline that displays breaks in the song. + /// + internal class BreakPart : TimelinePart + { + protected override void LoadBeatmap(WorkingBeatmap beatmap) + { + base.LoadBeatmap(beatmap); + foreach (var breakPeriod in beatmap.Beatmap.Breaks) + Add(new BreakVisualisation(breakPeriod)); + } + + private class BreakVisualisation : DurationVisualisation + { + public BreakVisualisation(BreakPeriod breakPeriod) + : base(breakPeriod.StartTime, breakPeriod.EndTime) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) => Colour = colours.Yellow; + } + } +} diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs new file mode 100644 index 0000000000..d230578e13 --- /dev/null +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs @@ -0,0 +1,70 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Extensions.IEnumerableExtensions; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations; + +namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts +{ + /// + /// The part of the timeline that displays the control points. + /// + internal class ControlPointPart : TimelinePart + { + protected override void LoadBeatmap(WorkingBeatmap beatmap) + { + base.LoadBeatmap(beatmap); + + ControlPointInfo cpi = beatmap.Beatmap.ControlPointInfo; + + cpi.TimingPoints.ForEach(addTimingPoint); + + // Consider all non-timing points as the same type + cpi.SoundPoints.Select(c => (ControlPoint)c) + .Concat(cpi.EffectPoints) + .Concat(cpi.DifficultyPoints) + .Distinct() + // Non-timing points should not be added where there are timing points + .Where(c => cpi.TimingPointAt(c.Time).Time != c.Time) + .ForEach(addNonTimingPoint); + } + + private void addTimingPoint(ControlPoint controlPoint) => Add(new TimingPointVisualisation(controlPoint)); + private void addNonTimingPoint(ControlPoint controlPoint) => Add(new NonTimingPointVisualisation(controlPoint)); + + private class TimingPointVisualisation : ControlPointVisualisation + { + public TimingPointVisualisation(ControlPoint controlPoint) + : base(controlPoint) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) => Colour = colours.YellowDark; + } + + private class NonTimingPointVisualisation : ControlPointVisualisation + { + public NonTimingPointVisualisation(ControlPoint controlPoint) + : base(controlPoint) + { + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) => Colour = colours.Green; + } + + private abstract class ControlPointVisualisation : PointVisualisation + { + protected ControlPointVisualisation(ControlPoint controlPoint) + : base(controlPoint.Time) + { + } + } + } +} diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs new file mode 100644 index 0000000000..0bdd081907 --- /dev/null +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/MarkerPart.cs @@ -0,0 +1,106 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input; +using osu.Game.Beatmaps; +using osu.Game.Graphics; + +namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts +{ + /// + /// The part of the timeline that displays the current position of the song. + /// + internal class MarkerPart : TimelinePart + { + private readonly Drawable marker; + + public MarkerPart() + { + Add(marker = new MarkerVisualisation()); + } + + protected override bool OnDragStart(InputState state) => true; + protected override bool OnDragEnd(InputState state) => true; + protected override bool OnDrag(InputState state) + { + seekToPosition(state.Mouse.NativeState.Position); + return true; + } + + protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) + { + seekToPosition(state.Mouse.NativeState.Position); + return true; + } + + /// + /// Seeks the to the time closest to a position on the screen relative to the . + /// + /// The position in screen coordinates. + private void seekToPosition(Vector2 screenPosition) + { + if (Beatmap.Value == null) + return; + + float markerPos = MathHelper.Clamp(ToLocalSpace(screenPosition).X, 0, DrawWidth); + seekTo(markerPos / DrawWidth * Beatmap.Value.Track.Length); + } + + private void seekTo(double time) => Beatmap.Value?.Track.Seek(time); + + protected override void Update() + { + base.Update(); + marker.X = (float)(Beatmap.Value?.Track.CurrentTime ?? 0); + } + + protected override void LoadBeatmap(WorkingBeatmap beatmap) + { + // block base call so we don't clear our marker (can be reused on beatmap change). + } + + private class MarkerVisualisation : CompositeDrawable + { + public MarkerVisualisation() + { + Anchor = Anchor.CentreLeft; + Origin = Anchor.Centre; + RelativePositionAxes = Axes.X; + RelativeSizeAxes = Axes.Y; + AutoSizeAxes = Axes.X; + InternalChildren = new Drawable[] + { + new Triangle + { + Anchor = Anchor.TopCentre, + Origin = Anchor.BottomCentre, + Scale = new Vector2(1, -1), + Size = new Vector2(10, 5), + }, + new Triangle + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + Size = new Vector2(10, 5) + }, + new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Y, + Width = 2, + EdgeSmoothness = new Vector2(1, 0) + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) => Colour = colours.Red; + } + } +} diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs new file mode 100644 index 0000000000..378ce78c67 --- /dev/null +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/TimelinePart.cs @@ -0,0 +1,53 @@ +// 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 osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Beatmaps; + +namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts +{ + /// + /// Represents a part of the summary timeline.. + /// + internal abstract class TimelinePart : CompositeDrawable + { + public Bindable Beatmap = new Bindable(); + + private readonly Container timeline; + + protected TimelinePart() + { + AddInternal(timeline = new Container { RelativeSizeAxes = Axes.Both }); + + Beatmap.ValueChanged += b => + { + updateRelativeChildSize(); + LoadBeatmap(b); + }; + } + + private void updateRelativeChildSize() + { + // the track may not be loaded completely (only has a length once it is). + if (!Beatmap.Value.Track.IsLoaded) + { + timeline.RelativeChildSize = Vector2.One; + Schedule(updateRelativeChildSize); + return; + } + + timeline.RelativeChildSize = new Vector2((float)Math.Max(1, Beatmap.Value.Track.Length), 1); + } + + protected void Add(Drawable visualisation) => timeline.Add(visualisation); + + protected virtual void LoadBeatmap(WorkingBeatmap beatmap) + { + timeline.Clear(); + } + } +} diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs new file mode 100644 index 0000000000..4d925f7584 --- /dev/null +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/SummaryTimeline.cs @@ -0,0 +1,112 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Allocation; +using osu.Framework.Configuration; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts; + +namespace osu.Game.Screens.Edit.Components.Timelines.Summary +{ + /// + /// The timeline that sits at the bottom of the editor. + /// + public class SummaryTimeline : CompositeDrawable + { + private const float corner_radius = 5; + private const float contents_padding = 15; + + public Bindable Beatmap = new Bindable(); + + private readonly Drawable background; + + private readonly Drawable timelineBar; + + public SummaryTimeline() + { + Masking = true; + CornerRadius = corner_radius; + + TimelinePart markerPart, controlPointPart, bookmarkPart, breakPart; + + InternalChildren = new[] + { + background = new Box { RelativeSizeAxes = Axes.Both }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Left = contents_padding, Right = contents_padding }, + Children = new[] + { + markerPart = new MarkerPart { RelativeSizeAxes = Axes.Both }, + controlPointPart = new ControlPointPart + { + Anchor = Anchor.Centre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.Both, + Height = 0.35f + }, + bookmarkPart = new BookmarkPart + { + Anchor = Anchor.Centre, + Origin = Anchor.TopCentre, + RelativeSizeAxes = Axes.Both, + Height = 0.35f + }, + timelineBar = new Container + { + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + new Circle + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreRight, + Size = new Vector2(5) + }, + new Box + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + RelativeSizeAxes = Axes.X, + Height = 1, + EdgeSmoothness = new Vector2(0, 1), + }, + new Circle + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreLeft, + Size = new Vector2(5) + }, + } + }, + breakPart = new BreakPart + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Height = 0.25f + } + } + } + }; + + markerPart.Beatmap.BindTo(Beatmap); + controlPointPart.Beatmap.BindTo(Beatmap); + bookmarkPart.Beatmap.BindTo(Beatmap); + breakPart.Beatmap.BindTo(Beatmap); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Gray1; + timelineBar.Colour = colours.Gray5; + } + } +} diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/DurationVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/DurationVisualisation.cs new file mode 100644 index 0000000000..aee8e250c3 --- /dev/null +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/DurationVisualisation.cs @@ -0,0 +1,28 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; + +namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations +{ + /// + /// Represents a spanning point on a timeline part. + /// + internal class DurationVisualisation : Container + { + protected DurationVisualisation(double startTime, double endTime) + { + Masking = true; + CornerRadius = 5; + + RelativePositionAxes = Axes.X; + RelativeSizeAxes = Axes.Both; + X = (float)startTime; + Width = (float)(endTime - startTime); + + AddInternal(new Box { RelativeSizeAxes = Axes.Both }); + } + } +} diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs new file mode 100644 index 0000000000..9d7272808b --- /dev/null +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Visualisations/PointVisualisation.cs @@ -0,0 +1,27 @@ +// Copyright (c) 2007-2017 ppy Pty Ltd . +// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE + +using OpenTK; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; + +namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Visualisations +{ + /// + /// Represents a singular point on a timeline part. + /// + internal class PointVisualisation : Box + { + protected PointVisualisation(double startTime) + { + Origin = Anchor.TopCentre; + + RelativeSizeAxes = Axes.Y; + Width = 1; + EdgeSmoothness = new Vector2(1, 0); + + RelativePositionAxes = Axes.X; + X = (float)startTime; + } + } +} diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index be9098e3be..3ffd7754c1 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -1,29 +1,29 @@ // 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.Graphics; using osu.Framework.Screens; using osu.Game.Screens.Backgrounds; -using osu.Game.Screens.Select; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Edit.Menus; +using osu.Game.Screens.Edit.Components.Timelines.Summary; +using OpenTK; +using osu.Framework.Allocation; namespace osu.Game.Screens.Edit { - internal class Editor : ScreenWhiteBox + internal class Editor : OsuScreen { - protected override IEnumerable PossibleChildren => new[] { typeof(EditSongSelect) }; - protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg4"); internal override bool ShowOverlays => false; + private readonly Box bottomBackground; + public Editor() { Add(new Container @@ -189,6 +189,49 @@ namespace osu.Game.Screens.Edit } } }); + + SummaryTimeline summaryTimeline; + Add(new Container + { + Anchor = Anchor.BottomLeft, + Origin = Anchor.BottomLeft, + RelativeSizeAxes = Axes.X, + Height = 60, + Children = new Drawable[] + { + bottomBackground = new Box { RelativeSizeAxes = Axes.Both }, + new Container + { + RelativeSizeAxes = Axes.Both, + Padding = new MarginPadding { Top = 5, Bottom = 5, Left = 10, Right = 10 }, + Child = new FillFlowContainer + { + Name = "Bottom bar", + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), + Children = new[] + { + summaryTimeline = new SummaryTimeline + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Width = 0.65f + } + } + } + } + } + }); + + summaryTimeline.Beatmap.BindTo(Beatmap); + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + bottomBackground.Colour = colours.Gray2; } protected override void OnResuming(Screen last) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 593abb7d26..e120c7f193 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -24,6 +24,8 @@ using osu.Game.Screens.Ranking; using osu.Framework.Audio.Sample; using osu.Game.Beatmaps; using osu.Game.Online.API; +using osu.Game.Storyboards.Drawables; +using OpenTK.Graphics; namespace osu.Game.Screens.Play { @@ -59,6 +61,7 @@ namespace osu.Game.Screens.Play #region User Settings private Bindable dimLevel; + private Bindable showStoryboard; private Bindable mouseWheelDisabled; private Bindable userAudioOffset; @@ -66,6 +69,9 @@ namespace osu.Game.Screens.Play #endregion + private Container storyboardContainer; + private DrawableStoryboard storyboard; + private HUDOverlay hudOverlay; private FailOverlay failOverlay; @@ -77,6 +83,7 @@ namespace osu.Game.Screens.Play this.api = api; dimLevel = config.GetBindable(OsuSetting.DimLevel); + showStoryboard = config.GetBindable(OsuSetting.ShowStoryboard); mouseWheelDisabled = config.GetBindable(OsuSetting.MouseDisableWheel); @@ -145,6 +152,12 @@ namespace osu.Game.Screens.Play Children = new Drawable[] { + storyboardContainer = new Container + { + RelativeSizeAxes = Axes.Both, + Clock = offsetClock, + Alpha = 0, + }, pauseContainer = new PauseContainer { AudioClock = decoupledClock, @@ -196,6 +209,9 @@ namespace osu.Game.Screens.Play scoreProcessor = RulesetContainer.CreateScoreProcessor(); + if (showStoryboard) + initializeStoryboard(false); + hudOverlay.BindProcessor(scoreProcessor); hudOverlay.BindRulesetContainer(RulesetContainer); @@ -211,6 +227,16 @@ namespace osu.Game.Screens.Play scoreProcessor.Failed += onFail; } + private void initializeStoryboard(bool asyncLoad) + { + var beatmap = Beatmap.Value.Beatmap; + + storyboard = beatmap.Storyboard.CreateDrawable(Beatmap.Value); + storyboard.Masking = true; + + storyboardContainer.Add(asyncLoad ? new AsyncLoadWrapper(storyboard) { RelativeSizeAxes = Axes.Both } : (Drawable)storyboard); + } + public void Restart() { sampleRestart?.Play(); @@ -266,12 +292,12 @@ namespace osu.Game.Screens.Play return; (Background as BackgroundScreenBeatmap)?.BlurTo(Vector2.Zero, 1500, Easing.OutQuint); - Background?.FadeTo(1 - (float)dimLevel, 1500, Easing.OutQuint); + + dimLevel.ValueChanged += dimLevel_ValueChanged; + showStoryboard.ValueChanged += showStoryboard_ValueChanged; + updateBackgroundElements(); Content.Alpha = 0; - - dimLevel.ValueChanged += newDim => Background?.FadeTo(1 - (float)newDim, 800); - Content .ScaleTo(0.7f) .ScaleTo(1, 750, Easing.OutQuint) @@ -310,8 +336,33 @@ namespace osu.Game.Screens.Play return true; } + private void dimLevel_ValueChanged(double newValue) + => updateBackgroundElements(); + + private void showStoryboard_ValueChanged(bool newValue) + => updateBackgroundElements(); + + private void updateBackgroundElements() + { + var opacity = 1 - (float)dimLevel; + + if (showStoryboard && storyboard == null) + initializeStoryboard(true); + + var beatmap = Beatmap.Value; + var storyboardVisible = showStoryboard && beatmap.Beatmap.Storyboard.HasDrawable; + + storyboardContainer.FadeColour(new Color4(opacity, opacity, opacity, 1), 800); + storyboardContainer.FadeTo(storyboardVisible && opacity > 0 ? 1 : 0); + + Background?.FadeTo(!storyboardVisible || beatmap.Background == null ? opacity : 0, 800, Easing.OutQuint); + } + private void fadeOut() { + dimLevel.ValueChanged -= dimLevel_ValueChanged; + showStoryboard.ValueChanged -= showStoryboard_ValueChanged; + const float fade_out_duration = 250; RulesetContainer?.FadeOut(fade_out_duration); diff --git a/osu.Game/Screens/Ranking/ResultsPageScore.cs b/osu.Game/Screens/Ranking/ResultsPageScore.cs index bf406ff912..b01410cff5 100644 --- a/osu.Game/Screens/Ranking/ResultsPageScore.cs +++ b/osu.Game/Screens/Ranking/ResultsPageScore.cs @@ -186,9 +186,9 @@ namespace osu.Game.Screens.Ranking private class DrawableScoreStatistic : Container { - private readonly KeyValuePair statistic; + private readonly KeyValuePair statistic; - public DrawableScoreStatistic(KeyValuePair statistic) + public DrawableScoreStatistic(KeyValuePair statistic) { this.statistic = statistic; diff --git a/osu.Game/Storyboards/CommandLoop.cs b/osu.Game/Storyboards/CommandLoop.cs index 02b5eb0122..0d8b57e46c 100644 --- a/osu.Game/Storyboards/CommandLoop.cs +++ b/osu.Game/Storyboards/CommandLoop.cs @@ -10,8 +10,8 @@ namespace osu.Game.Storyboards public double LoopStartTime; public int LoopCount; - public override double StartTime => LoopStartTime; - public override double EndTime => LoopStartTime + CommandsDuration * LoopCount; + public override double StartTime => LoopStartTime + CommandsStartTime; + public override double EndTime => StartTime + CommandsDuration * LoopCount; public CommandLoop(double startTime, int loopCount) { diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs index f88e5d118f..5df88b342f 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboard.cs @@ -5,6 +5,7 @@ using OpenTK; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Textures; using osu.Game.IO; @@ -14,6 +15,16 @@ namespace osu.Game.Storyboards.Drawables { public Storyboard Storyboard { get; private set; } + private readonly Background background; + public Texture BackgroundTexture + { + get { return background.Texture; } + set { background.Texture = value; } + } + + private readonly Container content; + protected override Container Content => content; + protected override Vector2 DrawScale => new Vector2(Parent.DrawHeight / 480); public override bool HandleInput => false; @@ -39,6 +50,18 @@ namespace osu.Game.Storyboards.Drawables Size = new Vector2(640, 480); Anchor = Anchor.Centre; Origin = Anchor.Centre; + + AddInternal(background = new Background + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); + AddInternal(content = new Container + { + Size = new Vector2(640, 480), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }); } [BackgroundDependencyLoader] @@ -55,5 +78,10 @@ namespace osu.Game.Storyboards.Drawables foreach (var layer in Children) layer.Enabled = passing ? layer.Layer.EnabledWhenPassing : layer.Layer.EnabledWhenFailing; } + + private class Background : Sprite + { + protected override Vector2 DrawScale => Texture != null ? new Vector2(Parent.DrawHeight / Texture.DisplayHeight) : base.DrawScale; + } } } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs index d8b7d05ee9..9757756316 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardAnimation.cs @@ -14,9 +14,6 @@ namespace osu.Game.Storyboards.Drawables { public StoryboardAnimation Animation { get; private set; } - protected override bool ShouldBeAlive => Animation.HasCommands && base.ShouldBeAlive; - public override bool RemoveWhenNotAlive => !Animation.HasCommands || base.RemoveWhenNotAlive; - public bool FlipH { get; set; } public bool FlipV { get; set; } @@ -59,11 +56,8 @@ namespace osu.Game.Storyboards.Drawables Position = animation.InitialPosition; Repeat = animation.LoopType == AnimationLoopType.LoopForever; - if (animation.HasCommands) - { - LifetimeStart = animation.StartTime; - LifetimeEnd = animation.EndTime; - } + LifetimeStart = animation.StartTime; + LifetimeEnd = animation.EndTime; } [BackgroundDependencyLoader] diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs index 2b5db5b6fa..737704f6d0 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardLayer.cs @@ -28,9 +28,8 @@ namespace osu.Game.Storyboards.Drawables { foreach (var element in Layer.Elements) { - var drawable = element.CreateDrawable(); - if (drawable != null) - Add(drawable); + if (element.IsDrawable) + Add(element.CreateDrawable()); } } } diff --git a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs index 4b491fa008..9153b3e514 100644 --- a/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs +++ b/osu.Game/Storyboards/Drawables/DrawableStoryboardSprite.cs @@ -14,9 +14,6 @@ namespace osu.Game.Storyboards.Drawables { public StoryboardSprite Sprite { get; private set; } - protected override bool ShouldBeAlive => Sprite.HasCommands && base.ShouldBeAlive; - public override bool RemoveWhenNotAlive => !Sprite.HasCommands || base.RemoveWhenNotAlive; - public bool FlipH { get; set; } public bool FlipV { get; set; } @@ -58,11 +55,8 @@ namespace osu.Game.Storyboards.Drawables Origin = sprite.Origin; Position = sprite.InitialPosition; - if (sprite.HasCommands) - { - LifetimeStart = sprite.StartTime; - LifetimeEnd = sprite.EndTime; - } + LifetimeStart = sprite.StartTime; + LifetimeEnd = sprite.EndTime; } [BackgroundDependencyLoader] diff --git a/osu.Game/Storyboards/IStoryboardElement.cs b/osu.Game/Storyboards/IStoryboardElement.cs index d5fc86b0f7..74b6a8d8bc 100644 --- a/osu.Game/Storyboards/IStoryboardElement.cs +++ b/osu.Game/Storyboards/IStoryboardElement.cs @@ -8,6 +8,8 @@ namespace osu.Game.Storyboards public interface IStoryboardElement { string Path { get; } + bool IsDrawable { get; } + Drawable CreateDrawable(); } } diff --git a/osu.Game/Storyboards/Storyboard.cs b/osu.Game/Storyboards/Storyboard.cs index 111cdd5d41..59cbe74650 100644 --- a/osu.Game/Storyboards/Storyboard.cs +++ b/osu.Game/Storyboards/Storyboard.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 osu.Game.Beatmaps; using osu.Game.Storyboards.Drawables; using System.Collections.Generic; using System.Linq; @@ -12,6 +13,8 @@ namespace osu.Game.Storyboards private readonly Dictionary layers = new Dictionary(); public IEnumerable Layers => layers.Values; + public bool HasDrawable => Layers.Any(l => l.Elements.Any(e => e.IsDrawable)); + public Storyboard() { layers.Add("Background", new StoryboardLayer("Background", 3)); @@ -29,7 +32,32 @@ namespace osu.Game.Storyboards return layer; } - public DrawableStoryboard CreateDrawable() - => new DrawableStoryboard(this); + /// + /// Whether the beatmap's background should be hidden while this storyboard is being displayed. + /// + public bool ReplacesBackground(BeatmapInfo beatmapInfo) + { + var backgroundPath = beatmapInfo.BeatmapSet?.Metadata?.BackgroundFile?.ToLowerInvariant(); + if (backgroundPath == null) + return false; + + return GetLayer("Background").Elements.Any(e => e.Path.ToLowerInvariant() == backgroundPath); + } + + public float AspectRatio(BeatmapInfo beatmapInfo) + => beatmapInfo.WidescreenStoryboard ? 16 / 9f : 4 / 3f; + + public DrawableStoryboard CreateDrawable(WorkingBeatmap working = null) + { + var drawable = new DrawableStoryboard(this); + if (working != null) + { + var beatmapInfo = working.Beatmap.BeatmapInfo; + drawable.Width = drawable.Height * AspectRatio(beatmapInfo); + if (!ReplacesBackground(beatmapInfo)) + drawable.BackgroundTexture = working.Background; + } + return drawable; + } } } diff --git a/osu.Game/Storyboards/StoryboardSample.cs b/osu.Game/Storyboards/StoryboardSample.cs index bcf6a4329d..e7a157c2f4 100644 --- a/osu.Game/Storyboards/StoryboardSample.cs +++ b/osu.Game/Storyboards/StoryboardSample.cs @@ -2,12 +2,15 @@ // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE using osu.Framework.Graphics; +using System; namespace osu.Game.Storyboards { public class StoryboardSample : IStoryboardElement { public string Path { get; set; } + public bool IsDrawable => false; + public double Time; public float Volume; @@ -19,6 +22,8 @@ namespace osu.Game.Storyboards } public Drawable CreateDrawable() - => null; + { + throw new InvalidOperationException(); + } } } diff --git a/osu.Game/Storyboards/StoryboardSprite.cs b/osu.Game/Storyboards/StoryboardSprite.cs index 598167d720..349a59dee0 100644 --- a/osu.Game/Storyboards/StoryboardSprite.cs +++ b/osu.Game/Storyboards/StoryboardSprite.cs @@ -16,6 +16,8 @@ namespace osu.Game.Storyboards private readonly List triggers = new List(); public string Path { get; set; } + public bool IsDrawable => HasCommands; + public Anchor Origin; public Vector2 InitialPosition; diff --git a/osu.Game/Tests/Visual/TestCaseContextMenu.cs b/osu.Game/Tests/Visual/TestCaseContextMenu.cs index 28aae1f5b9..91a766f8c7 100644 --- a/osu.Game/Tests/Visual/TestCaseContextMenu.cs +++ b/osu.Game/Tests/Visual/TestCaseContextMenu.cs @@ -9,6 +9,7 @@ using osu.Framework.Graphics.UserInterface; using osu.Game.Graphics.UserInterface; using OpenTK; using OpenTK.Graphics; +using osu.Game.Graphics.Cursor; namespace osu.Game.Tests.Visual { @@ -23,32 +24,32 @@ namespace osu.Game.Tests.Visual public TestCaseContextMenu() { - Add(container = new MyContextMenuContainer + Add(new OsuContextMenuContainer { - Size = new Vector2(200), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, Children = new Drawable[] { - new Box + container = new MyContextMenuContainer { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Green, - } - } - }); - - Add(new AnotherContextMenuContainer - { - Size = new Vector2(200), - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - Children = new Drawable[] - { - new Box + Size = new Vector2(200), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Green, + } + }, + new AnotherContextMenuContainer { - RelativeSizeAxes = Axes.Both, - Colour = Color4.Red, + Size = new Vector2(200), + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Red, + } } } }); diff --git a/osu.Game/Tests/Visual/TestCaseEditorSummaryTimeline.cs b/osu.Game/Tests/Visual/TestCaseEditorSummaryTimeline.cs new file mode 100644 index 0000000000..c35355aefd --- /dev/null +++ b/osu.Game/Tests/Visual/TestCaseEditorSummaryTimeline.cs @@ -0,0 +1,93 @@ +// 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 osu.Framework.Audio.Track; +using osu.Framework.Graphics.Textures; +using osu.Framework.Graphics; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using OpenTK; +using osu.Game.Screens.Edit.Components.Timelines.Summary; +using osu.Framework.Configuration; + +namespace osu.Game.Tests.Visual +{ + internal class TestCaseEditorSummaryTimeline : OsuTestCase + { + private const int length = 60000; + private readonly Random random; + + public override IReadOnlyList RequiredTypes => new[] { typeof(SummaryTimeline) }; + + private readonly Bindable beatmap = new Bindable(); + + public TestCaseEditorSummaryTimeline() + { + random = new Random(1337); + + SummaryTimeline summaryTimeline; + Add(summaryTimeline = new SummaryTimeline + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(500, 50) + }); + + summaryTimeline.Beatmap.BindTo(beatmap); + + AddStep("New beatmap", newBeatmap); + + newBeatmap(); + } + + private void newBeatmap() + { + var b = new Beatmap(); + + for (int i = 0; i < random.Next(1, 10); i++) + b.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { Time = random.Next(0, length) }); + + for (int i = 0; i < random.Next(1, 5); i++) + b.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint { Time = random.Next(0, length) }); + + for (int i = 0; i < random.Next(1, 5); i++) + b.ControlPointInfo.EffectPoints.Add(new EffectControlPoint { Time = random.Next(0, length) }); + + for (int i = 0; i < random.Next(1, 5); i++) + b.ControlPointInfo.SoundPoints.Add(new SoundControlPoint { Time = random.Next(0, length) }); + + b.BeatmapInfo.Bookmarks = new int[random.Next(10, 30)]; + for (int i = 0; i < b.BeatmapInfo.Bookmarks.Length; i++) + b.BeatmapInfo.Bookmarks[i] = random.Next(0, length); + + beatmap.Value = new TestWorkingBeatmap(b); + } + + private class TestWorkingBeatmap : WorkingBeatmap + { + private readonly Beatmap beatmap; + + public TestWorkingBeatmap(Beatmap beatmap) + : base(beatmap.BeatmapInfo) + { + this.beatmap = beatmap; + } + + protected override Texture GetBackground() => null; + + protected override Beatmap GetBeatmap() => beatmap; + + protected override Track GetTrack() => new TestTrack(); + + private class TestTrack : TrackVirtual + { + public TestTrack() + { + Length = length; + } + } + } + } +} diff --git a/osu.Game/Tests/Visual/TestCaseStoryboard.cs b/osu.Game/Tests/Visual/TestCaseStoryboard.cs index f7ac58c632..c6ef3f4ecf 100644 --- a/osu.Game/Tests/Visual/TestCaseStoryboard.cs +++ b/osu.Game/Tests/Visual/TestCaseStoryboard.cs @@ -79,11 +79,13 @@ namespace osu.Game.Tests.Visual storyboardContainer.Remove(storyboard); var decoupledClock = new DecoupleableInterpolatingFramedClock { IsCoupled = true }; - decoupledClock.ChangeSource(working.Track); storyboardContainer.Clock = decoupledClock; - storyboardContainer.Add(storyboard = working.Beatmap.Storyboard.CreateDrawable()); + storyboard = working.Beatmap.Storyboard.CreateDrawable(beatmapBacking); storyboard.Passing = false; + + storyboardContainer.Add(storyboard); + decoupledClock.ChangeSource(working.Track); } } } diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 3d81c5a539..0b72e22d25 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -124,7 +124,6 @@ $(SolutionDir)\packages\DotNetZip.1.10.1\lib\net20\DotNetZip.dll True - $(SolutionDir)\packages\Mono.Cecil.0.9.6.4\lib\net45\Mono.Cecil.dll True @@ -608,6 +607,14 @@ + + + + + + + + @@ -737,6 +744,7 @@ +