From 1b5a9aecffdef0b307c3712d0d85992928b0d894 Mon Sep 17 00:00:00 2001 From: DTSDAO Date: Fri, 2 Aug 2019 23:34:25 +0800 Subject: [PATCH 001/177] Add iOS URL Scheme support --- .../Graphics/Containers/LinkFlowContainer.cs | 47 +--------------- osu.Game/Online/Chat/MessageFormatter.cs | 54 ++++++++++++++++++- osu.Game/OsuGame.cs | 22 ++++++++ osu.iOS/AppDelegate.cs | 7 ++- osu.iOS/Info.plist | 14 +++++ 5 files changed, 93 insertions(+), 51 deletions(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 15068d81c0..51a842fc84 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -86,52 +86,7 @@ namespace osu.Game.Graphics.Containers { RelativeSizeAxes = Axes.Both, TooltipText = tooltipText ?? (url != text ? url : string.Empty), - Action = action ?? (() => - { - switch (linkType) - { - case LinkAction.OpenBeatmap: - // TODO: proper query params handling - if (linkArgument != null && int.TryParse(linkArgument.Contains('?') ? linkArgument.Split('?')[0] : linkArgument, out int beatmapId)) - game?.ShowBeatmap(beatmapId); - break; - - case LinkAction.OpenBeatmapSet: - if (int.TryParse(linkArgument, out int setId)) - game?.ShowBeatmapSet(setId); - break; - - case LinkAction.OpenChannel: - try - { - channelManager?.OpenChannel(linkArgument); - } - catch (ChannelNotFoundException) - { - Logger.Log($"The requested channel \"{linkArgument}\" does not exist"); - } - - break; - - case LinkAction.OpenEditorTimestamp: - case LinkAction.JoinMultiplayerMatch: - case LinkAction.Spectate: - showNotImplementedError?.Invoke(); - break; - - case LinkAction.External: - game?.OpenUrlExternally(url); - break; - - case LinkAction.OpenUserProfile: - if (long.TryParse(linkArgument, out long userId)) - game?.ShowUser(userId); - break; - - default: - throw new NotImplementedException($"This {nameof(LinkAction)} ({linkType.ToString()}) is missing an associated action."); - } - }), + Action = action ?? (() => LinkUtils.HandleLink(url, linkType, linkArgument, game, channelManager, showNotImplementedError)), }); return drawables; diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 4aaffdd161..ee8f45803b 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -3,7 +3,9 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Text.RegularExpressions; +using osu.Framework.Logging; namespace osu.Game.Online.Chat { @@ -98,7 +100,7 @@ namespace osu.Game.Online.Chat } } - private static LinkDetails getLinkDetails(string url) + public static LinkDetails getLinkDetails(string url) { var args = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); args[0] = args[0].TrimEnd(':'); @@ -288,4 +290,54 @@ namespace osu.Game.Online.Chat public int CompareTo(Link otherLink) => Index > otherLink.Index ? 1 : -1; } + + public static class LinkUtils + { + public static void HandleLink(string url, LinkAction linkType, string linkArgument, OsuGame game, ChannelManager channelManager = null, Action showNotImplementedError = null) + { + switch (linkType) + { + case LinkAction.OpenBeatmap: + // TODO: proper query params handling + if (linkArgument != null && int.TryParse(linkArgument.Contains('?') ? linkArgument.Split('?')[0] : linkArgument, out int beatmapId)) + game?.ShowBeatmap(beatmapId); + break; + + case LinkAction.OpenBeatmapSet: + if (int.TryParse(linkArgument, out int setId)) + game?.ShowBeatmapSet(setId); + break; + + case LinkAction.OpenChannel: + try + { + channelManager?.OpenChannel(linkArgument); + } + catch (ChannelNotFoundException) + { + Logger.Log($"The requested channel \"{linkArgument}\" does not exist"); + } + + break; + + case LinkAction.OpenEditorTimestamp: + case LinkAction.JoinMultiplayerMatch: + case LinkAction.Spectate: + showNotImplementedError?.Invoke(); + break; + + case LinkAction.External: + game?.OpenUrlExternally(url); + break; + + case LinkAction.OpenUserProfile: + if (long.TryParse(linkArgument, out long userId)) + game?.ShowUser(userId); + break; + + default: + throw new NotImplementedException($"This {nameof(LinkAction)} ({linkType.ToString()}) is missing an associated action."); + } + } + } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index e71dd67bf2..5dea67253d 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -43,6 +43,7 @@ using osu.Game.Scoring; using osu.Game.Screens.Select; using osu.Game.Utils; using LogLevel = osu.Framework.Logging.LogLevel; +using static osu.Game.Online.Chat.MessageFormatter; namespace osu.Game { @@ -90,6 +91,8 @@ namespace osu.Game private IntroScreen introScreen; + private bool loaded = false; + private Bindable configRuleset; private Bindable configSkin; @@ -190,10 +193,29 @@ namespace osu.Game Audio.AddAdjustment(AdjustableProperty.Volume, inactiveVolumeFade); Beatmap.BindValueChanged(beatmapChanged, true); + + loaded = true; } private ExternalLinkOpener externalLinkOpener; + public void HandleUrl(string url) + { + Logger.Log($"Request to handle url: {url}"); + if (loaded) + { + Action showNotImplementedError = () => notifications?.Post(new SimpleNotification + { + Text = @"This link type is not yet supported!", + Icon = FontAwesome.Solid.LifeRing, + }); + LinkDetails linkDetails = getLinkDetails(url); + Schedule(() => LinkUtils.HandleLink(url, linkDetails.Action, linkDetails.Argument, this, channelManager, showNotImplementedError)); + } + else + Scheduler.AddDelayed(() => HandleUrl(url), 1000); + } + public void OpenUrlExternally(string url) { if (url.StartsWith("/")) diff --git a/osu.iOS/AppDelegate.cs b/osu.iOS/AppDelegate.cs index 9ef21e014c..e727305997 100644 --- a/osu.iOS/AppDelegate.cs +++ b/osu.iOS/AppDelegate.cs @@ -1,10 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Threading.Tasks; using Foundation; using osu.Framework.iOS; -using osu.Game; +using osu.Framework.Threading; using UIKit; namespace osu.iOS @@ -16,9 +15,9 @@ namespace osu.iOS protected override Framework.Game CreateGame() => game = new OsuGameIOS(); - public override bool OpenUrl(UIApplication application, NSUrl url, string sourceApplication, NSObject annotation) + public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options) { - Task.Run(() => game.Import(url.Path)); + game.HandleUrl(url.AbsoluteString); return true; } } diff --git a/osu.iOS/Info.plist b/osu.iOS/Info.plist index 4fbc67e27b..5fe8cd6d5b 100644 --- a/osu.iOS/Info.plist +++ b/osu.iOS/Info.plist @@ -107,5 +107,19 @@ + UIFileSharingEnabled + + CFBundleURLTypes + + + CFBundleURLSchemes + + osu + osump + + CFBundleTypeRole + Editor + + From 471103fd101180e78f2d54fc34d96cb5198dcada Mon Sep 17 00:00:00 2001 From: DTSDAO Date: Sat, 3 Aug 2019 00:00:22 +0800 Subject: [PATCH 002/177] Fix code format --- osu.Game/Graphics/Containers/LinkFlowContainer.cs | 1 - osu.Game/Online/Chat/MessageFormatter.cs | 2 +- osu.Game/OsuGame.cs | 8 +++++--- 3 files changed, 6 insertions(+), 5 deletions(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 51a842fc84..ab4629cfff 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -8,7 +8,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using System.Collections.Generic; using osu.Framework.Graphics; -using osu.Framework.Logging; using osu.Game.Overlays; using osu.Game.Overlays.Notifications; using osu.Game.Users; diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index ee8f45803b..347139975e 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -100,7 +100,7 @@ namespace osu.Game.Online.Chat } } - public static LinkDetails getLinkDetails(string url) + public static LinkDetails GetLinkDetails(string url) { var args = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); args[0] = args[0].TrimEnd(':'); diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 5dea67253d..fa68142fea 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -91,7 +91,7 @@ namespace osu.Game private IntroScreen introScreen; - private bool loaded = false; + private bool loaded; private Bindable configRuleset; @@ -193,8 +193,6 @@ namespace osu.Game Audio.AddAdjustment(AdjustableProperty.Volume, inactiveVolumeFade); Beatmap.BindValueChanged(beatmapChanged, true); - - loaded = true; } private ExternalLinkOpener externalLinkOpener; @@ -202,6 +200,7 @@ namespace osu.Game public void HandleUrl(string url) { Logger.Log($"Request to handle url: {url}"); + if (loaded) { Action showNotImplementedError = () => notifications?.Post(new SimpleNotification @@ -402,6 +401,8 @@ namespace osu.Game protected override void LoadComplete() { + loaded = false; + base.LoadComplete(); // The next time this is updated is in UpdateAfterChildren, which occurs too late and results @@ -597,6 +598,7 @@ namespace osu.Game settings.State.ValueChanged += _ => updateScreenOffset(); notifications.State.ValueChanged += _ => updateScreenOffset(); + loaded = true; } public class GameIdleTracker : IdleTracker From c9e14c8f063afae562789111864fc094601a9202 Mon Sep 17 00:00:00 2001 From: DTSDAO Date: Sat, 3 Aug 2019 19:46:57 +0800 Subject: [PATCH 003/177] Fix typo --- osu.Game/Online/Chat/MessageFormatter.cs | 4 ++-- osu.Game/OsuGame.cs | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 347139975e..73396ce4d2 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -78,7 +78,7 @@ namespace osu.Game.Online.Chat //since we just changed the line display text, offset any already processed links. result.Links.ForEach(l => l.Index -= l.Index > index ? m.Length - displayText.Length : 0); - var details = getLinkDetails(linkText); + var details = GetLinkDetails(linkText); result.Links.Add(new Link(linkText, index, displayText.Length, linkActionOverride ?? details.Action, details.Argument)); //adjust the offset for processing the current matches group. @@ -95,7 +95,7 @@ namespace osu.Game.Online.Chat var link = m.Groups["link"].Value; var indexLength = link.Length; - var details = getLinkDetails(link); + var details = GetLinkDetails(link); result.Links.Add(new Link(link, index, indexLength, details.Action, details.Argument)); } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index fa68142fea..c478ad0a57 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -208,7 +208,7 @@ namespace osu.Game Text = @"This link type is not yet supported!", Icon = FontAwesome.Solid.LifeRing, }); - LinkDetails linkDetails = getLinkDetails(url); + LinkDetails linkDetails = GetLinkDetails(url); Schedule(() => LinkUtils.HandleLink(url, linkDetails.Action, linkDetails.Argument, this, channelManager, showNotImplementedError)); } else From 0fe6585975a7efdf6b00a73f7be41593c7ea6e20 Mon Sep 17 00:00:00 2001 From: DTSDAO Date: Mon, 5 Aug 2019 22:30:35 +0800 Subject: [PATCH 004/177] Fix iOS importing --- osu.Game/Database/ArchiveModelManager.cs | 2 +- osu.Game/OsuGame.cs | 24 ++++++++---------------- osu.iOS/AppDelegate.cs | 6 +++++- osu.iOS/Info.plist | 2 ++ osu.iOS/OsuGameIOS.cs | 2 ++ 5 files changed, 18 insertions(+), 18 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index efb76deff8..37ea7b2ca9 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -54,7 +54,7 @@ namespace osu.Game.Database public virtual string[] HandledExtensions => new[] { ".zip" }; - public virtual bool SupportsImportFromStable => RuntimeInfo.IsDesktop; + public virtual bool SupportsImportFromStable => (RuntimeInfo.IsDesktop || RuntimeInfo.OS == RuntimeInfo.Platform.iOS); protected readonly FileStore Files; diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b64f04de2a..846677a0a9 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -91,8 +91,6 @@ namespace osu.Game private IntroScreen introScreen; - private bool loaded; - private Bindable configRuleset; private Bindable configSkin; @@ -201,18 +199,15 @@ namespace osu.Game { Logger.Log($"Request to handle url: {url}"); - if (loaded) + Action showNotImplementedError = () => notifications?.Post(new SimpleNotification { - Action showNotImplementedError = () => notifications?.Post(new SimpleNotification - { - Text = @"This link type is not yet supported!", - Icon = FontAwesome.Solid.LifeRing, - }); - LinkDetails linkDetails = GetLinkDetails(url); - Schedule(() => LinkUtils.HandleLink(url, linkDetails.Action, linkDetails.Argument, this, channelManager, showNotImplementedError)); - } - else - Scheduler.AddDelayed(() => HandleUrl(url), 1000); + Text = @"This link type is not yet supported!", + Icon = FontAwesome.Solid.LifeRing, + }); + + LinkDetails linkDetails = GetLinkDetails(url); + + Schedule(() => LinkUtils.HandleLink(url, linkDetails.Action, linkDetails.Argument, this, channelManager, showNotImplementedError)); } public void OpenUrlExternally(string url) @@ -403,8 +398,6 @@ namespace osu.Game protected override void LoadComplete() { - loaded = false; - base.LoadComplete(); // The next time this is updated is in UpdateAfterChildren, which occurs too late and results @@ -600,7 +593,6 @@ namespace osu.Game settings.State.ValueChanged += _ => updateScreenOffset(); notifications.State.ValueChanged += _ => updateScreenOffset(); - loaded = true; } public class GameIdleTracker : IdleTracker diff --git a/osu.iOS/AppDelegate.cs b/osu.iOS/AppDelegate.cs index e727305997..07e0245195 100644 --- a/osu.iOS/AppDelegate.cs +++ b/osu.iOS/AppDelegate.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Threading.Tasks; using Foundation; using osu.Framework.iOS; using osu.Framework.Threading; @@ -17,7 +18,10 @@ namespace osu.iOS public override bool OpenUrl(UIApplication app, NSUrl url, NSDictionary options) { - game.HandleUrl(url.AbsoluteString); + if (url.IsFileUrl) + Task.Run(() => game.Import(url.Path)); + else + Task.Run(() => game.HandleUrl(url.AbsoluteString)); return true; } } diff --git a/osu.iOS/Info.plist b/osu.iOS/Info.plist index f3067bdcec..5fe8cd6d5b 100644 --- a/osu.iOS/Info.plist +++ b/osu.iOS/Info.plist @@ -14,6 +14,8 @@ 0.1.0 LSRequiresIPhoneOS + LSSupportsOpeningDocumentsInPlace + MinimumOSVersion 10.0 UIDeviceFamily diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index 6cf18df9a6..ac66357fc9 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -3,12 +3,14 @@ using System; using Foundation; +using osu.Framework.Platform; using osu.Game; namespace osu.iOS { public class OsuGameIOS : OsuGame { + public override Storage GetStorageForStableInstall() => Storage; public override Version AssemblyVersion => new Version(NSBundle.MainBundle.InfoDictionary["CFBundleVersion"].ToString()); } } From 411916d4a31b39725b58cbc549fd0a9001e691f5 Mon Sep 17 00:00:00 2001 From: DTSDAO Date: Wed, 7 Aug 2019 19:32:48 +0800 Subject: [PATCH 005/177] Move static method to Game class --- .../Graphics/Containers/LinkFlowContainer.cs | 2 +- osu.Game/Online/Chat/MessageFormatter.cs | 52 ------------------ osu.Game/OsuGame.cs | 55 +++++++++++++++++-- 3 files changed, 52 insertions(+), 57 deletions(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index ab4629cfff..ca9ffd06c6 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -85,7 +85,7 @@ namespace osu.Game.Graphics.Containers { RelativeSizeAxes = Axes.Both, TooltipText = tooltipText ?? (url != text ? url : string.Empty), - Action = action ?? (() => LinkUtils.HandleLink(url, linkType, linkArgument, game, channelManager, showNotImplementedError)), + Action = action ?? (() => game.HandleLink(url, linkType, linkArgument)), }); return drawables; diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 73396ce4d2..c875150091 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -3,9 +3,7 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Text.RegularExpressions; -using osu.Framework.Logging; namespace osu.Game.Online.Chat { @@ -290,54 +288,4 @@ namespace osu.Game.Online.Chat public int CompareTo(Link otherLink) => Index > otherLink.Index ? 1 : -1; } - - public static class LinkUtils - { - public static void HandleLink(string url, LinkAction linkType, string linkArgument, OsuGame game, ChannelManager channelManager = null, Action showNotImplementedError = null) - { - switch (linkType) - { - case LinkAction.OpenBeatmap: - // TODO: proper query params handling - if (linkArgument != null && int.TryParse(linkArgument.Contains('?') ? linkArgument.Split('?')[0] : linkArgument, out int beatmapId)) - game?.ShowBeatmap(beatmapId); - break; - - case LinkAction.OpenBeatmapSet: - if (int.TryParse(linkArgument, out int setId)) - game?.ShowBeatmapSet(setId); - break; - - case LinkAction.OpenChannel: - try - { - channelManager?.OpenChannel(linkArgument); - } - catch (ChannelNotFoundException) - { - Logger.Log($"The requested channel \"{linkArgument}\" does not exist"); - } - - break; - - case LinkAction.OpenEditorTimestamp: - case LinkAction.JoinMultiplayerMatch: - case LinkAction.Spectate: - showNotImplementedError?.Invoke(); - break; - - case LinkAction.External: - game?.OpenUrlExternally(url); - break; - - case LinkAction.OpenUserProfile: - if (long.TryParse(linkArgument, out long userId)) - game?.ShowUser(userId); - break; - - default: - throw new NotImplementedException($"This {nameof(LinkAction)} ({linkType.ToString()}) is missing an associated action."); - } - } - } } diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 846677a0a9..43f3f1f5b5 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -195,19 +195,66 @@ namespace osu.Game private ExternalLinkOpener externalLinkOpener; - public void HandleUrl(string url) + public void HandleLink(string url, LinkAction linkType, string linkArgument) { - Logger.Log($"Request to handle url: {url}"); - Action showNotImplementedError = () => notifications?.Post(new SimpleNotification { Text = @"This link type is not yet supported!", Icon = FontAwesome.Solid.LifeRing, }); + switch (linkType) + { + case LinkAction.OpenBeatmap: + // TODO: proper query params handling + if (linkArgument != null && int.TryParse(linkArgument.Contains('?') ? linkArgument.Split('?')[0] : linkArgument, out int beatmapId)) + ShowBeatmap(beatmapId); + break; + + case LinkAction.OpenBeatmapSet: + if (int.TryParse(linkArgument, out int setId)) + ShowBeatmapSet(setId); + break; + + case LinkAction.OpenChannel: + try + { + channelManager.OpenChannel(linkArgument); + } + catch (ChannelNotFoundException) + { + Logger.Log($"The requested channel \"{linkArgument}\" does not exist"); + } + + break; + + case LinkAction.OpenEditorTimestamp: + case LinkAction.JoinMultiplayerMatch: + case LinkAction.Spectate: + showNotImplementedError?.Invoke(); + break; + + case LinkAction.External: + OpenUrlExternally(url); + break; + + case LinkAction.OpenUserProfile: + if (long.TryParse(linkArgument, out long userId)) + ShowUser(userId); + break; + + default: + throw new NotImplementedException($"This {nameof(LinkAction)} ({linkType.ToString()}) is missing an associated action."); + } + } + + public void HandleUrl(string url) + { + Logger.Log($"Request to handle url: {url}"); + LinkDetails linkDetails = GetLinkDetails(url); - Schedule(() => LinkUtils.HandleLink(url, linkDetails.Action, linkDetails.Argument, this, channelManager, showNotImplementedError)); + Schedule(() => HandleLink(url, linkDetails.Action, linkDetails.Argument)); } public void OpenUrlExternally(string url) From 37e430177384e047ce2ce932b280a22583c7d223 Mon Sep 17 00:00:00 2001 From: DTSDAO Date: Wed, 7 Aug 2019 20:16:22 +0800 Subject: [PATCH 006/177] Fix code format --- osu.Game/Graphics/Containers/LinkFlowContainer.cs | 11 +---------- osu.Game/OsuGame.cs | 2 +- 2 files changed, 2 insertions(+), 11 deletions(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index ca9ffd06c6..b03bc4e3aa 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -22,21 +22,12 @@ namespace osu.Game.Graphics.Containers } private OsuGame game; - private ChannelManager channelManager; - private Action showNotImplementedError; [BackgroundDependencyLoader(true)] - private void load(OsuGame game, NotificationOverlay notifications, ChannelManager channelManager) + private void load(OsuGame game) { // will be null in tests this.game = game; - this.channelManager = channelManager; - - showNotImplementedError = () => notifications?.Post(new SimpleNotification - { - Text = @"This link type is not yet supported!", - Icon = FontAwesome.Solid.LifeRing, - }); } public void AddLinks(string text, List links) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 43f3f1f5b5..82bf8abf94 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -231,7 +231,7 @@ namespace osu.Game case LinkAction.OpenEditorTimestamp: case LinkAction.JoinMultiplayerMatch: case LinkAction.Spectate: - showNotImplementedError?.Invoke(); + showNotImplementedError.Invoke(); break; case LinkAction.External: From 9f72e92e0e35dc4c71ba7d6d184334fbd5b732ca Mon Sep 17 00:00:00 2001 From: DTSDAO Date: Wed, 7 Aug 2019 20:27:39 +0800 Subject: [PATCH 007/177] Fix code format --- osu.Game/Graphics/Containers/LinkFlowContainer.cs | 2 -- 1 file changed, 2 deletions(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index b03bc4e3aa..66911d9615 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -8,8 +8,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using System.Collections.Generic; using osu.Framework.Graphics; -using osu.Game.Overlays; -using osu.Game.Overlays.Notifications; using osu.Game.Users; namespace osu.Game.Graphics.Containers From 7b414092afd261c953fd9f51f9bdf93632021523 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 4 Oct 2019 17:35:41 +0300 Subject: [PATCH 008/177] Implement beatmap ruleset selector --- .../BeatmapSet/BeatmapRulesetSelector.cs | 50 ++++++ .../BeatmapSet/BeatmapRulesetTabItem.cs | 148 ++++++++++++++++++ 2 files changed, 198 insertions(+) create mode 100644 osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs create mode 100644 osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs new file mode 100644 index 0000000000..0847272e1f --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs @@ -0,0 +1,50 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Beatmaps; +using osu.Game.Rulesets; +using osuTK; +using System.Linq; + +namespace osu.Game.Overlays.BeatmapSet +{ + public class BeatmapRulesetSelector : RulesetSelector + { + private BeatmapSetInfo beatmapSet; + + public BeatmapSetInfo BeatmapSet + { + get => beatmapSet; + set + { + if (value == beatmapSet) + return; + + beatmapSet = value; + + foreach (BeatmapRulesetTabItem tab in TabContainer.TabItems) + tab.SetBeatmaps(beatmapSet?.Beatmaps.FindAll(b => b.Ruleset.Equals(tab.Value))); + + var firstRuleset = beatmapSet?.Beatmaps.OrderBy(b => b.Ruleset.ID).FirstOrDefault()?.Ruleset; + SelectTab(TabContainer.TabItems.FirstOrDefault(t => t.Value.Equals(firstRuleset))); + } + } + + public BeatmapRulesetSelector() + { + AutoSizeAxes = Axes.Both; + TabContainer.Spacing = new Vector2(10, 0); + } + + protected override TabItem CreateTabItem(RulesetInfo value) => new BeatmapRulesetTabItem(value); + + protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer + { + Direction = FillDirection.Horizontal, + AutoSizeAxes = Axes.Both, + }; + } +} diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs new file mode 100644 index 0000000000..19c9af13d5 --- /dev/null +++ b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs @@ -0,0 +1,148 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.UserInterface; +using osu.Framework.Input.Events; +using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets; +using osuTK; +using osuTK.Graphics; +using System.Collections.Generic; +using System.Diagnostics; + +namespace osu.Game.Overlays.BeatmapSet +{ + public class BeatmapRulesetTabItem : TabItem + { + private readonly OsuSpriteText name, count; + private readonly Box bar; + + public override bool PropagatePositionalInputSubTree => Enabled.Value && !Active.Value && base.PropagatePositionalInputSubTree; + + public BeatmapRulesetTabItem(RulesetInfo value) + : base(value) + { + AutoSizeAxes = Axes.Both; + Masking = true; + + FillFlowContainer nameContainer; + + Children = new Drawable[] + { + nameContainer = new FillFlowContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Margin = new MarginPadding { Bottom = 7.5f }, + Spacing = new Vector2(2.5f), + Children = new Drawable[] + { + name = new OsuSpriteText + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Text = value.Name, + Font = OsuFont.Default.With(size: 18), + }, + new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + AutoSizeAxes = Axes.Both, + Masking = true, + CornerRadius = 4f, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black.Opacity(0.5f), + }, + count = new OsuSpriteText + { + Alpha = 0, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Margin = new MarginPadding { Horizontal = 5f }, + Font = OsuFont.Default.With(weight: FontWeight.SemiBold), + } + } + } + } + }, + bar = new Box + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.X, + Height = 4f, + }, + new HoverClickSounds(), + }; + + Enabled.BindValueChanged(v => nameContainer.Alpha = v.NewValue ? 1f : 0.5f, true); + } + + [Resolved] + private OsuColour colour { get; set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + count.Colour = colour.Gray9; + bar.Colour = colour.Blue; + + updateState(); + } + + public void SetBeatmaps(List beatmaps) + { + Trace.Assert(beatmaps?.TrueForAll(b => b.Ruleset.Equals(Value)) ?? true, "A beatmap has a ruleset not of this tab value"); + + count.Text = beatmaps?.Count.ToString(); + + var hasBeatmaps = (beatmaps?.Count ?? 0) > 0; + count.Alpha = hasBeatmaps ? 1f : 0f; + Enabled.Value = hasBeatmaps; + } + + private void updateState() + { + var isHoveredOrActive = IsHovered || Active.Value; + name.Colour = isHoveredOrActive ? colour.GrayE : colour.GrayC; + bar.MoveToY(isHoveredOrActive ? 0f : bar.Height, 120); + + name.Font = name.Font.With(weight: Active.Value ? FontWeight.Bold : FontWeight.Regular); + } + + #region Hovering and activation logic + + protected override void OnActivated() => updateState(); + + protected override void OnDeactivated() => updateState(); + + protected override bool OnHover(HoverEvent e) + { + updateState(); + return false; + } + + protected override void OnHoverLost(HoverLostEvent e) + { + updateState(); + } + + #endregion + } +} From edddbdb7845a250002ee5dd111750b3290a29401 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 4 Oct 2019 17:37:09 +0300 Subject: [PATCH 009/177] Add tests for beatmap ruleset selector --- .../Online/TestSceneBeatmapRulesetSelector.cs | 102 ++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneBeatmapRulesetSelector.cs diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapRulesetSelector.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapRulesetSelector.cs new file mode 100644 index 0000000000..1f8df438fb --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapRulesetSelector.cs @@ -0,0 +1,102 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Beatmaps; +using osu.Game.Overlays.BeatmapSet; +using osu.Game.Rulesets; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneBeatmapRulesetSelector : OsuTestScene + { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(BeatmapRulesetSelector), + typeof(BeatmapRulesetTabItem), + }; + + private readonly TestRulesetSelector selector; + + public TestSceneBeatmapRulesetSelector() + { + Add(selector = new TestRulesetSelector()); + } + + [Resolved] + private RulesetStore rulesets { get; set; } + + [Test] + public void TestMultipleRulesetsBeatmapSet() + { + var enabledRulesets = rulesets.AvailableRulesets.Skip(1).Take(2); + + AddStep("load multiple rulesets beatmapset", () => + { + selector.BeatmapSet = new BeatmapSetInfo + { + Beatmaps = enabledRulesets.Select(r => new BeatmapInfo { Ruleset = r }).ToList() + }; + }); + + var tabItems = selector.TabContainer.TabItems; + AddAssert("other rulesets disabled", () => tabItems.Except(tabItems.Where(t => enabledRulesets.Any(r => r.Equals(t.Value)))).All(t => !t.Enabled.Value)); + AddAssert("left-most ruleset selected", () => tabItems.First(t => t.Enabled.Value).Active.Value); + } + + [Test] + public void TestSingleRulesetBeatmapSet() + { + var enabledRuleset = rulesets.AvailableRulesets.Last(); + + AddStep("load single ruleset beatmapset", () => + { + selector.BeatmapSet = new BeatmapSetInfo + { + Beatmaps = new List + { + new BeatmapInfo + { + Ruleset = enabledRuleset + } + } + }; + }); + + AddAssert("single ruleset selected", () => selector.SelectedTab.Value.Equals(enabledRuleset)); + } + + [Test] + public void TestEmptyBeatmapSet() + { + AddStep("load empty beatmapset", () => selector.BeatmapSet = new BeatmapSetInfo + { + Beatmaps = new List() + }); + + AddAssert("no ruleset selected", () => selector.SelectedTab == null); + AddAssert("all rulesets disabled", () => selector.TabContainer.TabItems.All(t => !t.Enabled.Value)); + } + + [Test] + public void TestNullBeatmapSet() + { + AddStep("load null beatmapset", () => selector.BeatmapSet = null); + + AddAssert("no ruleset selected", () => selector.SelectedTab == null); + AddAssert("all rulesets disabled", () => selector.TabContainer.TabItems.All(t => !t.Enabled.Value)); + } + + private class TestRulesetSelector : BeatmapRulesetSelector + { + public new TabItem SelectedTab => base.SelectedTab; + + public new TabFillFlowContainer TabContainer => base.TabContainer; + } + } +} From 7d5f5d2fd9272e207bc26860e76f613ce01e38ec Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 4 Oct 2019 17:55:33 +0300 Subject: [PATCH 010/177] Add ruleset selector to the beatmap overlay header --- osu.Game/Overlays/BeatmapSet/Header.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index 260a989628..0e3d29c25b 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -3,6 +3,7 @@ using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Colour; @@ -16,6 +17,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Overlays.BeatmapSet.Buttons; using osu.Game.Overlays.Direct; +using osu.Game.Rulesets; using osuTK; using osuTK.Graphics; @@ -39,6 +41,7 @@ namespace osu.Game.Overlays.BeatmapSet public bool DownloadButtonsVisible => downloadButtonsContainer.Any(); + public readonly BeatmapRulesetSelector RulesetSelector; public readonly BeatmapPicker Picker; private readonly FavouriteButton favouriteButton; @@ -69,12 +72,17 @@ namespace osu.Game.Overlays.BeatmapSet { RelativeSizeAxes = Axes.X, Height = tabs_height, - Children = new[] + Children = new Drawable[] { tabsBg = new Box { RelativeSizeAxes = Axes.Both, }, + RulesetSelector = new BeatmapRulesetSelector + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + } }, }, new Container @@ -214,6 +222,13 @@ namespace osu.Game.Overlays.BeatmapSet }; } + protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) + { + var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); + dependencies.CacheAs>(RulesetSelector.Current); + return dependencies; + } + [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -223,7 +238,7 @@ namespace osu.Game.Overlays.BeatmapSet BeatmapSet.BindValueChanged(setInfo => { - Picker.BeatmapSet = author.BeatmapSet = beatmapAvailability.BeatmapSet = Details.BeatmapSet = setInfo.NewValue; + Picker.BeatmapSet = RulesetSelector.BeatmapSet = author.BeatmapSet = beatmapAvailability.BeatmapSet = Details.BeatmapSet = setInfo.NewValue; cover.BeatmapSet = setInfo.NewValue; if (setInfo.NewValue == null) From 555c82e9c9077f8e363aebb12e73ff07127aa97f Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 4 Oct 2019 17:56:42 +0300 Subject: [PATCH 011/177] Filter beatmap difficulties by current ruleset --- osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 25 ++++++++++++------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index 28947b6f22..e7a3d15401 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -17,6 +17,7 @@ using osu.Game.Beatmaps.Drawables; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osu.Game.Rulesets; using osuTK; using osuTK.Graphics; @@ -27,10 +28,11 @@ namespace osu.Game.Overlays.BeatmapSet private const float tile_icon_padding = 7; private const float tile_spacing = 2; - private readonly DifficultiesContainer difficulties; private readonly OsuSpriteText version, starRating; private readonly Statistic plays, favourites; + public readonly DifficultiesContainer Difficulties; + public readonly Bindable Beatmap = new Bindable(); private BeatmapSetInfo beatmapSet; @@ -50,11 +52,11 @@ namespace osu.Game.Overlays.BeatmapSet private void updateDisplay() { - difficulties.Clear(); + Difficulties.Clear(); if (BeatmapSet != null) { - difficulties.ChildrenEnumerable = BeatmapSet.Beatmaps.OrderBy(beatmap => beatmap.StarDifficulty).Select(b => new DifficultySelectorButton(b) + Difficulties.ChildrenEnumerable = BeatmapSet.Beatmaps.FindAll(b => b.Ruleset.Equals(ruleset.Value)).OrderBy(b => b.StarDifficulty).Select(b => new DifficultySelectorButton(b) { State = DifficultySelectorState.NotSelected, OnHovered = beatmap => @@ -68,7 +70,7 @@ namespace osu.Game.Overlays.BeatmapSet } starRating.FadeOut(100); - Beatmap.Value = BeatmapSet?.Beatmaps.FirstOrDefault(); + Beatmap.Value = Difficulties.FirstOrDefault()?.Beatmap; plays.Value = BeatmapSet?.OnlineInfo.PlayCount ?? 0; favourites.Value = BeatmapSet?.OnlineInfo.FavouriteCount ?? 0; @@ -89,7 +91,7 @@ namespace osu.Game.Overlays.BeatmapSet Direction = FillDirection.Vertical, Children = new Drawable[] { - difficulties = new DifficultiesContainer + Difficulties = new DifficultiesContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -147,6 +149,9 @@ namespace osu.Game.Overlays.BeatmapSet }; } + [Resolved] + private IBindable ruleset { get; set; } + [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -158,6 +163,8 @@ namespace osu.Game.Overlays.BeatmapSet { base.LoadComplete(); + ruleset.ValueChanged += r => updateDisplay(); + // done here so everything can bind in intialization and get the first trigger Beatmap.TriggerChange(); } @@ -169,10 +176,10 @@ namespace osu.Game.Overlays.BeatmapSet private void updateDifficultyButtons() { - difficulties.Children.ToList().ForEach(diff => diff.State = diff.Beatmap == Beatmap.Value ? DifficultySelectorState.Selected : DifficultySelectorState.NotSelected); + Difficulties.Children.ToList().ForEach(diff => diff.State = diff.Beatmap == Beatmap.Value ? DifficultySelectorState.Selected : DifficultySelectorState.NotSelected); } - private class DifficultiesContainer : FillFlowContainer + public class DifficultiesContainer : FillFlowContainer { public Action OnLostHover; @@ -183,7 +190,7 @@ namespace osu.Game.Overlays.BeatmapSet } } - private class DifficultySelectorButton : OsuClickableContainer, IStateful + public class DifficultySelectorButton : OsuClickableContainer, IStateful { private const float transition_duration = 100; private const float size = 52; @@ -320,7 +327,7 @@ namespace osu.Game.Overlays.BeatmapSet } } - private enum DifficultySelectorState + public enum DifficultySelectorState { Selected, NotSelected, From 4f40a044258b392f8c0d42a672d5d492ab11779e Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 4 Oct 2019 17:57:39 +0300 Subject: [PATCH 012/177] Add tests ensuring correct behaviour with ruleset selection --- .../Online/TestSceneBeatmapSetOverlay.cs | 70 +++++++++++++++---- 1 file changed, 57 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index 9f03d947b9..6c7a3e4108 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -3,6 +3,7 @@ using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet; @@ -40,24 +41,19 @@ namespace osu.Game.Tests.Visual.Online typeof(PreviewButton), typeof(SuccessRate), typeof(BeatmapAvailability), + typeof(BeatmapRulesetSelector), + typeof(BeatmapRulesetTabItem), }; protected override bool UseOnlineAPI => true; - private RulesetInfo taikoRuleset; - private RulesetInfo maniaRuleset; - public TestSceneBeatmapSetOverlay() { Add(overlay = new TestBeatmapSetOverlay()); } - [BackgroundDependencyLoader] - private void load(RulesetStore rulesets) - { - taikoRuleset = rulesets.GetRuleset(1); - maniaRuleset = rulesets.GetRuleset(3); - } + [Resolved] + private RulesetStore rulesets { get; set; } [Test] public void TestLoading() @@ -111,7 +107,7 @@ namespace osu.Game.Tests.Visual.Online StarDifficulty = 9.99, Version = @"TEST", Length = 456000, - Ruleset = maniaRuleset, + Ruleset = rulesets.GetRuleset(3), BaseDifficulty = new BeatmapDifficulty { CircleSize = 1, @@ -189,7 +185,7 @@ namespace osu.Game.Tests.Visual.Online StarDifficulty = 5.67, Version = @"ANOTHER TEST", Length = 123000, - Ruleset = taikoRuleset, + Ruleset = rulesets.GetRuleset(1), BaseDifficulty = new BeatmapDifficulty { CircleSize = 9, @@ -217,6 +213,54 @@ namespace osu.Game.Tests.Visual.Online downloadAssert(false); } + [Test] + public void TestMultipleRulesets() + { + AddStep("show multiple rulesets beatmap", () => + { + var beatmaps = new List(); + + foreach (var ruleset in rulesets.AvailableRulesets.Skip(1)) + { + beatmaps.Add(new BeatmapInfo + { + Version = ruleset.Name, + Ruleset = ruleset, + BaseDifficulty = new BeatmapDifficulty(), + OnlineInfo = new BeatmapOnlineInfo(), + Metrics = new BeatmapMetrics + { + Fails = Enumerable.Range(1, 100).Select(i => i % 12 - 6).ToArray(), + Retries = Enumerable.Range(-2, 100).Select(i => i % 12 - 6).ToArray(), + }, + }); + } + + overlay.ShowBeatmapSet(new BeatmapSetInfo + { + Metadata = new BeatmapMetadata + { + Title = @"multiple rulesets beatmap", + Artist = @"none", + Author = new User + { + Username = "BanchoBot", + Id = 3, + } + }, + OnlineInfo = new BeatmapSetOnlineInfo + { + Covers = new BeatmapSetOnlineCovers(), + }, + Metrics = new BeatmapSetMetrics { Ratings = Enumerable.Range(0, 11).ToArray() }, + Beatmaps = beatmaps + }); + }); + + AddAssert("shown beatmaps of current ruleset", () => overlay.Header.Picker.Difficulties.All(b => b.Beatmap.Ruleset.Equals(overlay.Header.RulesetSelector.Current.Value))); + AddAssert("left-most beatmap selected", () => overlay.Header.Picker.Difficulties.First().State == BeatmapPicker.DifficultySelectorState.Selected); + } + [Test] public void TestHide() { @@ -281,12 +325,12 @@ namespace osu.Game.Tests.Visual.Online private void downloadAssert(bool shown) { - AddAssert($"is download button {(shown ? "shown" : "hidden")}", () => overlay.DownloadButtonsVisible == shown); + AddAssert($"is download button {(shown ? "shown" : "hidden")}", () => overlay.Header.DownloadButtonsVisible == shown); } private class TestBeatmapSetOverlay : BeatmapSetOverlay { - public bool DownloadButtonsVisible => Header.DownloadButtonsVisible; + public new Header Header => base.Header; } } } From 0c4f24825969c64775705b8feb9297af2f4fc9ac Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 4 Oct 2019 18:02:13 +0300 Subject: [PATCH 013/177] Fix CI issues --- osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs index 0847272e1f..1abe103fc8 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs @@ -25,7 +25,7 @@ namespace osu.Game.Overlays.BeatmapSet beatmapSet = value; - foreach (BeatmapRulesetTabItem tab in TabContainer.TabItems) + foreach (var tab in TabContainer.TabItems.OfType()) tab.SetBeatmaps(beatmapSet?.Beatmaps.FindAll(b => b.Ruleset.Equals(tab.Value))); var firstRuleset = beatmapSet?.Beatmaps.OrderBy(b => b.Ruleset.ID).FirstOrDefault()?.Ruleset; From 6985249d90e63e8e18ca92dd31c056eed82f35af Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Fri, 4 Oct 2019 21:18:03 +0300 Subject: [PATCH 014/177] Simplify properties --- osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs index 1abe103fc8..bfb188a83b 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs @@ -36,15 +36,15 @@ namespace osu.Game.Overlays.BeatmapSet public BeatmapRulesetSelector() { AutoSizeAxes = Axes.Both; - TabContainer.Spacing = new Vector2(10, 0); } protected override TabItem CreateTabItem(RulesetInfo value) => new BeatmapRulesetTabItem(value); protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer { - Direction = FillDirection.Horizontal, AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Spacing = new Vector2(10, 0), }; } } From ba1a8547011d721f61ef1798b6dab6ed6d0e083e Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Sat, 5 Oct 2019 10:30:32 +0300 Subject: [PATCH 015/177] Use IEnumerable.Where<>() rather than List.FindAll() Saves a whole list allocation --- osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index e7a3d15401..bffe779da1 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -56,7 +56,7 @@ namespace osu.Game.Overlays.BeatmapSet if (BeatmapSet != null) { - Difficulties.ChildrenEnumerable = BeatmapSet.Beatmaps.FindAll(b => b.Ruleset.Equals(ruleset.Value)).OrderBy(b => b.StarDifficulty).Select(b => new DifficultySelectorButton(b) + Difficulties.ChildrenEnumerable = BeatmapSet.Beatmaps.Where(b => b.Ruleset.Equals(ruleset.Value)).OrderBy(b => b.StarDifficulty).Select(b => new DifficultySelectorButton(b) { State = DifficultySelectorState.NotSelected, OnHovered = beatmap => From 937dbb7bf690c22fff07cade2d4cadd6c8c9128a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 9 Oct 2019 16:06:16 +0900 Subject: [PATCH 016/177] Initial layout for timing screen --- .../Visual/Editor/TestSceneTimingScreen.cs | 21 ++++ osu.Game/Screens/Edit/Timing/TimingScreen.cs | 97 ++++++++++++++++++- 2 files changed, 115 insertions(+), 3 deletions(-) create mode 100644 osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs new file mode 100644 index 0000000000..be84ae2cf9 --- /dev/null +++ b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs @@ -0,0 +1,21 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using NUnit.Framework; +using osu.Framework.Allocation; +using osu.Game.Rulesets.Osu; +using osu.Game.Screens.Edit.Timing; + +namespace osu.Game.Tests.Visual.Editor +{ + [TestFixture] + public class TestSceneTimingScreen : EditorClockTestScene + { + [BackgroundDependencyLoader] + private void load() + { + Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo); + Child = new TimingScreen(); + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 9ded4207e5..b723ffac9a 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -1,13 +1,104 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Allocation; +using osu.Framework.Bindables; +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.Graphics.Containers; +using osu.Game.Graphics.Sprites; + namespace osu.Game.Screens.Edit.Timing { - public class TimingScreen : EditorScreen + public class TimingScreen : EditorScreenWithTimeline { - public TimingScreen() + protected override Drawable CreateMainContent() => new GridContainer { - Child = new ScreenWhiteBox.UnderConstructionMessage("Timing mode"); + RelativeSizeAxes = Axes.Both, + ColumnDimensions = new[] + { + new Dimension(), + new Dimension(GridSizeMode.Absolute, 200), + }, + Content = new[] + { + new Drawable[] + { + new ControlPointList(), + new ControlPointSettings(), + }, + } + }; + + public class ControlPointList : CompositeDrawable + { + [Resolved] + protected IBindable Beatmap { get; private set; } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = colours.Gray0, + RelativeSizeAxes = Axes.Both, + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Children = new Drawable[] + { + new ControlPointRow(), + new ControlPointRow(), + new ControlPointRow(), + new ControlPointRow(), + new ControlPointRow(), + new ControlPointRow(), + } + }, + } + }; + } + + private class ControlPointRow : CompositeDrawable + { + public ControlPointRow() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChildren = new Drawable[] + { + new OsuSpriteText { Text = "sample row" }, + }; + } + } + } + + public class ControlPointSettings : CompositeDrawable + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.Both; + + InternalChild = new Box + { + Colour = colours.Gray3, + RelativeSizeAxes = Axes.Both, + }; + } } } } From de13320a2debb7c48d46691ea5a34a0b09d722a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 11 Oct 2019 18:46:05 +0900 Subject: [PATCH 017/177] Add initial table display --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 187 ++++++++++++++++--- 1 file changed, 160 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index b723ffac9a..be7058ae08 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -1,12 +1,18 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Globalization; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; @@ -15,6 +21,9 @@ namespace osu.Game.Screens.Edit.Timing { public class TimingScreen : EditorScreenWithTimeline { + [Cached] + private readonly Bindable controlPoint = new Bindable(); + protected override Drawable CreateMainContent() => new GridContainer { RelativeSizeAxes = Axes.Both, @@ -53,37 +62,13 @@ namespace osu.Game.Screens.Edit.Timing new OsuScrollContainer { RelativeSizeAxes = Axes.Both, - Child = new FillFlowContainer + Child = new ControlPointTable { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Children = new Drawable[] - { - new ControlPointRow(), - new ControlPointRow(), - new ControlPointRow(), - new ControlPointRow(), - new ControlPointRow(), - new ControlPointRow(), - } - }, + ControlPoints = Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints + } } }; } - - private class ControlPointRow : CompositeDrawable - { - public ControlPointRow() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - InternalChildren = new Drawable[] - { - new OsuSpriteText { Text = "sample row" }, - }; - } - } } public class ControlPointSettings : CompositeDrawable @@ -101,4 +86,152 @@ namespace osu.Game.Screens.Edit.Timing } } } + + public class ControlPointTable : TableContainer + { + private const float horizontal_inset = 20; + private const float row_height = 25; + private const int text_size = 14; + + private readonly FillFlowContainer backgroundFlow; + + [Resolved] + private Bindable controlPoint { get; set; } + + public ControlPointTable() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Padding = new MarginPadding { Horizontal = horizontal_inset }; + RowSize = new Dimension(GridSizeMode.Absolute, row_height); + + AddInternal(backgroundFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Depth = 1f, + Padding = new MarginPadding { Horizontal = -horizontal_inset }, + Margin = new MarginPadding { Top = row_height } + }); + } + + public IReadOnlyList ControlPoints + { + set + { + Content = null; + backgroundFlow.Clear(); + + if (value?.Any() != true) + return; + + for (int i = 0; i < value.Count; i++) + { + var cp = value[i]; + backgroundFlow.Add(new RowBackground { Action = () => controlPoint.Value = cp }); + } + + Columns = createHeaders(); + Content = value.Select((s, i) => createContent(i, s)).ToArray().ToRectangular(); + } + } + + private TableColumn[] createHeaders() + { + var columns = new List + { + new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("offset", Anchor.Centre), + new TableColumn("BPM", Anchor.Centre), + new TableColumn("Meter", Anchor.Centre), + new TableColumn("Sample Set", Anchor.Centre), + new TableColumn("Volume", Anchor.Centre), + }; + + return columns.ToArray(); + } + + private Drawable[] createContent(int index, ControlPoint controlPoint) + { + return new Drawable[] + { + new OsuSpriteText + { + Text = $"#{index + 1}", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) + }, + new OsuSpriteText + { + Text = $"{controlPoint.Time}", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) + }, + new OsuSpriteText + { + Text = $"{(controlPoint as TimingControlPoint)?.BeatLength.ToString(CultureInfo.InvariantCulture) ?? "-"}", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) + }, + new OsuSpriteText + { + Text = $"{(controlPoint as TimingControlPoint)?.TimeSignature.ToString() ?? "-"}", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) + }, + }; + } + + protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty); + + private class HeaderText : OsuSpriteText + { + public HeaderText(string text) + { + Text = text.ToUpper(); + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Black); + } + } + + public class RowBackground : OsuClickableContainer + { + private const int fade_duration = 100; + + private readonly Box hoveredBackground; + + public RowBackground() + { + RelativeSizeAxes = Axes.X; + Height = 25; + + AlwaysPresent = true; + + CornerRadius = 3; + Masking = true; + + Children = new Drawable[] + { + hoveredBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + hoveredBackground.Colour = colours.Blue; + } + + protected override bool OnHover(HoverEvent e) + { + hoveredBackground.FadeIn(fade_duration, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hoveredBackground.FadeOut(fade_duration, Easing.OutQuint); + base.OnHoverLost(e); + } + } + } } From 60133ba0c3fa62ef7f7cc6f8f14ea47f886b2015 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Tue, 15 Oct 2019 23:33:50 +0300 Subject: [PATCH 018/177] Propagate BeatmapSetInfo to tab items with bindable --- .../BeatmapSet/BeatmapRulesetSelector.cs | 22 +++++++--------- .../BeatmapSet/BeatmapRulesetTabItem.cs | 26 +++++++++---------- 2 files changed, 23 insertions(+), 25 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs index bfb188a83b..a0bedc848e 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetSelector.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.UserInterface; @@ -13,23 +14,17 @@ namespace osu.Game.Overlays.BeatmapSet { public class BeatmapRulesetSelector : RulesetSelector { - private BeatmapSetInfo beatmapSet; + private readonly Bindable beatmapSet = new Bindable(); public BeatmapSetInfo BeatmapSet { - get => beatmapSet; + get => beatmapSet.Value; set { - if (value == beatmapSet) - return; + // propagate value to tab items first to enable only available rulesets. + beatmapSet.Value = value; - beatmapSet = value; - - foreach (var tab in TabContainer.TabItems.OfType()) - tab.SetBeatmaps(beatmapSet?.Beatmaps.FindAll(b => b.Ruleset.Equals(tab.Value))); - - var firstRuleset = beatmapSet?.Beatmaps.OrderBy(b => b.Ruleset.ID).FirstOrDefault()?.Ruleset; - SelectTab(TabContainer.TabItems.FirstOrDefault(t => t.Value.Equals(firstRuleset))); + SelectTab(TabContainer.TabItems.FirstOrDefault(t => t.Enabled.Value)); } } @@ -38,7 +33,10 @@ namespace osu.Game.Overlays.BeatmapSet AutoSizeAxes = Axes.Both; } - protected override TabItem CreateTabItem(RulesetInfo value) => new BeatmapRulesetTabItem(value); + protected override TabItem CreateTabItem(RulesetInfo value) => new BeatmapRulesetTabItem(value) + { + BeatmapSet = { BindTarget = beatmapSet } + }; protected override TabFillFlowContainer CreateTabFlow() => new TabFillFlowContainer { diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs index 19c9af13d5..5227bec92a 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -15,8 +16,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Rulesets; using osuTK; using osuTK.Graphics; -using System.Collections.Generic; -using System.Diagnostics; +using System.Linq; namespace osu.Game.Overlays.BeatmapSet { @@ -25,6 +25,8 @@ namespace osu.Game.Overlays.BeatmapSet private readonly OsuSpriteText name, count; private readonly Box bar; + public readonly Bindable BeatmapSet = new Bindable(); + public override bool PropagatePositionalInputSubTree => Enabled.Value && !Active.Value && base.PropagatePositionalInputSubTree; public BeatmapRulesetTabItem(RulesetInfo value) @@ -90,6 +92,15 @@ namespace osu.Game.Overlays.BeatmapSet new HoverClickSounds(), }; + BeatmapSet.BindValueChanged(setInfo => + { + var beatmapsCount = setInfo.NewValue?.Beatmaps.Count(b => b.Ruleset.Equals(Value)) ?? 0; + count.Text = beatmapsCount.ToString(); + + count.Alpha = beatmapsCount > 0 ? 1f : 0f; + Enabled.Value = beatmapsCount > 0; + }, true); + Enabled.BindValueChanged(v => nameContainer.Alpha = v.NewValue ? 1f : 0.5f, true); } @@ -106,17 +117,6 @@ namespace osu.Game.Overlays.BeatmapSet updateState(); } - public void SetBeatmaps(List beatmaps) - { - Trace.Assert(beatmaps?.TrueForAll(b => b.Ruleset.Equals(Value)) ?? true, "A beatmap has a ruleset not of this tab value"); - - count.Text = beatmaps?.Count.ToString(); - - var hasBeatmaps = (beatmaps?.Count ?? 0) > 0; - count.Alpha = hasBeatmaps ? 1f : 0f; - Enabled.Value = hasBeatmaps; - } - private void updateState() { var isHoveredOrActive = IsHovered || Active.Value; From 71d45d41d1f85125dfae1171e3b5a2f954f4053d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Oct 2019 17:46:39 +0900 Subject: [PATCH 019/177] Add basic visualisation of different control point types --- .../ControlPoints/ControlPointInfo.cs | 6 + osu.Game/Screens/Edit/Timing/TimingScreen.cs | 133 +++++++++++++----- 2 files changed, 105 insertions(+), 34 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 855084ad02..68eb0ec6d1 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -36,6 +36,12 @@ namespace osu.Game.Beatmaps.ControlPoints [JsonProperty] public SortedList EffectPoints { get; private set; } = new SortedList(Comparer.Default); + public IReadOnlyList AllControlPoints => + TimingPoints + .Concat((IEnumerable)DifficultyPoints) + .Concat(SamplePoints) + .Concat(EffectPoints).ToArray(); + /// /// Finds the difficulty control point that is active at . /// diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index be7058ae08..97ec03e6dd 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -2,13 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Globalization; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Beatmaps; @@ -16,6 +16,7 @@ using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; +using osuTK; namespace osu.Game.Screens.Edit.Timing { @@ -64,7 +65,7 @@ namespace osu.Game.Screens.Edit.Timing RelativeSizeAxes = Axes.Both, Child = new ControlPointTable { - ControlPoints = Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints + ControlPoints = Beatmap.Value.Beatmap.ControlPointInfo.AllControlPoints } } }; @@ -115,7 +116,7 @@ namespace osu.Game.Screens.Edit.Timing }); } - public IReadOnlyList ControlPoints + public IEnumerable ControlPoints { set { @@ -125,14 +126,15 @@ namespace osu.Game.Screens.Edit.Timing if (value?.Any() != true) return; - for (int i = 0; i < value.Count; i++) + var grouped = value.GroupBy(cp => cp.Time, cp => cp); + + foreach (var group in grouped) { - var cp = value[i]; - backgroundFlow.Add(new RowBackground { Action = () => controlPoint.Value = cp }); + backgroundFlow.Add(new RowBackground { Action = () => controlPoint.Value = group.First() }); } Columns = createHeaders(); - Content = value.Select((s, i) => createContent(i, s)).ToArray().ToRectangular(); + Content = grouped.Select((s, i) => createContent(i, s)).ToArray().ToRectangular(); } } @@ -141,41 +143,104 @@ namespace osu.Game.Screens.Edit.Timing var columns = new List { new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("offset", Anchor.Centre), - new TableColumn("BPM", Anchor.Centre), - new TableColumn("Meter", Anchor.Centre), - new TableColumn("Sample Set", Anchor.Centre), - new TableColumn("Volume", Anchor.Centre), + new TableColumn("time", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("Attributes", Anchor.Centre), }; return columns.ToArray(); } - private Drawable[] createContent(int index, ControlPoint controlPoint) + private Drawable[] createContent(int index, IGrouping controlPoints) => new Drawable[] { - return new Drawable[] + new OsuSpriteText { - new OsuSpriteText + Text = $"#{index + 1}", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), + Margin = new MarginPadding(10) + }, + new OsuSpriteText + { + Text = $"{controlPoints.Key:n0}ms", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + ChildrenEnumerable = controlPoints.Select(createAttribute).Where(c => c != null), + Padding = new MarginPadding(10), + Spacing = new Vector2(10) + }, + }; + + private Drawable createAttribute(ControlPoint controlPoint) + { + if (controlPoint.AutoGenerated) + return null; + + switch (controlPoint) + { + case TimingControlPoint timing: + return new RowAttribute("timing", $"{60000 / timing.BeatLength:n1}bpm {timing.TimeSignature}"); + + case DifficultyControlPoint difficulty: + + return new RowAttribute("difficulty", $"{difficulty.SpeedMultiplier:n2}x"); + + case EffectControlPoint effect: + return new RowAttribute("effect", $"{(effect.KiaiMode ? "Kiai " : "")}{(effect.OmitFirstBarLine ? "NoBarLine " : "")}"); + + case SampleControlPoint sample: + return new RowAttribute("sample", $"{sample.SampleBank} {sample.SampleVolume}%"); + } + + return null; + } + + private class RowAttribute : CompositeDrawable, IHasTooltip + { + private readonly string header; + private readonly string content; + + public RowAttribute(string header, string content) + { + this.header = header; + this.content = content; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AutoSizeAxes = Axes.X; + + Height = 20; + + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + + Masking = true; + CornerRadius = 5; + + InternalChildren = new Drawable[] { - Text = $"#{index + 1}", - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) - }, - new OsuSpriteText - { - Text = $"{controlPoint.Time}", - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) - }, - new OsuSpriteText - { - Text = $"{(controlPoint as TimingControlPoint)?.BeatLength.ToString(CultureInfo.InvariantCulture) ?? "-"}", - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) - }, - new OsuSpriteText - { - Text = $"{(controlPoint as TimingControlPoint)?.TimeSignature.ToString() ?? "-"}", - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) - }, - }; + new Box + { + Colour = colours.Yellow, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Padding = new MarginPadding(2), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Default.With(weight: FontWeight.SemiBold, size: 12), + Text = header, + Colour = colours.Gray3 + }, + }; + } + + public string TooltipText => content; } protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty); From ffec960b77960b855f9e8b63fc2a9b4e90814d2a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 18 Oct 2019 17:59:54 +0900 Subject: [PATCH 020/177] Split out classes --- .../Screens/Edit/Timing/ControlPointTable.cs | 186 +++++++++++++++ osu.Game/Screens/Edit/Timing/RowAttribute.cs | 59 +++++ osu.Game/Screens/Edit/Timing/TimingScreen.cs | 219 ------------------ 3 files changed, 245 insertions(+), 219 deletions(-) create mode 100644 osu.Game/Screens/Edit/Timing/ControlPointTable.cs create mode 100644 osu.Game/Screens/Edit/Timing/RowAttribute.cs diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs new file mode 100644 index 0000000000..1ff88fb5b4 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -0,0 +1,186 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Input.Events; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osuTK; + +namespace osu.Game.Screens.Edit.Timing +{ + public class ControlPointTable : TableContainer + { + private const float horizontal_inset = 20; + private const float row_height = 25; + private const int text_size = 14; + + private readonly FillFlowContainer backgroundFlow; + + [Resolved] + private Bindable controlPoint { get; set; } + + public ControlPointTable() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + Padding = new MarginPadding { Horizontal = horizontal_inset }; + RowSize = new Dimension(GridSizeMode.Absolute, row_height); + + AddInternal(backgroundFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Depth = 1f, + Padding = new MarginPadding { Horizontal = -horizontal_inset }, + Margin = new MarginPadding { Top = row_height } + }); + } + + public IEnumerable ControlPoints + { + set + { + Content = null; + backgroundFlow.Clear(); + + if (value?.Any() != true) + return; + + var grouped = value.GroupBy(cp => cp.Time, cp => cp); + + foreach (var group in grouped) + { + backgroundFlow.Add(new RowBackground { Action = () => controlPoint.Value = group.First() }); + } + + Columns = createHeaders(); + Content = grouped.Select((s, i) => createContent(i, s)).ToArray().ToRectangular(); + } + } + + private TableColumn[] createHeaders() + { + var columns = new List + { + new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("time", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("Attributes", Anchor.Centre), + }; + + return columns.ToArray(); + } + + private Drawable[] createContent(int index, IGrouping controlPoints) => new Drawable[] + { + new OsuSpriteText + { + Text = $"#{index + 1}", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), + Margin = new MarginPadding(10) + }, + new OsuSpriteText + { + Text = $"{controlPoints.Key:n0}ms", + Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + ChildrenEnumerable = controlPoints.Select(createAttribute).Where(c => c != null), + Padding = new MarginPadding(10), + Spacing = new Vector2(10) + }, + }; + + private Drawable createAttribute(ControlPoint controlPoint) + { + if (controlPoint.AutoGenerated) + return null; + + switch (controlPoint) + { + case TimingControlPoint timing: + return new RowAttribute("timing", $"{60000 / timing.BeatLength:n1}bpm {timing.TimeSignature}"); + + case DifficultyControlPoint difficulty: + + return new RowAttribute("difficulty", $"{difficulty.SpeedMultiplier:n2}x"); + + case EffectControlPoint effect: + return new RowAttribute("effect", $"{(effect.KiaiMode ? "Kiai " : "")}{(effect.OmitFirstBarLine ? "NoBarLine " : "")}"); + + case SampleControlPoint sample: + return new RowAttribute("sample", $"{sample.SampleBank} {sample.SampleVolume}%"); + } + + return null; + } + + protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty); + + private class HeaderText : OsuSpriteText + { + public HeaderText(string text) + { + Text = text.ToUpper(); + Font = OsuFont.GetFont(size: 12, weight: FontWeight.Black); + } + } + + public class RowBackground : OsuClickableContainer + { + private const int fade_duration = 100; + + private readonly Box hoveredBackground; + + public RowBackground() + { + RelativeSizeAxes = Axes.X; + Height = 25; + + AlwaysPresent = true; + + CornerRadius = 3; + Masking = true; + + Children = new Drawable[] + { + hoveredBackground = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + }, + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + hoveredBackground.Colour = colours.Blue; + } + + protected override bool OnHover(HoverEvent e) + { + hoveredBackground.FadeIn(fade_duration, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + hoveredBackground.FadeOut(fade_duration, Easing.OutQuint); + base.OnHoverLost(e); + } + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/RowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttribute.cs new file mode 100644 index 0000000000..8716382142 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/RowAttribute.cs @@ -0,0 +1,59 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Cursor; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Timing +{ + public class RowAttribute : CompositeDrawable, IHasTooltip + { + private readonly string header; + private readonly string content; + + public RowAttribute(string header, string content) + { + this.header = header; + this.content = content; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + AutoSizeAxes = Axes.X; + + Height = 20; + + Anchor = Anchor.CentreLeft; + Origin = Anchor.CentreLeft; + + Masking = true; + CornerRadius = 5; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = colours.Yellow, + RelativeSizeAxes = Axes.Both, + }, + new OsuSpriteText + { + Padding = new MarginPadding(2), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Font = OsuFont.Default.With(weight: FontWeight.SemiBold, size: 12), + Text = header, + Colour = colours.Gray3 + }, + }; + } + + public string TooltipText => content; + } +} diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 97ec03e6dd..3dda57becc 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -1,22 +1,15 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; -using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Shapes; -using osu.Framework.Input.Events; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osuTK; namespace osu.Game.Screens.Edit.Timing { @@ -87,216 +80,4 @@ namespace osu.Game.Screens.Edit.Timing } } } - - public class ControlPointTable : TableContainer - { - private const float horizontal_inset = 20; - private const float row_height = 25; - private const int text_size = 14; - - private readonly FillFlowContainer backgroundFlow; - - [Resolved] - private Bindable controlPoint { get; set; } - - public ControlPointTable() - { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - Padding = new MarginPadding { Horizontal = horizontal_inset }; - RowSize = new Dimension(GridSizeMode.Absolute, row_height); - - AddInternal(backgroundFlow = new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Depth = 1f, - Padding = new MarginPadding { Horizontal = -horizontal_inset }, - Margin = new MarginPadding { Top = row_height } - }); - } - - public IEnumerable ControlPoints - { - set - { - Content = null; - backgroundFlow.Clear(); - - if (value?.Any() != true) - return; - - var grouped = value.GroupBy(cp => cp.Time, cp => cp); - - foreach (var group in grouped) - { - backgroundFlow.Add(new RowBackground { Action = () => controlPoint.Value = group.First() }); - } - - Columns = createHeaders(); - Content = grouped.Select((s, i) => createContent(i, s)).ToArray().ToRectangular(); - } - } - - private TableColumn[] createHeaders() - { - var columns = new List - { - new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("time", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("Attributes", Anchor.Centre), - }; - - return columns.ToArray(); - } - - private Drawable[] createContent(int index, IGrouping controlPoints) => new Drawable[] - { - new OsuSpriteText - { - Text = $"#{index + 1}", - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold), - Margin = new MarginPadding(10) - }, - new OsuSpriteText - { - Text = $"{controlPoints.Key:n0}ms", - Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - ChildrenEnumerable = controlPoints.Select(createAttribute).Where(c => c != null), - Padding = new MarginPadding(10), - Spacing = new Vector2(10) - }, - }; - - private Drawable createAttribute(ControlPoint controlPoint) - { - if (controlPoint.AutoGenerated) - return null; - - switch (controlPoint) - { - case TimingControlPoint timing: - return new RowAttribute("timing", $"{60000 / timing.BeatLength:n1}bpm {timing.TimeSignature}"); - - case DifficultyControlPoint difficulty: - - return new RowAttribute("difficulty", $"{difficulty.SpeedMultiplier:n2}x"); - - case EffectControlPoint effect: - return new RowAttribute("effect", $"{(effect.KiaiMode ? "Kiai " : "")}{(effect.OmitFirstBarLine ? "NoBarLine " : "")}"); - - case SampleControlPoint sample: - return new RowAttribute("sample", $"{sample.SampleBank} {sample.SampleVolume}%"); - } - - return null; - } - - private class RowAttribute : CompositeDrawable, IHasTooltip - { - private readonly string header; - private readonly string content; - - public RowAttribute(string header, string content) - { - this.header = header; - this.content = content; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - AutoSizeAxes = Axes.X; - - Height = 20; - - Anchor = Anchor.CentreLeft; - Origin = Anchor.CentreLeft; - - Masking = true; - CornerRadius = 5; - - InternalChildren = new Drawable[] - { - new Box - { - Colour = colours.Yellow, - RelativeSizeAxes = Axes.Both, - }, - new OsuSpriteText - { - Padding = new MarginPadding(2), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Font = OsuFont.Default.With(weight: FontWeight.SemiBold, size: 12), - Text = header, - Colour = colours.Gray3 - }, - }; - } - - public string TooltipText => content; - } - - protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty); - - private class HeaderText : OsuSpriteText - { - public HeaderText(string text) - { - Text = text.ToUpper(); - Font = OsuFont.GetFont(size: 12, weight: FontWeight.Black); - } - } - - public class RowBackground : OsuClickableContainer - { - private const int fade_duration = 100; - - private readonly Box hoveredBackground; - - public RowBackground() - { - RelativeSizeAxes = Axes.X; - Height = 25; - - AlwaysPresent = true; - - CornerRadius = 3; - Masking = true; - - Children = new Drawable[] - { - hoveredBackground = new Box - { - RelativeSizeAxes = Axes.Both, - Alpha = 0, - }, - }; - } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - hoveredBackground.Colour = colours.Blue; - } - - protected override bool OnHover(HoverEvent e) - { - hoveredBackground.FadeIn(fade_duration, Easing.OutQuint); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - hoveredBackground.FadeOut(fade_duration, Easing.OutQuint); - base.OnHoverLost(e); - } - } - } } From 7dc65ec9645e71bd790f5e157bf757ebfd6748c4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 20 Oct 2019 23:32:49 +0900 Subject: [PATCH 021/177] Add missing required types --- osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs index be84ae2cf9..54dcdc8da5 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs @@ -1,6 +1,8 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; +using System.Collections.Generic; using NUnit.Framework; using osu.Framework.Allocation; using osu.Game.Rulesets.Osu; @@ -11,6 +13,12 @@ namespace osu.Game.Tests.Visual.Editor [TestFixture] public class TestSceneTimingScreen : EditorClockTestScene { + public override IReadOnlyList RequiredTypes => new[] + { + typeof(ControlPointTable), + typeof(RowAttribute) + }; + [BackgroundDependencyLoader] private void load() { From 0fbba9a5e5c875f63a1e4afc7181aa4ee41895cf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 20 Oct 2019 23:42:13 +0900 Subject: [PATCH 022/177] Split out more classes --- .../Visual/Editor/TestSceneTimingScreen.cs | 1 + .../Edit/Timing/ControlPointSettings.cs | 26 +++++++++++++++++++ osu.Game/Screens/Edit/Timing/TimingScreen.cs | 15 ----------- 3 files changed, 27 insertions(+), 15 deletions(-) create mode 100644 osu.Game/Screens/Edit/Timing/ControlPointSettings.cs diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs index 54dcdc8da5..3e227169e0 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs @@ -16,6 +16,7 @@ namespace osu.Game.Tests.Visual.Editor public override IReadOnlyList RequiredTypes => new[] { typeof(ControlPointTable), + typeof(ControlPointSettings), typeof(RowAttribute) }; diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs new file mode 100644 index 0000000000..2feced9738 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -0,0 +1,26 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics; + +namespace osu.Game.Screens.Edit.Timing +{ + public class ControlPointSettings : CompositeDrawable + { + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.Both; + + InternalChild = new Box + { + Colour = colours.Gray3, + RelativeSizeAxes = Axes.Both, + }; + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 3dda57becc..9cbba6b155 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -64,20 +64,5 @@ namespace osu.Game.Screens.Edit.Timing }; } } - - public class ControlPointSettings : CompositeDrawable - { - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - RelativeSizeAxes = Axes.Both; - - InternalChild = new Box - { - Colour = colours.Gray3, - RelativeSizeAxes = Axes.Both, - }; - } - } } } From 80bf68c1081ed4dc90f1cc5c99c7810e8e34e597 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 21 Oct 2019 00:06:38 +0900 Subject: [PATCH 023/177] Add control sections and hook up bindable control groups --- .../Edit/Timing/ControlPointSettings.cs | 130 +++++++++++++++++- .../Screens/Edit/Timing/ControlPointTable.cs | 4 +- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 3 +- 3 files changed, 131 insertions(+), 6 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs index 2feced9738..dc4fb2a338 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -1,11 +1,20 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; +using osu.Game.Graphics.Containers; +using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; +using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Edit.Timing { @@ -16,11 +25,126 @@ namespace osu.Game.Screens.Edit.Timing { RelativeSizeAxes = Axes.Both; - InternalChild = new Box + InternalChildren = new Drawable[] { - Colour = colours.Gray3, - RelativeSizeAxes = Axes.Both, + new Box + { + Colour = colours.Gray3, + RelativeSizeAxes = Axes.Both, + }, + new OsuScrollContainer + { + RelativeSizeAxes = Axes.Both, + Child = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Spacing = new Vector2(30), + Children = createSections() + }, + } }; } + + private IReadOnlyList createSections() => new Drawable[] + { + new TimingSection(), + new DifficultySection(), + new SampleSection(), + new EffectSection(), + }; + + private class TimingSection : Section + { + } + + private class DifficultySection : Section + { + } + + private class SampleSection : Section + { + } + + private class EffectSection : Section + { + } + + private class Section : Container + where T : ControlPoint + { + private const float header_height = 20; + + protected override Container Content { get; } + + [Resolved] + private Bindable> selectedPoints { get; set; } + + protected Section() + { + RelativeSizeAxes = Axes.X; + AutoSizeDuration = 200; + AutoSizeEasing = Easing.OutQuint; + AutoSizeAxes = Axes.Y; + + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = Color4.Gray, + RelativeSizeAxes = Axes.Both, + }, + new Container + { + RelativeSizeAxes = Axes.X, + Height = header_height, + Children = new Drawable[] + { + new OsuSpriteText + { + Text = typeof(T).Name.Replace(typeof(ControlPoint).Name, string.Empty) + }, + } + }, + Content = new Container() + { + Y = header_height, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + Colour = Color4.DarkGray, + RelativeSizeAxes = Axes.X, + Height = 100, + }, + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedPoints.BindValueChanged(points => + { + var matching = points.NewValue?.OfType().Where(p => !p.AutoGenerated).FirstOrDefault(); + + if (matching != null) + { + Content.BypassAutoSizeAxes = Axes.None; + } + else + { + Content.BypassAutoSizeAxes = Axes.Y; + } + }, true); + } + } } } diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 1ff88fb5b4..5032e71fe8 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Edit.Timing private readonly FillFlowContainer backgroundFlow; [Resolved] - private Bindable controlPoint { get; set; } + private Bindable> selectedPoints { get; set; } public ControlPointTable() { @@ -60,7 +60,7 @@ namespace osu.Game.Screens.Edit.Timing foreach (var group in grouped) { - backgroundFlow.Add(new RowBackground { Action = () => controlPoint.Value = group.First() }); + backgroundFlow.Add(new RowBackground { Action = () => selectedPoints.Value = group }); } Columns = createHeaders(); diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 9cbba6b155..5da5d3f785 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -16,7 +17,7 @@ namespace osu.Game.Screens.Edit.Timing public class TimingScreen : EditorScreenWithTimeline { [Cached] - private readonly Bindable controlPoint = new Bindable(); + private Bindable> selectedPoints = new Bindable>(); protected override Drawable CreateMainContent() => new GridContainer { From 81e8b678d342c8a9f9f646974a9659790573cdea Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Oct 2019 20:17:19 +0900 Subject: [PATCH 024/177] Update editor time when a new timing point is selected --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 5da5d3f785..ed045aca86 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -2,11 +2,13 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; +using osu.Framework.Timing; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; @@ -19,6 +21,9 @@ namespace osu.Game.Screens.Edit.Timing [Cached] private Bindable> selectedPoints = new Bindable>(); + [Resolved] + private IAdjustableClock clock { get; set; } + protected override Drawable CreateMainContent() => new GridContainer { RelativeSizeAxes = Axes.Both, @@ -37,6 +42,13 @@ namespace osu.Game.Screens.Edit.Timing } }; + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedPoints.BindValueChanged(selected => { clock.Seek(selected.NewValue.First().Time); }); + } + public class ControlPointList : CompositeDrawable { [Resolved] From 4883844c4c390aa515fef26d4b730c8992d6fe57 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Oct 2019 20:57:56 +0900 Subject: [PATCH 025/177] Add basic information display for all types of control points --- .../Edit/Timing/ControlPointSettings.cs | 136 +++++++++++++++--- 1 file changed, 114 insertions(+), 22 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs index dc4fb2a338..1ab22679d9 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -14,7 +14,6 @@ using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; using osuTK; -using osuTK.Graphics; namespace osu.Game.Screens.Edit.Timing { @@ -40,7 +39,7 @@ namespace osu.Game.Screens.Edit.Timing RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(30), + Spacing = new Vector2(2), Children = createSections() }, } @@ -57,31 +56,123 @@ namespace osu.Game.Screens.Edit.Timing private class TimingSection : Section { + private OsuSpriteText bpm; + private OsuSpriteText timeSignature; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + bpm = new OsuSpriteText(), + timeSignature = new OsuSpriteText(), + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => + { + bpm.Text = $"BPM: {point.NewValue?.BeatLength}"; + timeSignature.Text = $"Signature: {point.NewValue?.TimeSignature}"; + }); + } } private class DifficultySection : Section { + private OsuSpriteText multiplier; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + multiplier = new OsuSpriteText(), + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier}"; }); + } } private class SampleSection : Section { + private OsuSpriteText bank; + private OsuSpriteText volume; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + bank = new OsuSpriteText(), + volume = new OsuSpriteText(), + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => + { + bank.Text = $"Bank: {point.NewValue?.SampleBank}"; + volume.Text = $"Volume: {point.NewValue?.SampleVolume}"; + }); + } } private class EffectSection : Section { + private OsuSpriteText kiai; + private OsuSpriteText omitBarLine; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + kiai = new OsuSpriteText(), + omitBarLine = new OsuSpriteText(), + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => + { + kiai.Text = $"Kiai: {point.NewValue?.KiaiMode}"; + omitBarLine.Text = $"Skip Bar Line: {point.NewValue?.OmitFirstBarLine}"; + }); + } } - private class Section : Container + private class Section : CompositeDrawable where T : ControlPoint { - private const float header_height = 20; + private OsuCheckbox checkbox; + private Container content; - protected override Container Content { get; } + protected FillFlowContainer Flow { get; private set; } + + protected Bindable ControlPoint { get; } = new Bindable(); + + private const float header_height = 20; [Resolved] private Bindable> selectedPoints { get; set; } - protected Section() + [BackgroundDependencyLoader] + private void load(OsuColour colours) { RelativeSizeAxes = Axes.X; AutoSizeDuration = 200; @@ -94,7 +185,7 @@ namespace osu.Game.Screens.Edit.Timing { new Box { - Colour = Color4.Gray, + Colour = colours.Gray1, RelativeSizeAxes = Axes.Both, }, new Container @@ -103,13 +194,13 @@ namespace osu.Game.Screens.Edit.Timing Height = header_height, Children = new Drawable[] { - new OsuSpriteText + checkbox = new OsuCheckbox { - Text = typeof(T).Name.Replace(typeof(ControlPoint).Name, string.Empty) - }, + LabelText = typeof(T).Name.Replace(typeof(ControlPoint).Name, string.Empty) + } } }, - Content = new Container() + content = new Container { Y = header_height, RelativeSizeAxes = Axes.X, @@ -118,9 +209,15 @@ namespace osu.Game.Screens.Edit.Timing { new Box { - Colour = Color4.DarkGray, + Colour = colours.Gray2, + RelativeSizeAxes = Axes.Both, + }, + Flow = new FillFlowContainer + { + Padding = new MarginPadding(10), RelativeSizeAxes = Axes.X, - Height = 100, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, }, } } @@ -133,17 +230,12 @@ namespace osu.Game.Screens.Edit.Timing selectedPoints.BindValueChanged(points => { - var matching = points.NewValue?.OfType().Where(p => !p.AutoGenerated).FirstOrDefault(); + ControlPoint.Value = points.NewValue?.OfType().Where(p => !p.AutoGenerated).FirstOrDefault(); - if (matching != null) - { - Content.BypassAutoSizeAxes = Axes.None; - } - else - { - Content.BypassAutoSizeAxes = Axes.Y; - } + checkbox.Current.Value = ControlPoint.Value != null; }, true); + + checkbox.Current.BindValueChanged(selected => { content.BypassAutoSizeAxes = selected.NewValue ? Axes.None : Axes.Y; }, true); } } } From 5e22eed131ca60093f816fa8a50a06a4536f8b85 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 22 Oct 2019 21:50:21 +0900 Subject: [PATCH 026/177] Add add/remove buttons --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 53 +++++++++++++++++++- 1 file changed, 52 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index ed045aca86..b6bd9ae61d 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; +using System.ComponentModel; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; @@ -13,6 +14,8 @@ using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; +using osu.Game.Graphics.UserInterface; +using osuTK; namespace osu.Game.Screens.Edit.Timing { @@ -51,9 +54,14 @@ namespace osu.Game.Screens.Edit.Timing public class ControlPointList : CompositeDrawable { + private OsuButton deleteButton; + [Resolved] protected IBindable Beatmap { get; private set; } + [Resolved] + private Bindable> selectedPoints { get; set; } + [BackgroundDependencyLoader] private void load(OsuColour colours) { @@ -73,9 +81,52 @@ namespace osu.Game.Screens.Edit.Timing { ControlPoints = Beatmap.Value.Beatmap.ControlPointInfo.AllControlPoints } - } + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + Direction = FillDirection.Horizontal, + Margin = new MarginPadding(10), + Spacing = new Vector2(5), + Children = new Drawable[] + { + deleteButton = new OsuButton + { + Text = "-", + Size = new Vector2(30, 30), + Action = delete, + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + new OsuButton + { + Text = "+", + Action = addNew, + Size = new Vector2(30, 30), + Anchor = Anchor.BottomRight, + Origin = Anchor.BottomRight, + }, + } + }, }; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedPoints.BindValueChanged(selected => { deleteButton.Enabled.Value = selected.NewValue != null; }, true); + } + + private void delete() + { + } + + private void addNew() + { + } } } } From c06f142433d547abe9d96bf082df5c36e8c8400b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 23 Oct 2019 11:22:55 +0900 Subject: [PATCH 027/177] Fix some spacing and references --- osu.Game/Screens/Edit/Timing/ControlPointSettings.cs | 2 -- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 2 +- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 1 - 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs index 1ab22679d9..0974745294 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -13,7 +13,6 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; -using osuTK; namespace osu.Game.Screens.Edit.Timing { @@ -39,7 +38,6 @@ namespace osu.Game.Screens.Edit.Timing RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, - Spacing = new Vector2(2), Children = createSections() }, } diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 5032e71fe8..777d34e75b 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -73,7 +73,7 @@ namespace osu.Game.Screens.Edit.Timing var columns = new List { new TableColumn(string.Empty, Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), - new TableColumn("time", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), + new TableColumn("Time", Anchor.Centre, new Dimension(GridSizeMode.AutoSize)), new TableColumn("Attributes", Anchor.Centre), }; diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index b6bd9ae61d..bd0a75f0b0 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -2,7 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.ComponentModel; using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; From e38b7cb169767b0d637cfcb166ed919161aa3087 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Oct 2019 17:00:56 +0900 Subject: [PATCH 028/177] Replace local Equatable implementations with abstract EquivalentTo --- osu.Game/Beatmaps/ControlPoints/ControlPoint.cs | 12 +++++++++--- .../Beatmaps/ControlPoints/DifficultyControlPoint.cs | 8 +++----- .../Beatmaps/ControlPoints/EffectControlPoint.cs | 10 ++++------ .../Beatmaps/ControlPoints/SampleControlPoint.cs | 9 ++++----- .../Beatmaps/ControlPoints/TimingControlPoint.cs | 9 ++++----- osu.Game/Beatmaps/Formats/LegacyDecoder.cs | 8 ++++---- 6 files changed, 28 insertions(+), 28 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index abe7e5e803..0081fab46a 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -5,7 +5,7 @@ using System; namespace osu.Game.Beatmaps.ControlPoints { - public class ControlPoint : IComparable, IEquatable + public abstract class ControlPoint : IComparable, IEquatable { /// /// The time at which the control point takes effect. @@ -19,7 +19,13 @@ namespace osu.Game.Beatmaps.ControlPoints public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time); - public bool Equals(ControlPoint other) - => Time.Equals(other?.Time); + /// + /// Whether this control point is equivalent to another, ignoring time. + /// + /// Another control point to compare with. + /// Whether equivalent. + public abstract bool EquivalentTo(ControlPoint other); + + public bool Equals(ControlPoint other) => Time.Equals(other?.Time) && EquivalentTo(other); } } diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index a3e3121575..42651fd0ca 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -1,12 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osuTK; namespace osu.Game.Beatmaps.ControlPoints { - public class DifficultyControlPoint : ControlPoint, IEquatable + public class DifficultyControlPoint : ControlPoint { /// /// The speed multiplier at this control point. @@ -19,8 +18,7 @@ namespace osu.Game.Beatmaps.ControlPoints private double speedMultiplier = 1; - public bool Equals(DifficultyControlPoint other) - => base.Equals(other) - && SpeedMultiplier.Equals(other?.SpeedMultiplier); + public override bool EquivalentTo(ControlPoint other) => + other is DifficultyControlPoint otherTyped && otherTyped.SpeedMultiplier.Equals(speedMultiplier); } } diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs index 354d86dc13..928f2a51ad 100644 --- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs @@ -1,11 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; - namespace osu.Game.Beatmaps.ControlPoints { - public class EffectControlPoint : ControlPoint, IEquatable + public class EffectControlPoint : ControlPoint { /// /// Whether this control point enables Kiai mode. @@ -17,8 +15,8 @@ namespace osu.Game.Beatmaps.ControlPoints /// public bool OmitFirstBarLine; - public bool Equals(EffectControlPoint other) - => base.Equals(other) - && KiaiMode == other?.KiaiMode && OmitFirstBarLine == other.OmitFirstBarLine; + public override bool EquivalentTo(ControlPoint other) => + other is EffectControlPoint otherTyped && + KiaiMode == otherTyped.KiaiMode && OmitFirstBarLine == otherTyped.OmitFirstBarLine; } } diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index 7bc7a9056d..35eefebca4 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -1,12 +1,11 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osu.Game.Audio; namespace osu.Game.Beatmaps.ControlPoints { - public class SampleControlPoint : ControlPoint, IEquatable + public class SampleControlPoint : ControlPoint { public const string DEFAULT_BANK = "normal"; @@ -45,8 +44,8 @@ namespace osu.Game.Beatmaps.ControlPoints return newSampleInfo; } - public bool Equals(SampleControlPoint other) - => base.Equals(other) - && string.Equals(SampleBank, other?.SampleBank) && SampleVolume == other?.SampleVolume; + public override bool EquivalentTo(ControlPoint other) => + other is SampleControlPoint otherTyped && + string.Equals(SampleBank, otherTyped?.SampleBank) && SampleVolume == otherTyped?.SampleVolume; } } diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index ccb8a92b3a..03b188929b 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -1,13 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osuTK; using osu.Game.Beatmaps.Timing; namespace osu.Game.Beatmaps.ControlPoints { - public class TimingControlPoint : ControlPoint, IEquatable + public class TimingControlPoint : ControlPoint { /// /// The time signature at this control point. @@ -27,8 +26,8 @@ namespace osu.Game.Beatmaps.ControlPoints private double beatLength = DEFAULT_BEAT_LENGTH; - public bool Equals(TimingControlPoint other) - => base.Equals(other) - && TimeSignature == other?.TimeSignature && beatLength.Equals(other.beatLength); + public override bool EquivalentTo(ControlPoint other) => + other is TimingControlPoint otherTyped + && TimeSignature == otherTyped?.TimeSignature && beatLength.Equals(otherTyped.beatLength); } } diff --git a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs index 83d20da458..26f7209be6 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDecoder.cs @@ -189,7 +189,7 @@ namespace osu.Game.Beatmaps.Formats Foreground = 3 } - internal class LegacySampleControlPoint : SampleControlPoint, IEquatable + internal class LegacySampleControlPoint : SampleControlPoint { public int CustomSampleBank; @@ -203,9 +203,9 @@ namespace osu.Game.Beatmaps.Formats return baseInfo; } - public bool Equals(LegacySampleControlPoint other) - => base.Equals(other) - && CustomSampleBank == other?.CustomSampleBank; + public override bool EquivalentTo(ControlPoint other) => + base.EquivalentTo(other) + && CustomSampleBank == ((LegacySampleControlPoint)other).CustomSampleBank; } } } From 94ffe03e6e71701e723db6196532dde41fd1c254 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Oct 2019 16:19:06 +0900 Subject: [PATCH 029/177] Group timing points --- .../TestSceneDrawableHitObjects.cs | 2 +- .../TestSceneOsuDistanceSnapGrid.cs | 13 +- .../TestSceneSlider.cs | 2 +- .../TestSceneSliderInput.cs | 6 +- .../TestSceneTaikoPlayfield.cs | 6 +- .../Audio/DrumSampleMapping.cs | 7 +- .../Editor/TestSceneDistanceSnapGrid.cs | 10 +- .../Editor/TestSceneEditorSeekSnapping.cs | 20 ++- .../TestSceneDrawableScrollingRuleset.cs | 34 ++--- .../TestSceneBeatSyncedContainer.cs | 4 +- .../Beatmaps/ControlPoints/ControlPoint.cs | 6 +- .../ControlPoints/ControlPointGroup.cs | 48 +++++++ .../ControlPoints/ControlPointInfo.cs | 130 ++++++++++++++++-- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 92 ++----------- .../Containers/BeatSyncedContainer.cs | 2 - osu.Game/Screens/Edit/EditorClock.cs | 5 +- 16 files changed, 233 insertions(+), 154 deletions(-) create mode 100644 osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs diff --git a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs index 7a9b61c60c..0369b6db4e 100644 --- a/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs +++ b/osu.Game.Rulesets.Catch.Tests/TestSceneDrawableHitObjects.cs @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Catch.Tests private void load() { var controlPointInfo = new ControlPointInfo(); - controlPointInfo.TimingPoints.Add(new TimingControlPoint()); + controlPointInfo.Add(0, new TimingControlPoint()); WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap { diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs index 6b8daa531f..b66123e628 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs @@ -53,9 +53,8 @@ namespace osu.Game.Rulesets.Osu.Tests Clear(); editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1; - editorBeatmap.ControlPointInfo.DifficultyPoints.Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); + editorBeatmap.ControlPointInfo.Clear(); + editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); beatDivisor.Value = 1; }); @@ -80,8 +79,8 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep($"set beat length = {beatLength}", () => { - editorBeatmap.ControlPointInfo.TimingPoints.Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beatLength }); + editorBeatmap.ControlPointInfo.Clear(); + editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beatLength }); }); createGrid(); @@ -95,8 +94,8 @@ namespace osu.Game.Rulesets.Osu.Tests { AddStep($"set speed multiplier = {multiplier}", () => { - editorBeatmap.ControlPointInfo.DifficultyPoints.Clear(); - editorBeatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = multiplier }); + editorBeatmap.ControlPointInfo.Clear(); + editorBeatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = multiplier }); }); createGrid(); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs index 4893ebfdd4..a955911bd5 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSlider.cs @@ -308,7 +308,7 @@ namespace osu.Game.Rulesets.Osu.Tests private Drawable createDrawable(Slider slider, float circleSize, double speedMultiplier) { var cpi = new ControlPointInfo(); - cpi.DifficultyPoints.Add(new DifficultyControlPoint { SpeedMultiplier = speedMultiplier }); + cpi.Add(0, new DifficultyControlPoint { SpeedMultiplier = speedMultiplier }); slider.ApplyDefaults(cpi, new BeatmapDifficulty { CircleSize = circleSize, SliderTickRate = 3 }); diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs index 2eb783233a..5f75cbabec 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderInput.cs @@ -313,10 +313,6 @@ namespace osu.Game.Rulesets.Osu.Tests }, 25), } }, - ControlPointInfo = - { - DifficultyPoints = { new DifficultyControlPoint { SpeedMultiplier = 0.1f } } - }, BeatmapInfo = { BaseDifficulty = new BeatmapDifficulty { SliderTickRate = 3 }, @@ -324,6 +320,8 @@ namespace osu.Game.Rulesets.Osu.Tests }, }); + Beatmap.Value.Beatmap.ControlPointInfo.Add(0, new DifficultyControlPoint { SpeedMultiplier = 0.1f }); + var p = new ScoreAccessibleReplayPlayer(new Score { Replay = new Replay { Frames = frames } }); p.OnLoadComplete += _ => diff --git a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs index eaa8ca7ebb..8522a42739 100644 --- a/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs +++ b/osu.Game.Rulesets.Taiko.Tests/TestSceneTaikoPlayfield.cs @@ -66,7 +66,7 @@ namespace osu.Game.Rulesets.Taiko.Tests AddStep("Reset height", () => changePlayfieldSize(6)); var controlPointInfo = new ControlPointInfo(); - controlPointInfo.TimingPoints.Add(new TimingControlPoint()); + controlPointInfo.Add(0, new TimingControlPoint()); WorkingBeatmap beatmap = CreateWorkingBeatmap(new Beatmap { @@ -142,7 +142,7 @@ namespace osu.Game.Rulesets.Taiko.Tests HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Good : HitResult.Great; var cpi = new ControlPointInfo(); - cpi.EffectPoints.Add(new EffectControlPoint { KiaiMode = kiai }); + cpi.Add(0, new EffectControlPoint { KiaiMode = kiai }); Hit hit = new Hit(); hit.ApplyDefaults(cpi, new BeatmapDifficulty()); @@ -157,7 +157,7 @@ namespace osu.Game.Rulesets.Taiko.Tests HitResult hitResult = RNG.Next(2) == 0 ? HitResult.Good : HitResult.Great; var cpi = new ControlPointInfo(); - cpi.EffectPoints.Add(new EffectControlPoint { KiaiMode = kiai }); + cpi.Add(0, new EffectControlPoint { KiaiMode = kiai }); Hit hit = new Hit(); hit.ApplyDefaults(cpi, new BeatmapDifficulty()); diff --git a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs index ad2596931d..aaf113f216 100644 --- a/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs +++ b/osu.Game.Rulesets.Taiko/Audio/DrumSampleMapping.cs @@ -19,12 +19,7 @@ namespace osu.Game.Rulesets.Taiko.Audio { this.controlPoints = controlPoints; - IEnumerable samplePoints; - if (controlPoints.SamplePoints.Count == 0) - // Get the default sample point - samplePoints = new[] { controlPoints.SamplePointAt(double.MinValue) }; - else - samplePoints = controlPoints.SamplePoints; + IEnumerable samplePoints = controlPoints.SamplePoints.Count == 0 ? new[] { controlPoints.SamplePointAt(double.MinValue) } : controlPoints.SamplePoints; foreach (var s in samplePoints) { diff --git a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs index a9e5930478..07646fdb78 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs @@ -32,7 +32,7 @@ namespace osu.Game.Tests.Visual.Editor public TestSceneDistanceSnapGrid() { editorBeatmap = new EditorBeatmap(new OsuBeatmap()); - editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); + editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); createGrid(); } @@ -42,8 +42,8 @@ namespace osu.Game.Tests.Visual.Editor { Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beat_length }); + editorBeatmap.ControlPointInfo.Clear(); + editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); BeatDivisor.Value = 1; }); @@ -81,8 +81,8 @@ namespace osu.Game.Tests.Visual.Editor { AddStep($"set beat length = {beatLength}", () => { - editorBeatmap.ControlPointInfo.TimingPoints.Clear(); - editorBeatmap.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { BeatLength = beatLength }); + editorBeatmap.ControlPointInfo.Clear(); + editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beatLength }); }); createGrid(); diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorSeekSnapping.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorSeekSnapping.cs index b997d6aaeb..3118e0cabe 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorSeekSnapping.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorSeekSnapping.cs @@ -28,18 +28,7 @@ namespace osu.Game.Tests.Visual.Editor { var testBeatmap = new Beatmap { - ControlPointInfo = new ControlPointInfo - { - TimingPoints = - { - new TimingControlPoint { Time = 0, BeatLength = 200 }, - new TimingControlPoint { Time = 100, BeatLength = 400 }, - new TimingControlPoint { Time = 175, BeatLength = 800 }, - new TimingControlPoint { Time = 350, BeatLength = 200 }, - new TimingControlPoint { Time = 450, BeatLength = 100 }, - new TimingControlPoint { Time = 500, BeatLength = 307.69230769230802 } - } - }, + ControlPointInfo = new ControlPointInfo(), HitObjects = { new HitCircle { StartTime = 0 }, @@ -47,6 +36,13 @@ namespace osu.Game.Tests.Visual.Editor } }; + testBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 200 }); + testBeatmap.ControlPointInfo.Add(100, new TimingControlPoint { BeatLength = 400 }); + testBeatmap.ControlPointInfo.Add(175, new TimingControlPoint { BeatLength = 800 }); + testBeatmap.ControlPointInfo.Add(350, new TimingControlPoint { BeatLength = 200 }); + testBeatmap.ControlPointInfo.Add(450, new TimingControlPoint { BeatLength = 100 }); + testBeatmap.ControlPointInfo.Add(500, new TimingControlPoint { BeatLength = 307.69230769230802 }); + Beatmap.Value = CreateWorkingBeatmap(testBeatmap); Child = new TimingPointVisualiser(testBeatmap, 5000) { Clock = Clock }; diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs index dcab964d6d..684e79b3f5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneDrawableScrollingRuleset.cs @@ -47,7 +47,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestRelativeBeatLengthScaleSingleTimingPoint() { - var beatmap = createBeatmap(new TimingControlPoint { BeatLength = time_range / 2 }); + var beatmap = createBeatmap(); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range / 2 }); createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true); @@ -61,10 +62,10 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestRelativeBeatLengthScaleTimingPointBeyondEndDoesNotBecomeDominant() { - var beatmap = createBeatmap( - new TimingControlPoint { BeatLength = time_range / 2 }, - new TimingControlPoint { Time = 12000, BeatLength = time_range }, - new TimingControlPoint { Time = 100000, BeatLength = time_range }); + var beatmap = createBeatmap(); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range / 2 }); + beatmap.ControlPointInfo.Add(12000, new TimingControlPoint { BeatLength = time_range }); + beatmap.ControlPointInfo.Add(100000, new TimingControlPoint { BeatLength = time_range }); createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true); @@ -75,9 +76,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestRelativeBeatLengthScaleFromSecondTimingPoint() { - var beatmap = createBeatmap( - new TimingControlPoint { BeatLength = time_range }, - new TimingControlPoint { Time = 3 * time_range, BeatLength = time_range / 2 }); + var beatmap = createBeatmap(); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range }); + beatmap.ControlPointInfo.Add(3 * time_range, new TimingControlPoint { BeatLength = time_range / 2 }); createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true); @@ -97,9 +98,9 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestNonRelativeScale() { - var beatmap = createBeatmap( - new TimingControlPoint { BeatLength = time_range }, - new TimingControlPoint { Time = 3 * time_range, BeatLength = time_range / 2 }); + var beatmap = createBeatmap(); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range }); + beatmap.ControlPointInfo.Add(3 * time_range, new TimingControlPoint { BeatLength = time_range / 2 }); createTest(beatmap); @@ -119,7 +120,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestSliderMultiplierDoesNotAffectRelativeBeatLength() { - var beatmap = createBeatmap(new TimingControlPoint { BeatLength = time_range }); + var beatmap = createBeatmap(); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range }); beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2; createTest(beatmap, d => d.RelativeScaleBeatLengthsOverride = true); @@ -132,7 +134,8 @@ namespace osu.Game.Tests.Visual.Gameplay [Test] public void TestSliderMultiplierAffectsNonRelativeBeatLength() { - var beatmap = createBeatmap(new TimingControlPoint { BeatLength = time_range }); + var beatmap = createBeatmap(); + beatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = time_range }); beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 2; createTest(beatmap); @@ -154,14 +157,11 @@ namespace osu.Game.Tests.Visual.Gameplay /// Creates an , containing 10 hitobjects and user-provided timing points. /// The hitobjects are spaced milliseconds apart. /// - /// The timing points to add to the beatmap. /// The . - private IBeatmap createBeatmap(params TimingControlPoint[] timingControlPoints) + private IBeatmap createBeatmap() { var beatmap = new Beatmap { BeatmapInfo = { Ruleset = new OsuRuleset().RulesetInfo } }; - beatmap.ControlPointInfo.TimingPoints.AddRange(timingControlPoints); - for (int i = 0; i < 10; i++) beatmap.HitObjects.Add(new HitObject { StartTime = i * time_range }); diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs index d84ffa0d93..b6df559686 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneBeatSyncedContainer.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using Microsoft.EntityFrameworkCore.Internal; using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Audio.Track; @@ -10,7 +11,6 @@ using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Lists; using osu.Framework.Timing; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; @@ -153,7 +153,7 @@ namespace osu.Game.Tests.Visual.UserInterface }; } - private SortedList timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints; + private IReadOnlyList timingPoints => Beatmap.Value.Beatmap.ControlPointInfo.TimingPoints; private TimingControlPoint getNextTimingPoint(TimingControlPoint current) { diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index 0081fab46a..6288c1460f 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -10,13 +10,17 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time at which the control point takes effect. /// - public double Time; + public double Time => controlPointGroup?.Time ?? 0; /// /// Whether this timing point was generated internally, as opposed to parsed from the underlying beatmap. /// internal bool AutoGenerated; + private ControlPointGroup controlPointGroup; + + public void AttachGroup(ControlPointGroup pointGroup) => this.controlPointGroup = pointGroup; + public int CompareTo(ControlPoint other) => Time.CompareTo(other.Time); /// diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs new file mode 100644 index 0000000000..c4b990675e --- /dev/null +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs @@ -0,0 +1,48 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using System.Collections.Generic; +using System.Linq; + +namespace osu.Game.Beatmaps.ControlPoints +{ + public class ControlPointGroup : IComparable + { + public event Action ItemAdded; + public event Action ItemRemoved; + + /// + /// The time at which the control point takes effect. + /// + public double Time { get; } + + public IReadOnlyList ControlPoints => controlPoints; + + private readonly List controlPoints = new List(); + + public ControlPointGroup(double time) + { + Time = time; + } + + public int CompareTo(ControlPointGroup other) => Time.CompareTo(other.Time); + + public void Add(ControlPoint point) + { + point.AttachGroup(this); + + foreach (var existing in controlPoints.Where(p => p.GetType() == point.GetType()).ToArray()) + Remove(existing); + + controlPoints.Add(point); + ItemAdded?.Invoke(point); + } + + public void Remove(ControlPoint point) + { + controlPoints.Remove(point); + ItemRemoved?.Invoke(point); + } + } +} diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 68eb0ec6d1..2175eccaa2 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -12,35 +12,50 @@ namespace osu.Game.Beatmaps.ControlPoints [Serializable] public class ControlPointInfo { + /// + /// Control point groups. + /// + [JsonProperty] + public IReadOnlyList Groups => groups; + + private readonly SortedList groups = new SortedList(Comparer.Default); + /// /// All timing points. /// [JsonProperty] - public SortedList TimingPoints { get; private set; } = new SortedList(Comparer.Default); + public IReadOnlyList TimingPoints => timingPoints; + + private readonly SortedList timingPoints = new SortedList(Comparer.Default); /// /// All difficulty points. /// [JsonProperty] - public SortedList DifficultyPoints { get; private set; } = new SortedList(Comparer.Default); + public IReadOnlyList DifficultyPoints => difficultyPoints; + + private readonly SortedList difficultyPoints = new SortedList(Comparer.Default); /// /// All sound points. /// [JsonProperty] - public SortedList SamplePoints { get; private set; } = new SortedList(Comparer.Default); + public IReadOnlyList SamplePoints => samplePoints; + + private readonly SortedList samplePoints = new SortedList(Comparer.Default); /// /// All effect points. /// [JsonProperty] - public SortedList EffectPoints { get; private set; } = new SortedList(Comparer.Default); + public IReadOnlyList EffectPoints => effectPoints; - public IReadOnlyList AllControlPoints => - TimingPoints - .Concat((IEnumerable)DifficultyPoints) - .Concat(SamplePoints) - .Concat(EffectPoints).ToArray(); + private readonly SortedList effectPoints = new SortedList(Comparer.Default); + + /// + /// All control points, of all types. + /// + public IEnumerable AllControlPoints => Groups.SelectMany(g => g.ControlPoints); /// /// Finds the difficulty control point that is active at . @@ -70,6 +85,28 @@ namespace osu.Game.Beatmaps.ControlPoints /// The timing control point. public TimingControlPoint TimingPointAt(double time) => binarySearch(TimingPoints, time, TimingPoints.Count > 0 ? TimingPoints[0] : null); + /// + /// Finds the closest of the same type as that is active at . + /// + /// The time to find the timing control point at. + /// A reference point to infer type. + /// The timing control point. + public ControlPoint SimilarPointAt(double time, ControlPoint referencePoint) + { + switch (referencePoint) + { + case TimingControlPoint _: return TimingPointAt(time); + + case EffectControlPoint _: return EffectPointAt(time); + + case SampleControlPoint _: return SamplePointAt(time); + + case DifficultyControlPoint _: return DifficultyPointAt(time); + } + + return null; + } + /// /// Finds the maximum BPM represented by any timing control point. /// @@ -98,7 +135,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// The time to find the control point at. /// The control point to use when is before any control points. If null, a new control point will be constructed. /// The active control point at . - private T binarySearch(SortedList list, double time, T prePoint = null) + private T binarySearch(IReadOnlyList list, double time, T prePoint = null) where T : ControlPoint, new() { if (list == null) @@ -131,5 +168,78 @@ namespace osu.Game.Beatmaps.ControlPoints // l will be the first control point with Time > time, but we want the one before it return list[l - 1]; } + + public void Add(double time, ControlPoint newPoint, bool force = false) + { + if (!force && SimilarPointAt(time, newPoint)?.EquivalentTo(newPoint) == true) + return; + + GroupAt(time, true).Add(newPoint); + } + + public ControlPointGroup GroupAt(double time, bool createIfNotExisting) + { + var existing = Groups.FirstOrDefault(g => g.Time == time); + + if (existing != null) + return existing; + + if (createIfNotExisting) + { + var newGroup = new ControlPointGroup(time); + newGroup.ItemAdded += groupItemAdded; + newGroup.ItemRemoved += groupItemRemoved; + groups.Add(newGroup); + return newGroup; + } + + return null; + } + + private void groupItemRemoved(ControlPoint obj) + { + switch (obj) + { + case TimingControlPoint typed: + timingPoints.Remove(typed); + break; + + case EffectControlPoint typed: + effectPoints.Remove(typed); + break; + + case SampleControlPoint typed: + samplePoints.Remove(typed); + break; + + case DifficultyControlPoint typed: + difficultyPoints.Remove(typed); + break; + } + } + + private void groupItemAdded(ControlPoint obj) + { + switch (obj) + { + case TimingControlPoint typed: + timingPoints.Add(typed); + break; + + case EffectControlPoint typed: + effectPoints.Add(typed); + break; + + case SampleControlPoint typed: + samplePoints.Add(typed); + break; + + case DifficultyControlPoint typed: + difficultyPoints.Add(typed); + break; + } + } + + public void Clear() => groups.Clear(); } } diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 786b7611b5..61f70a8c27 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -369,31 +369,29 @@ namespace osu.Game.Beatmaps.Formats if (timingChange) { var controlPoint = CreateTimingControlPoint(); - controlPoint.Time = time; controlPoint.BeatLength = beatLength; controlPoint.TimeSignature = timeSignature; - handleTimingControlPoint(controlPoint); + beatmap.ControlPointInfo.Add(time, controlPoint); + } + else + { + beatmap.ControlPointInfo.Add(time, new DifficultyControlPoint + { + SpeedMultiplier = speedMultiplier, + AutoGenerated = timingChange + }); } - handleDifficultyControlPoint(new DifficultyControlPoint + beatmap.ControlPointInfo.Add(time, new EffectControlPoint { - Time = time, - SpeedMultiplier = speedMultiplier, - AutoGenerated = timingChange - }); - - handleEffectControlPoint(new EffectControlPoint - { - Time = time, KiaiMode = kiaiMode, OmitFirstBarLine = omitFirstBarSignature, AutoGenerated = timingChange }); - handleSampleControlPoint(new LegacySampleControlPoint + beatmap.ControlPointInfo.Add(time, new LegacySampleControlPoint { - Time = time, SampleBank = stringSampleSet, SampleVolume = sampleVolume, CustomSampleBank = customSampleBank, @@ -401,74 +399,6 @@ namespace osu.Game.Beatmaps.Formats }); } - private void handleTimingControlPoint(TimingControlPoint newPoint) - { - var existing = beatmap.ControlPointInfo.TimingPointAt(newPoint.Time); - - if (existing.Time == newPoint.Time) - { - // autogenerated points should not replace non-autogenerated. - // this allows for incorrectly ordered timing points to still be correctly handled. - if (newPoint.AutoGenerated && !existing.AutoGenerated) - return; - - beatmap.ControlPointInfo.TimingPoints.Remove(existing); - } - - beatmap.ControlPointInfo.TimingPoints.Add(newPoint); - } - - private void handleDifficultyControlPoint(DifficultyControlPoint newPoint) - { - var existing = beatmap.ControlPointInfo.DifficultyPointAt(newPoint.Time); - - if (existing.Time == newPoint.Time) - { - // autogenerated points should not replace non-autogenerated. - // this allows for incorrectly ordered timing points to still be correctly handled. - if (newPoint.AutoGenerated && !existing.AutoGenerated) - return; - - beatmap.ControlPointInfo.DifficultyPoints.Remove(existing); - } - - beatmap.ControlPointInfo.DifficultyPoints.Add(newPoint); - } - - private void handleEffectControlPoint(EffectControlPoint newPoint) - { - var existing = beatmap.ControlPointInfo.EffectPointAt(newPoint.Time); - - if (existing.Time == newPoint.Time) - { - // autogenerated points should not replace non-autogenerated. - // this allows for incorrectly ordered timing points to still be correctly handled. - if (newPoint.AutoGenerated && !existing.AutoGenerated) - return; - - beatmap.ControlPointInfo.EffectPoints.Remove(existing); - } - - beatmap.ControlPointInfo.EffectPoints.Add(newPoint); - } - - private void handleSampleControlPoint(SampleControlPoint newPoint) - { - var existing = beatmap.ControlPointInfo.SamplePointAt(newPoint.Time); - - if (existing.Time == newPoint.Time) - { - // autogenerated points should not replace non-autogenerated. - // this allows for incorrectly ordered timing points to still be correctly handled. - if (newPoint.AutoGenerated && !existing.AutoGenerated) - return; - - beatmap.ControlPointInfo.SamplePoints.Remove(existing); - } - - beatmap.ControlPointInfo.SamplePoints.Add(newPoint); - } - private void handleHitObject(string line) { // If the ruleset wasn't specified, assume the osu!standard ruleset. diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 370d044ba4..2832a70518 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -105,12 +105,10 @@ namespace osu.Game.Graphics.Containers { BeatLength = default_beat_length, AutoGenerated = true, - Time = 0 }; defaultEffect = new EffectControlPoint { - Time = 0, AutoGenerated = true, KiaiMode = false, OmitFirstBarLine = false diff --git a/osu.Game/Screens/Edit/EditorClock.cs b/osu.Game/Screens/Edit/EditorClock.cs index 24fb561f04..1cfb123f25 100644 --- a/osu.Game/Screens/Edit/EditorClock.cs +++ b/osu.Game/Screens/Edit/EditorClock.cs @@ -3,6 +3,7 @@ using System; using System.Linq; +using Microsoft.EntityFrameworkCore.Internal; using osu.Framework.MathUtils; using osu.Framework.Timing; using osu.Game.Beatmaps; @@ -57,7 +58,7 @@ namespace osu.Game.Screens.Edit // Depending on beatSnapLength, we may snap to a beat that is beyond timingPoint's end time, but we want to instead snap to // the next timing point's start time - var nextTimingPoint = ControlPointInfo.TimingPoints.Find(t => t.Time > timingPoint.Time); + var nextTimingPoint = ControlPointInfo.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time); if (position > nextTimingPoint?.Time) position = nextTimingPoint.Time; @@ -124,7 +125,7 @@ namespace osu.Game.Screens.Edit if (seekTime < timingPoint.Time && timingPoint != ControlPointInfo.TimingPoints.First()) seekTime = timingPoint.Time; - var nextTimingPoint = ControlPointInfo.TimingPoints.Find(t => t.Time > timingPoint.Time); + var nextTimingPoint = ControlPointInfo.TimingPoints.FirstOrDefault(t => t.Time > timingPoint.Time); if (seekTime > nextTimingPoint?.Time) seekTime = nextTimingPoint.Time; From da6769f0fcd9966a24624ec97451a16dd01fc187 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Oct 2019 17:00:23 +0900 Subject: [PATCH 030/177] Remove necessity of AutoGenerated flag --- .../Formats/LegacyBeatmapDecoderTest.cs | 10 +-- .../Beatmaps/ControlPoints/ControlPoint.cs | 7 +-- .../ControlPoints/ControlPointInfo.cs | 2 +- .../Beatmaps/Formats/LegacyBeatmapDecoder.cs | 63 ++++++++++++++----- .../Containers/BeatSyncedContainer.cs | 2 - .../Edit/Timing/ControlPointSettings.cs | 2 +- .../Screens/Edit/Timing/ControlPointTable.cs | 3 - 7 files changed, 56 insertions(+), 33 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index de516d3142..c50250159e 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -167,9 +167,9 @@ namespace osu.Game.Tests.Beatmaps.Formats var controlPoints = beatmap.ControlPointInfo; Assert.AreEqual(4, controlPoints.TimingPoints.Count); - Assert.AreEqual(42, controlPoints.DifficultyPoints.Count); - Assert.AreEqual(42, controlPoints.SamplePoints.Count); - Assert.AreEqual(42, controlPoints.EffectPoints.Count); + Assert.AreEqual(5, controlPoints.DifficultyPoints.Count); + Assert.AreEqual(34, controlPoints.SamplePoints.Count); + Assert.AreEqual(8, controlPoints.EffectPoints.Count); var timingPoint = controlPoints.TimingPointAt(0); Assert.AreEqual(956, timingPoint.Time); @@ -191,7 +191,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); difficultyPoint = controlPoints.DifficultyPointAt(48428); - Assert.AreEqual(48428, difficultyPoint.Time); + Assert.AreEqual(0, difficultyPoint.Time); Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); difficultyPoint = controlPoints.DifficultyPointAt(116999); @@ -224,7 +224,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.IsFalse(effectPoint.OmitFirstBarLine); effectPoint = controlPoints.EffectPointAt(119637); - Assert.AreEqual(119637, effectPoint.Time); + Assert.AreEqual(95901, effectPoint.Time); Assert.IsFalse(effectPoint.KiaiMode); Assert.IsFalse(effectPoint.OmitFirstBarLine); } diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs index 6288c1460f..0861e00d8d 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPoint.cs @@ -1,4 +1,4 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. using System; @@ -12,11 +12,6 @@ namespace osu.Game.Beatmaps.ControlPoints /// public double Time => controlPointGroup?.Time ?? 0; - /// - /// Whether this timing point was generated internally, as opposed to parsed from the underlying beatmap. - /// - internal bool AutoGenerated; - private ControlPointGroup controlPointGroup; public void AttachGroup(ControlPointGroup pointGroup) => this.controlPointGroup = pointGroup; diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 2175eccaa2..7e9c8844f0 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -177,7 +177,7 @@ namespace osu.Game.Beatmaps.ControlPoints GroupAt(time, true).Add(newPoint); } - public ControlPointGroup GroupAt(double time, bool createIfNotExisting) + public ControlPointGroup GroupAt(double time, bool createIfNotExisting = false) { var existing = Groups.FirstOrDefault(g => g.Time == time); diff --git a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs index 61f70a8c27..5589aecc19 100644 --- a/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyBeatmapDecoder.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; using System.IO; using System.Linq; using osu.Framework.IO.File; @@ -50,6 +51,8 @@ namespace osu.Game.Beatmaps.Formats base.ParseStreamInto(stream, beatmap); + flushPendingPoints(); + // Objects may be out of order *only* if a user has manually edited an .osu file. // Unfortunately there are ranked maps in this state (example: https://osu.ppy.sh/s/594828). // OrderBy is used to guarantee that the parsing order of hitobjects with equal start times is maintained (stably-sorted) @@ -369,34 +372,64 @@ namespace osu.Game.Beatmaps.Formats if (timingChange) { var controlPoint = CreateTimingControlPoint(); + controlPoint.BeatLength = beatLength; controlPoint.TimeSignature = timeSignature; - beatmap.ControlPointInfo.Add(time, controlPoint); - } - else - { - beatmap.ControlPointInfo.Add(time, new DifficultyControlPoint - { - SpeedMultiplier = speedMultiplier, - AutoGenerated = timingChange - }); + addControlPoint(time, controlPoint, true); } - beatmap.ControlPointInfo.Add(time, new EffectControlPoint + addControlPoint(time, new DifficultyControlPoint + { + SpeedMultiplier = speedMultiplier, + }, timingChange); + + addControlPoint(time, new EffectControlPoint { KiaiMode = kiaiMode, OmitFirstBarLine = omitFirstBarSignature, - AutoGenerated = timingChange - }); + }, timingChange); - beatmap.ControlPointInfo.Add(time, new LegacySampleControlPoint + addControlPoint(time, new LegacySampleControlPoint { SampleBank = stringSampleSet, SampleVolume = sampleVolume, CustomSampleBank = customSampleBank, - AutoGenerated = timingChange - }); + }, timingChange); + + // To handle the scenario where a non-timing line shares the same time value as a subsequent timing line but + // appears earlier in the file, we buffer non-timing control points and rewrite them *after* control points from the timing line + // with the same time value (allowing them to overwrite as necessary). + // + // The expected outcome is that we prefer the non-timing line's adjustments over the timing line's adjustments when time is equal. + if (timingChange) + flushPendingPoints(); + } + + private readonly List pendingControlPoints = new List(); + private double pendingControlPointsTime; + + private void addControlPoint(double time, ControlPoint point, bool timingChange) + { + if (timingChange) + { + beatmap.ControlPointInfo.Add(time, point); + return; + } + + if (time != pendingControlPointsTime) + flushPendingPoints(); + + pendingControlPoints.Add(point); + pendingControlPointsTime = time; + } + + private void flushPendingPoints() + { + foreach (var p in pendingControlPoints) + beatmap.ControlPointInfo.Add(pendingControlPointsTime, p); + + pendingControlPoints.Clear(); } private void handleHitObject(string line) diff --git a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs index 2832a70518..2e76ab964f 100644 --- a/osu.Game/Graphics/Containers/BeatSyncedContainer.cs +++ b/osu.Game/Graphics/Containers/BeatSyncedContainer.cs @@ -104,12 +104,10 @@ namespace osu.Game.Graphics.Containers defaultTiming = new TimingControlPoint { BeatLength = default_beat_length, - AutoGenerated = true, }; defaultEffect = new EffectControlPoint { - AutoGenerated = true, KiaiMode = false, OmitFirstBarLine = false }; diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs index 0974745294..ce72d7c191 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -228,7 +228,7 @@ namespace osu.Game.Screens.Edit.Timing selectedPoints.BindValueChanged(points => { - ControlPoint.Value = points.NewValue?.OfType().Where(p => !p.AutoGenerated).FirstOrDefault(); + ControlPoint.Value = points.NewValue?.OfType().FirstOrDefault(); checkbox.Current.Value = ControlPoint.Value != null; }, true); diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 777d34e75b..597200e54c 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -105,9 +105,6 @@ namespace osu.Game.Screens.Edit.Timing private Drawable createAttribute(ControlPoint controlPoint) { - if (controlPoint.AutoGenerated) - return null; - switch (controlPoint) { case TimingControlPoint timing: From b8efc59cdcd2be6d4ca8cbe9466221c61b70672c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 25 Oct 2019 20:13:22 +0900 Subject: [PATCH 031/177] Update UI components to use new grouping --- .../Edit/Timing/ControlPointSettings.cs | 4 ++-- .../Screens/Edit/Timing/ControlPointTable.cs | 18 ++++++++---------- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 10 ++++------ 3 files changed, 14 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs index ce72d7c191..470750b860 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -167,7 +167,7 @@ namespace osu.Game.Screens.Edit.Timing private const float header_height = 20; [Resolved] - private Bindable> selectedPoints { get; set; } + private Bindable selectedPoints { get; set; } [BackgroundDependencyLoader] private void load(OsuColour colours) @@ -228,7 +228,7 @@ namespace osu.Game.Screens.Edit.Timing selectedPoints.BindValueChanged(points => { - ControlPoint.Value = points.NewValue?.OfType().FirstOrDefault(); + ControlPoint.Value = points.NewValue?.ControlPoints.OfType().FirstOrDefault(); checkbox.Current.Value = ControlPoint.Value != null; }, true); diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 597200e54c..aca74d4a20 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Edit.Timing private readonly FillFlowContainer backgroundFlow; [Resolved] - private Bindable> selectedPoints { get; set; } + private Bindable selectedGroup { get; set; } public ControlPointTable() { @@ -46,7 +46,7 @@ namespace osu.Game.Screens.Edit.Timing }); } - public IEnumerable ControlPoints + public IEnumerable ControlGroups { set { @@ -56,15 +56,13 @@ namespace osu.Game.Screens.Edit.Timing if (value?.Any() != true) return; - var grouped = value.GroupBy(cp => cp.Time, cp => cp); - - foreach (var group in grouped) + foreach (var group in value) { - backgroundFlow.Add(new RowBackground { Action = () => selectedPoints.Value = group }); + backgroundFlow.Add(new RowBackground { Action = () => selectedGroup.Value = group }); } Columns = createHeaders(); - Content = grouped.Select((s, i) => createContent(i, s)).ToArray().ToRectangular(); + Content = value.Select((g, i) => createContent(i, g)).ToArray().ToRectangular(); } } @@ -80,7 +78,7 @@ namespace osu.Game.Screens.Edit.Timing return columns.ToArray(); } - private Drawable[] createContent(int index, IGrouping controlPoints) => new Drawable[] + private Drawable[] createContent(int index, ControlPointGroup group) => new Drawable[] { new OsuSpriteText { @@ -90,14 +88,14 @@ namespace osu.Game.Screens.Edit.Timing }, new OsuSpriteText { - Text = $"{controlPoints.Key:n0}ms", + Text = $"{group.Time:n0}ms", Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) }, new FillFlowContainer { RelativeSizeAxes = Axes.Both, Direction = FillDirection.Horizontal, - ChildrenEnumerable = controlPoints.Select(createAttribute).Where(c => c != null), + ChildrenEnumerable = group.ControlPoints.Select(createAttribute).Where(c => c != null), Padding = new MarginPadding(10), Spacing = new Vector2(10) }, diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index bd0a75f0b0..a9fd038829 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -1,8 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -21,7 +19,7 @@ namespace osu.Game.Screens.Edit.Timing public class TimingScreen : EditorScreenWithTimeline { [Cached] - private Bindable> selectedPoints = new Bindable>(); + private Bindable selectedPoints = new Bindable(); [Resolved] private IAdjustableClock clock { get; set; } @@ -48,7 +46,7 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - selectedPoints.BindValueChanged(selected => { clock.Seek(selected.NewValue.First().Time); }); + selectedPoints.BindValueChanged(selected => { clock.Seek(selected.NewValue.Time); }); } public class ControlPointList : CompositeDrawable @@ -59,7 +57,7 @@ namespace osu.Game.Screens.Edit.Timing protected IBindable Beatmap { get; private set; } [Resolved] - private Bindable> selectedPoints { get; set; } + private Bindable selectedPoints { get; set; } [BackgroundDependencyLoader] private void load(OsuColour colours) @@ -78,7 +76,7 @@ namespace osu.Game.Screens.Edit.Timing RelativeSizeAxes = Axes.Both, Child = new ControlPointTable { - ControlPoints = Beatmap.Value.Beatmap.ControlPointInfo.AllControlPoints + ControlGroups = Beatmap.Value.Beatmap.ControlPointInfo.Groups } }, new FillFlowContainer From b0e21c2749d24d58de0b4c92569244bdeed77460 Mon Sep 17 00:00:00 2001 From: nwabear Date: Fri, 25 Oct 2019 14:57:49 -0500 Subject: [PATCH 032/177] Fixed Issue #6442 --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 7 +++++++ osu.Game/Screens/Play/Player.cs | 5 +++++ 2 files changed, 12 insertions(+) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index d5b3df27df..d37f053486 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -239,6 +239,11 @@ namespace osu.Game.Rulesets.UI continueResume(); } + public override void CancelResume() + { + ResumeOverlay.Hide(); + } + /// /// Creates and adds the visual representation of a to this . /// @@ -453,6 +458,8 @@ namespace osu.Game.Rulesets.UI /// The action to run when resuming is to be completed. public abstract void RequestResume(Action continueResume); + public abstract void CancelResume(); + /// /// Create a for the associated ruleset and link with this /// . diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 0b363eac4d..7eccde4555 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -443,6 +443,11 @@ namespace osu.Game.Screens.Play { if (!canPause) return; + if (IsResuming) + { + DrawableRuleset.CancelResume(); + } + IsResuming = false; GameplayClockContainer.Stop(); PauseOverlay.Show(); From f8354eefc4e058b98fc9d834bc0be2bc441db1e4 Mon Sep 17 00:00:00 2001 From: nwabear Date: Fri, 25 Oct 2019 16:49:18 -0500 Subject: [PATCH 033/177] Added null check in the CancelResume method --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index d37f053486..d3e1118625 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -241,7 +241,7 @@ namespace osu.Game.Rulesets.UI public override void CancelResume() { - ResumeOverlay.Hide(); + ResumeOverlay?.Hide(); } /// From 8ccff0e9cf6e42f21a23b324bc12cc6bea4105fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sat, 26 Oct 2019 11:20:07 +0900 Subject: [PATCH 034/177] temp --- .../Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 6 +++--- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 8 ++++---- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index c50250159e..7317771bac 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -167,9 +167,9 @@ namespace osu.Game.Tests.Beatmaps.Formats var controlPoints = beatmap.ControlPointInfo; Assert.AreEqual(4, controlPoints.TimingPoints.Count); - Assert.AreEqual(5, controlPoints.DifficultyPoints.Count); + Assert.AreEqual(6, controlPoints.DifficultyPoints.Count); Assert.AreEqual(34, controlPoints.SamplePoints.Count); - Assert.AreEqual(8, controlPoints.EffectPoints.Count); + Assert.AreEqual(9, controlPoints.EffectPoints.Count); var timingPoint = controlPoints.TimingPointAt(0); Assert.AreEqual(956, timingPoint.Time); @@ -191,7 +191,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); difficultyPoint = controlPoints.DifficultyPointAt(48428); - Assert.AreEqual(0, difficultyPoint.Time); + Assert.AreEqual(956, difficultyPoint.Time); Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); difficultyPoint = controlPoints.DifficultyPointAt(116999); diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 24e3c3d8ca..f8c6d3aa3b 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -95,13 +95,13 @@ namespace osu.Game.Beatmaps.ControlPoints { switch (referencePoint) { - case TimingControlPoint _: return TimingPointAt(time); + case TimingControlPoint _: return binarySearch(TimingPoints, time); - case EffectControlPoint _: return EffectPointAt(time); + case EffectControlPoint _: return binarySearch(EffectPoints, time); - case SampleControlPoint _: return SamplePointAt(time); + case SampleControlPoint _: return binarySearch(SamplePoints, time); - case DifficultyControlPoint _: return DifficultyPointAt(time); + case DifficultyControlPoint _: return binarySearch(DifficultyPoints, time); } return null; From 9e2e87c8d13cf147b720825a5ff266c024cabf72 Mon Sep 17 00:00:00 2001 From: nwabear Date: Sat, 26 Oct 2019 14:29:52 -0500 Subject: [PATCH 035/177] added visual tests added small commenting added xmldoc for CancelResume(); --- .../Visual/Gameplay/TestScenePause.cs | 18 ++++++++++++++++++ osu.Game/Rulesets/UI/DrawableRuleset.cs | 5 +++++ 2 files changed, 23 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 2df22df659..64022b2410 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -69,6 +69,24 @@ namespace osu.Game.Tests.Visual.Gameplay confirmClockRunning(true); } + [Test] + public void TestPauseWithResumeOverlay() + { + AddStep("move cursor to center", () => InputManager.MoveMouseTo(Player.ScreenSpaceDrawQuad.Centre)); + AddUntilStep("wait for hitobjects", () => Player.ScoreProcessor.Health.Value < 1); + + pauseAndConfirm(); + + resume(); + confirmClockRunning(false); + confirmPauseOverlayShown(false); + + pauseAndConfirm(); + + AddUntilStep("resume overlay is not active", () => Player.DrawableRuleset.ResumeOverlay.State.Value == Visibility.Hidden); + confirmPaused(); + } + [Test] public void TestResumeWithResumeOverlaySkipped() { diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index d3e1118625..44e2e60be7 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -239,8 +239,10 @@ namespace osu.Game.Rulesets.UI continueResume(); } + public override void CancelResume() { + // called if the user pauses while the resume overlay is open ResumeOverlay?.Hide(); } @@ -458,6 +460,9 @@ namespace osu.Game.Rulesets.UI /// The action to run when resuming is to be completed. public abstract void RequestResume(Action continueResume); + /// + /// Invoked when the user requests to pause while the resume overlay is active. + /// public abstract void CancelResume(); /// From e35931fdfc7b6c94d401ab0076ce3e80f2edffcd Mon Sep 17 00:00:00 2001 From: nwabear Date: Sat, 26 Oct 2019 14:33:59 -0500 Subject: [PATCH 036/177] removed blank line --- osu.Game/Rulesets/UI/DrawableRuleset.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Rulesets/UI/DrawableRuleset.cs b/osu.Game/Rulesets/UI/DrawableRuleset.cs index 44e2e60be7..e005eea831 100644 --- a/osu.Game/Rulesets/UI/DrawableRuleset.cs +++ b/osu.Game/Rulesets/UI/DrawableRuleset.cs @@ -239,7 +239,6 @@ namespace osu.Game.Rulesets.UI continueResume(); } - public override void CancelResume() { // called if the user pauses while the resume overlay is open From 93b003eb5adf1fdb2c31a6e6af2809e2fc9e315a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Oct 2019 13:31:23 +0900 Subject: [PATCH 037/177] Add selected row state --- .../Screens/Edit/Timing/ControlPointTable.cs | 54 +++++++++++++++++-- 1 file changed, 49 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index aca74d4a20..df1f6daf4e 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -15,6 +15,7 @@ using osu.Game.Graphics; using osu.Game.Graphics.Containers; using osu.Game.Graphics.Sprites; using osuTK; +using osuTK.Graphics; namespace osu.Game.Screens.Edit.Timing { @@ -58,7 +59,7 @@ namespace osu.Game.Screens.Edit.Timing foreach (var group in value) { - backgroundFlow.Add(new RowBackground { Action = () => selectedGroup.Value = group }); + backgroundFlow.Add(new RowBackground(group)); } Columns = createHeaders(); @@ -135,12 +136,17 @@ namespace osu.Game.Screens.Edit.Timing public class RowBackground : OsuClickableContainer { + private readonly ControlPointGroup controlGroup; private const int fade_duration = 100; private readonly Box hoveredBackground; - public RowBackground() + [Resolved] + private Bindable selectedGroup { get; set; } + + public RowBackground(ControlPointGroup controlGroup) { + this.controlGroup = controlGroup; RelativeSizeAxes = Axes.X; Height = 25; @@ -157,25 +163,63 @@ namespace osu.Game.Screens.Edit.Timing Alpha = 0, }, }; + + Action = () => selectedGroup.Value = controlGroup; } + private Color4 colourHover; + private Color4 colourSelected; + [BackgroundDependencyLoader] private void load(OsuColour colours) { - hoveredBackground.Colour = colours.Blue; + hoveredBackground.Colour = colourHover = colours.BlueDarker; + colourSelected = colours.YellowDarker; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + selectedGroup.BindValueChanged(group => { Selected = controlGroup == group.NewValue; }); + } + + private bool selected; + + protected bool Selected + { + get => selected; + set + { + if (value == selected) + return; + + selected = value; + updateState(); + } } protected override bool OnHover(HoverEvent e) { - hoveredBackground.FadeIn(fade_duration, Easing.OutQuint); + updateState(); return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - hoveredBackground.FadeOut(fade_duration, Easing.OutQuint); + updateState(); base.OnHoverLost(e); } + + private void updateState() + { + hoveredBackground.FadeColour(selected ? colourSelected : colourHover, 450, Easing.OutQuint); + + if (selected || IsHovered) + hoveredBackground.FadeIn(fade_duration, Easing.OutQuint); + else + hoveredBackground.FadeOut(fade_duration, Easing.OutQuint); + } } } } From de69665a4652f2f0595bb612c3c53570348522df Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Oct 2019 14:17:59 +0900 Subject: [PATCH 038/177] Reduce horizontal spacing of attributes --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index df1f6daf4e..8d5774cd09 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -98,7 +98,7 @@ namespace osu.Game.Screens.Edit.Timing Direction = FillDirection.Horizontal, ChildrenEnumerable = group.ControlPoints.Select(createAttribute).Where(c => c != null), Padding = new MarginPadding(10), - Spacing = new Vector2(10) + Spacing = new Vector2(2) }, }; From 0fba272e78dc1501847d8a97c8a38a69e126b695 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Oct 2019 15:19:36 +0900 Subject: [PATCH 039/177] Add the ability to add new ControlPoint types to existing groups --- .../Visual/Editor/TestSceneTimingScreen.cs | 5 + .../ControlPoints/ControlPointGroup.cs | 6 +- .../Edit/Timing/ControlPointSettings.cs | 190 ------------------ .../Screens/Edit/Timing/ControlPointTable.cs | 64 ++++-- .../Screens/Edit/Timing/DifficultySection.cs | 37 ++++ osu.Game/Screens/Edit/Timing/EffectSection.cs | 44 ++++ osu.Game/Screens/Edit/Timing/SampleSection.cs | 44 ++++ osu.Game/Screens/Edit/Timing/Section.cs | 126 ++++++++++++ osu.Game/Screens/Edit/Timing/TimingSection.cs | 44 ++++ 9 files changed, 346 insertions(+), 214 deletions(-) create mode 100644 osu.Game/Screens/Edit/Timing/DifficultySection.cs create mode 100644 osu.Game/Screens/Edit/Timing/EffectSection.cs create mode 100644 osu.Game/Screens/Edit/Timing/SampleSection.cs create mode 100644 osu.Game/Screens/Edit/Timing/Section.cs create mode 100644 osu.Game/Screens/Edit/Timing/TimingSection.cs diff --git a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs index 3e227169e0..121853d8d0 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneTimingScreen.cs @@ -17,6 +17,11 @@ namespace osu.Game.Tests.Visual.Editor { typeof(ControlPointTable), typeof(ControlPointSettings), + typeof(Section<>), + typeof(TimingSection), + typeof(EffectSection), + typeof(SampleSection), + typeof(DifficultySection), typeof(RowAttribute) }; diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs index d57baf25be..cb73ce884e 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointGroup.cs @@ -2,8 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; using System.Linq; +using osu.Framework.Bindables; namespace osu.Game.Beatmaps.ControlPoints { @@ -17,9 +17,9 @@ namespace osu.Game.Beatmaps.ControlPoints /// public double Time { get; } - public IReadOnlyList ControlPoints => controlPoints; + public IBindableList ControlPoints => controlPoints; - private readonly List controlPoints = new List(); + private readonly BindableList controlPoints = new BindableList(); public ControlPointGroup(double time) { diff --git a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs index 470750b860..e1182d9fa4 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointSettings.cs @@ -2,17 +2,12 @@ // See the LICENCE file in the repository root for full licence text. using System.Collections.Generic; -using System.Linq; using osu.Framework.Allocation; -using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; namespace osu.Game.Screens.Edit.Timing { @@ -51,190 +46,5 @@ namespace osu.Game.Screens.Edit.Timing new SampleSection(), new EffectSection(), }; - - private class TimingSection : Section - { - private OsuSpriteText bpm; - private OsuSpriteText timeSignature; - - [BackgroundDependencyLoader] - private void load() - { - Flow.AddRange(new[] - { - bpm = new OsuSpriteText(), - timeSignature = new OsuSpriteText(), - }); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => - { - bpm.Text = $"BPM: {point.NewValue?.BeatLength}"; - timeSignature.Text = $"Signature: {point.NewValue?.TimeSignature}"; - }); - } - } - - private class DifficultySection : Section - { - private OsuSpriteText multiplier; - - [BackgroundDependencyLoader] - private void load() - { - Flow.AddRange(new[] - { - multiplier = new OsuSpriteText(), - }); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier}"; }); - } - } - - private class SampleSection : Section - { - private OsuSpriteText bank; - private OsuSpriteText volume; - - [BackgroundDependencyLoader] - private void load() - { - Flow.AddRange(new[] - { - bank = new OsuSpriteText(), - volume = new OsuSpriteText(), - }); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => - { - bank.Text = $"Bank: {point.NewValue?.SampleBank}"; - volume.Text = $"Volume: {point.NewValue?.SampleVolume}"; - }); - } - } - - private class EffectSection : Section - { - private OsuSpriteText kiai; - private OsuSpriteText omitBarLine; - - [BackgroundDependencyLoader] - private void load() - { - Flow.AddRange(new[] - { - kiai = new OsuSpriteText(), - omitBarLine = new OsuSpriteText(), - }); - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => - { - kiai.Text = $"Kiai: {point.NewValue?.KiaiMode}"; - omitBarLine.Text = $"Skip Bar Line: {point.NewValue?.OmitFirstBarLine}"; - }); - } - } - - private class Section : CompositeDrawable - where T : ControlPoint - { - private OsuCheckbox checkbox; - private Container content; - - protected FillFlowContainer Flow { get; private set; } - - protected Bindable ControlPoint { get; } = new Bindable(); - - private const float header_height = 20; - - [Resolved] - private Bindable selectedPoints { get; set; } - - [BackgroundDependencyLoader] - private void load(OsuColour colours) - { - RelativeSizeAxes = Axes.X; - AutoSizeDuration = 200; - AutoSizeEasing = Easing.OutQuint; - AutoSizeAxes = Axes.Y; - - Masking = true; - - InternalChildren = new Drawable[] - { - new Box - { - Colour = colours.Gray1, - RelativeSizeAxes = Axes.Both, - }, - new Container - { - RelativeSizeAxes = Axes.X, - Height = header_height, - Children = new Drawable[] - { - checkbox = new OsuCheckbox - { - LabelText = typeof(T).Name.Replace(typeof(ControlPoint).Name, string.Empty) - } - } - }, - content = new Container - { - Y = header_height, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Children = new Drawable[] - { - new Box - { - Colour = colours.Gray2, - RelativeSizeAxes = Axes.Both, - }, - Flow = new FillFlowContainer - { - Padding = new MarginPadding(10), - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - }, - } - } - }; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - - selectedPoints.BindValueChanged(points => - { - ControlPoint.Value = points.NewValue?.ControlPoints.OfType().FirstOrDefault(); - - checkbox.Current.Value = ControlPoint.Value != null; - }, true); - - checkbox.Current.BindValueChanged(selected => { content.BypassAutoSizeAxes = selected.NewValue ? Axes.None : Axes.Y; }, true); - } - } } } diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 8d5774cd09..9c11443199 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -92,35 +92,57 @@ namespace osu.Game.Screens.Edit.Timing Text = $"{group.Time:n0}ms", Font = OsuFont.GetFont(size: text_size, weight: FontWeight.Bold) }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.Both, - Direction = FillDirection.Horizontal, - ChildrenEnumerable = group.ControlPoints.Select(createAttribute).Where(c => c != null), - Padding = new MarginPadding(10), - Spacing = new Vector2(2) - }, + new ControlGroupAttributes(group), }; - private Drawable createAttribute(ControlPoint controlPoint) + private class ControlGroupAttributes : CompositeDrawable { - switch (controlPoint) + private readonly IBindableList controlPoints; + + private readonly FillFlowContainer fill; + + public ControlGroupAttributes(ControlPointGroup group) { - case TimingControlPoint timing: - return new RowAttribute("timing", $"{60000 / timing.BeatLength:n1}bpm {timing.TimeSignature}"); + InternalChild = fill = new FillFlowContainer + { + RelativeSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Padding = new MarginPadding(10), + Spacing = new Vector2(2) + }; - case DifficultyControlPoint difficulty: + controlPoints = group.ControlPoints.GetBoundCopy(); + controlPoints.ItemsAdded += _ => createChildren(); + controlPoints.ItemsRemoved += _ => createChildren(); - return new RowAttribute("difficulty", $"{difficulty.SpeedMultiplier:n2}x"); - - case EffectControlPoint effect: - return new RowAttribute("effect", $"{(effect.KiaiMode ? "Kiai " : "")}{(effect.OmitFirstBarLine ? "NoBarLine " : "")}"); - - case SampleControlPoint sample: - return new RowAttribute("sample", $"{sample.SampleBank} {sample.SampleVolume}%"); + createChildren(); } - return null; + private void createChildren() + { + fill.ChildrenEnumerable = controlPoints.Select(createAttribute).Where(c => c != null); + } + + private Drawable createAttribute(ControlPoint controlPoint) + { + switch (controlPoint) + { + case TimingControlPoint timing: + return new RowAttribute("timing", $"{60000 / timing.BeatLength:n1}bpm {timing.TimeSignature}"); + + case DifficultyControlPoint difficulty: + + return new RowAttribute("difficulty", $"{difficulty.SpeedMultiplier:n2}x"); + + case EffectControlPoint effect: + return new RowAttribute("effect", $"{(effect.KiaiMode ? "Kiai " : "")}{(effect.OmitFirstBarLine ? "NoBarLine " : "")}"); + + case SampleControlPoint sample: + return new RowAttribute("sample", $"{sample.SampleBank} {sample.SampleVolume}%"); + } + + return null; + } } protected override Drawable CreateHeader(int index, TableColumn column) => new HeaderText(column?.Header ?? string.Empty); diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs new file mode 100644 index 0000000000..150c11f5ee --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/DifficultySection.cs @@ -0,0 +1,37 @@ +using osu.Framework.Allocation; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Timing +{ + internal class DifficultySection : Section + { + private OsuSpriteText multiplier; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + multiplier = new OsuSpriteText(), + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier}"; }); + } + + protected override DifficultyControlPoint CreatePoint() + { + var reference = Beatmap.Value.Beatmap.ControlPointInfo.DifficultyPointAt(SelectedGroup.Value.Time); + + return new DifficultyControlPoint + { + SpeedMultiplier = reference.SpeedMultiplier, + }; + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/EffectSection.cs b/osu.Game/Screens/Edit/Timing/EffectSection.cs new file mode 100644 index 0000000000..ff8817147a --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/EffectSection.cs @@ -0,0 +1,44 @@ +using osu.Framework.Allocation; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Timing +{ + internal class EffectSection : Section + { + private OsuSpriteText kiai; + private OsuSpriteText omitBarLine; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + kiai = new OsuSpriteText(), + omitBarLine = new OsuSpriteText(), + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => + { + kiai.Text = $"Kiai: {point.NewValue?.KiaiMode}"; + omitBarLine.Text = $"Skip Bar Line: {point.NewValue?.OmitFirstBarLine}"; + }); + } + + protected override EffectControlPoint CreatePoint() + { + var reference = Beatmap.Value.Beatmap.ControlPointInfo.EffectPointAt(SelectedGroup.Value.Time); + + return new EffectControlPoint + { + KiaiMode = reference.KiaiMode, + OmitFirstBarLine = reference.OmitFirstBarLine + }; + } + } +} diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs new file mode 100644 index 0000000000..0d6bc74057 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/SampleSection.cs @@ -0,0 +1,44 @@ +using osu.Framework.Allocation; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Timing +{ + internal class SampleSection : Section + { + private OsuSpriteText bank; + private OsuSpriteText volume; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + bank = new OsuSpriteText(), + volume = new OsuSpriteText(), + }); + } + + protected override SampleControlPoint CreatePoint() + { + var reference = Beatmap.Value.Beatmap.ControlPointInfo.SamplePointAt(SelectedGroup.Value.Time); + + return new SampleControlPoint + { + SampleBank = reference.SampleBank, + SampleVolume = reference.SampleVolume, + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => + { + bank.Text = $"Bank: {point.NewValue?.SampleBank}"; + volume.Text = $"Volume: {point.NewValue?.SampleVolume}"; + }); + } + } +} \ No newline at end of file diff --git a/osu.Game/Screens/Edit/Timing/Section.cs b/osu.Game/Screens/Edit/Timing/Section.cs new file mode 100644 index 0000000000..c6140ff497 --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/Section.cs @@ -0,0 +1,126 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Edit.Timing +{ + internal abstract class Section : CompositeDrawable + where T : ControlPoint + { + private OsuCheckbox checkbox; + private Container content; + + protected FillFlowContainer Flow { get; private set; } + + protected Bindable ControlPoint { get; } = new Bindable(); + + private const float header_height = 20; + + [Resolved] + protected IBindable Beatmap { get; private set; } + + [Resolved] + protected Bindable SelectedGroup { get; private set; } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + RelativeSizeAxes = Axes.X; + AutoSizeDuration = 200; + AutoSizeEasing = Easing.OutQuint; + AutoSizeAxes = Axes.Y; + + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + Colour = colours.Gray1, + RelativeSizeAxes = Axes.Both, + }, + new Container + { + RelativeSizeAxes = Axes.X, + Height = header_height, + Children = new Drawable[] + { + checkbox = new OsuCheckbox + { + LabelText = typeof(T).Name.Replace(typeof(ControlPoint).Name, string.Empty) + } + } + }, + content = new Container + { + Y = header_height, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Children = new Drawable[] + { + new Box + { + Colour = colours.Gray2, + RelativeSizeAxes = Axes.Both, + }, + Flow = new FillFlowContainer + { + Padding = new MarginPadding(10), + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + }, + } + } + }; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + checkbox.Current.BindValueChanged(selected => + { + if (selected.NewValue) + { + if (SelectedGroup.Value == null) + { + checkbox.Current.Value = false; + return; + } + + if (ControlPoint.Value == null) + SelectedGroup.Value.Add(ControlPoint.Value = CreatePoint()); + } + else + { + if (ControlPoint.Value != null) + { + SelectedGroup.Value.Remove(ControlPoint.Value); + ControlPoint.Value = null; + } + } + + content.BypassAutoSizeAxes = selected.NewValue ? Axes.None : Axes.Y; + }, true); + + SelectedGroup.BindValueChanged(points => + { + ControlPoint.Value = points.NewValue?.ControlPoints.OfType().FirstOrDefault(); + checkbox.Current.Value = ControlPoint.Value != null; + }, true); + } + + protected abstract T CreatePoint(); + } +} diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs new file mode 100644 index 0000000000..dcbb6f8bbf --- /dev/null +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -0,0 +1,44 @@ +using osu.Framework.Allocation; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Graphics.Sprites; + +namespace osu.Game.Screens.Edit.Timing +{ + internal class TimingSection : Section + { + private OsuSpriteText bpm; + private OsuSpriteText timeSignature; + + [BackgroundDependencyLoader] + private void load() + { + Flow.AddRange(new[] + { + bpm = new OsuSpriteText(), + timeSignature = new OsuSpriteText(), + }); + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + ControlPoint.BindValueChanged(point => + { + bpm.Text = $"BPM: {point.NewValue?.BeatLength}"; + timeSignature.Text = $"Signature: {point.NewValue?.TimeSignature}"; + }); + } + + protected override TimingControlPoint CreatePoint() + { + var reference = Beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(SelectedGroup.Value.Time); + + return new TimingControlPoint + { + BeatLength = reference.BeatLength, + TimeSignature = reference.TimeSignature + }; + } + } +} From 73369ae6130680101aeaf6216307b13e9b76f564 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Oct 2019 16:13:24 +0900 Subject: [PATCH 040/177] Add the ability to add/remove groups --- .../ControlPoints/ControlPointInfo.cs | 19 +++++++++++-- .../Screens/Edit/Timing/ControlPointTable.cs | 2 +- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 27 ++++++++++++++----- 3 files changed, 39 insertions(+), 9 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index e165adedea..4f83e14eaf 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -5,6 +5,7 @@ using System; using System.Collections.Generic; using System.Linq; using Newtonsoft.Json; +using osu.Framework.Bindables; using osu.Framework.Lists; namespace osu.Game.Beatmaps.ControlPoints @@ -16,9 +17,9 @@ namespace osu.Game.Beatmaps.ControlPoints /// Control point groups. /// [JsonProperty] - public IReadOnlyList Groups => groups; + public IBindableList Groups => groups; - private readonly SortedList groups = new SortedList(Comparer.Default); + private readonly BindableList groups = new BindableList(); /// /// All timing points. @@ -294,5 +295,19 @@ namespace osu.Game.Beatmaps.ControlPoints samplePoints.Clear(); effectPoints.Clear(); } + + public ControlPointGroup CreateGroup(double time) + { + var newGroup = new ControlPointGroup(time); + + int i = groups.BinarySearch(newGroup); + if (i < 0) i = ~i; + + groups.Insert(i, newGroup); + + return newGroup; + } + + public void RemoveGroup(ControlPointGroup group) => groups.Remove(group); } } diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 9c11443199..729e631d87 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -203,7 +203,7 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - selectedGroup.BindValueChanged(group => { Selected = controlGroup == group.NewValue; }); + selectedGroup.BindValueChanged(group => { Selected = controlGroup == group.NewValue; }, true); } private bool selected; diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index a9fd038829..2b1c8f8497 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -52,12 +52,18 @@ namespace osu.Game.Screens.Edit.Timing public class ControlPointList : CompositeDrawable { private OsuButton deleteButton; + private ControlPointTable table; + + private IBindableList controlGroups; + + [Resolved] + protected IFrameBasedClock EditorClock { get; private set; } [Resolved] protected IBindable Beatmap { get; private set; } [Resolved] - private Bindable selectedPoints { get; set; } + private Bindable selectedGroup { get; set; } [BackgroundDependencyLoader] private void load(OsuColour colours) @@ -74,10 +80,7 @@ namespace osu.Game.Screens.Edit.Timing new OsuScrollContainer { RelativeSizeAxes = Axes.Both, - Child = new ControlPointTable - { - ControlGroups = Beatmap.Value.Beatmap.ControlPointInfo.Groups - } + Child = table = new ControlPointTable(), }, new FillFlowContainer { @@ -114,15 +117,27 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - selectedPoints.BindValueChanged(selected => { deleteButton.Enabled.Value = selected.NewValue != null; }, true); + selectedGroup.BindValueChanged(selected => { deleteButton.Enabled.Value = selected.NewValue != null; }, true); + + controlGroups = Beatmap.Value.Beatmap.ControlPointInfo.Groups.GetBoundCopy(); + controlGroups.ItemsAdded += _ => createContent(); + controlGroups.ItemsRemoved += _ => createContent(); + createContent(); } + private void createContent() => table.ControlGroups = controlGroups; + private void delete() { + if (selectedGroup.Value == null) + return; + + Beatmap.Value.Beatmap.ControlPointInfo.RemoveGroup(selectedGroup.Value); } private void addNew() { + selectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.CreateGroup(EditorClock.CurrentTime); } } } From 81b5d7b79fd668463a7d1c616a66da5a77ec4dee Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Oct 2019 16:30:05 +0900 Subject: [PATCH 041/177] Select another group after deleting selected --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 2b1c8f8497..2e9164c60f 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Linq; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -57,7 +58,7 @@ namespace osu.Game.Screens.Edit.Timing private IBindableList controlGroups; [Resolved] - protected IFrameBasedClock EditorClock { get; private set; } + private IFrameBasedClock clock { get; set; } [Resolved] protected IBindable Beatmap { get; private set; } @@ -133,11 +134,13 @@ namespace osu.Game.Screens.Edit.Timing return; Beatmap.Value.Beatmap.ControlPointInfo.RemoveGroup(selectedGroup.Value); + + selectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.Groups.FirstOrDefault(g => g.Time >= clock.CurrentTime); } private void addNew() { - selectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.CreateGroup(EditorClock.CurrentTime); + selectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.CreateGroup(clock.CurrentTime); } } } From 0179586f7811a1d71209d61cb676a0dd856cb7d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 27 Oct 2019 16:31:23 +0900 Subject: [PATCH 042/177] Disallow inserting a group if one already exists with the current time value --- osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 4f83e14eaf..6418ef984b 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -301,9 +301,11 @@ namespace osu.Game.Beatmaps.ControlPoints var newGroup = new ControlPointGroup(time); int i = groups.BinarySearch(newGroup); - if (i < 0) i = ~i; - groups.Insert(i, newGroup); + if (i > 0) + return groups[i]; + + groups.Insert(~i, newGroup); return newGroup; } From 29e20bc8d2796d8ac5cb3f8a53dc684ec67263d0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Oct 2019 10:45:11 +0900 Subject: [PATCH 043/177] Add xmldoc and combine GroupAt / CreateGroup --- .../ControlPoints/ControlPointInfo.cs | 174 ++++++++---------- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 2 +- 2 files changed, 79 insertions(+), 97 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs index 6418ef984b..c3e2b469ae 100644 --- a/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs +++ b/osu.Game/Beatmaps/ControlPoints/ControlPointInfo.cs @@ -14,7 +14,7 @@ namespace osu.Game.Beatmaps.ControlPoints public class ControlPointInfo { /// - /// Control point groups. + /// All control points grouped by time. /// [JsonProperty] public IBindableList Groups => groups; @@ -86,28 +86,6 @@ namespace osu.Game.Beatmaps.ControlPoints /// The timing control point. public TimingControlPoint TimingPointAt(double time) => binarySearchWithFallback(TimingPoints, time, TimingPoints.Count > 0 ? TimingPoints[0] : null); - /// - /// Finds the closest of the same type as that is active at . - /// - /// The time to find the timing control point at. - /// A reference point to infer type. - /// The timing control point. - public ControlPoint SimilarPointAt(double time, ControlPoint referencePoint) - { - switch (referencePoint) - { - case TimingControlPoint _: return binarySearch(TimingPoints, time); - - case EffectControlPoint _: return binarySearch(EffectPoints, time); - - case SampleControlPoint _: return binarySearch(SamplePoints, time); - - case DifficultyControlPoint _: return binarySearch(DifficultyPoints, time); - } - - return null; - } - /// /// Finds the maximum BPM represented by any timing control point. /// @@ -129,6 +107,62 @@ namespace osu.Game.Beatmaps.ControlPoints public double BPMMode => 60000 / (TimingPoints.GroupBy(c => c.BeatLength).OrderByDescending(grp => grp.Count()).FirstOrDefault()?.FirstOrDefault() ?? new TimingControlPoint()).BeatLength; + /// + /// Remove all s and return to a pristine state. + /// + public void Clear() + { + groups.Clear(); + timingPoints.Clear(); + difficultyPoints.Clear(); + samplePoints.Clear(); + effectPoints.Clear(); + } + + /// + /// Add a new . Note that the provided control point may not be added if the correct state is already present at the provided time. + /// + /// The time at which the control point should be added. + /// The control point to add. + /// Whether the control point was added. + public bool Add(double time, ControlPoint controlPoint) + { + if (checkAlreadyExisting(time, controlPoint)) + return false; + + GroupAt(time, true).Add(controlPoint); + return true; + } + + public ControlPointGroup GroupAt(double time, bool addIfNotExisting = false) + { + var newGroup = new ControlPointGroup(time); + + int i = groups.BinarySearch(newGroup); + + if (i >= 0) + return groups[i]; + + if (addIfNotExisting) + { + newGroup.ItemAdded += groupItemAdded; + newGroup.ItemRemoved += groupItemRemoved; + + groups.Insert(~i, newGroup); + return newGroup; + } + + return null; + } + + public void RemoveGroup(ControlPointGroup group) + { + group.ItemAdded -= groupItemAdded; + group.ItemRemoved -= groupItemRemoved; + + groups.Remove(group); + } + /// /// Binary searches one of the control point lists to find the active control point at . /// Includes logic for returning a specific point when no matching point is found. @@ -183,33 +217,6 @@ namespace osu.Game.Beatmaps.ControlPoints return list[l - 1]; } - public void Add(double time, ControlPoint newPoint, bool force = false) - { - if (!force && checkAlreadyExisting(time, newPoint)) - return; - - GroupAt(time, true).Add(newPoint); - } - - public ControlPointGroup GroupAt(double time, bool createIfNotExisting = false) - { - var existing = Groups.FirstOrDefault(g => g.Time == time); - - if (existing != null) - return existing; - - if (createIfNotExisting) - { - var newGroup = new ControlPointGroup(time); - newGroup.ItemAdded += groupItemAdded; - newGroup.ItemRemoved += groupItemRemoved; - groups.Add(newGroup); - return newGroup; - } - - return null; - } - /// /// Check whether should be added. /// @@ -243,31 +250,9 @@ namespace osu.Game.Beatmaps.ControlPoints return existing?.EquivalentTo(newPoint) == true; } - private void groupItemRemoved(ControlPoint obj) + private void groupItemAdded(ControlPoint controlPoint) { - switch (obj) - { - case TimingControlPoint typed: - timingPoints.Remove(typed); - break; - - case EffectControlPoint typed: - effectPoints.Remove(typed); - break; - - case SampleControlPoint typed: - samplePoints.Remove(typed); - break; - - case DifficultyControlPoint typed: - difficultyPoints.Remove(typed); - break; - } - } - - private void groupItemAdded(ControlPoint obj) - { - switch (obj) + switch (controlPoint) { case TimingControlPoint typed: timingPoints.Add(typed); @@ -287,29 +272,26 @@ namespace osu.Game.Beatmaps.ControlPoints } } - public void Clear() + private void groupItemRemoved(ControlPoint controlPoint) { - groups.Clear(); - timingPoints.Clear(); - difficultyPoints.Clear(); - samplePoints.Clear(); - effectPoints.Clear(); + switch (controlPoint) + { + case TimingControlPoint typed: + timingPoints.Remove(typed); + break; + + case EffectControlPoint typed: + effectPoints.Remove(typed); + break; + + case SampleControlPoint typed: + samplePoints.Remove(typed); + break; + + case DifficultyControlPoint typed: + difficultyPoints.Remove(typed); + break; + } } - - public ControlPointGroup CreateGroup(double time) - { - var newGroup = new ControlPointGroup(time); - - int i = groups.BinarySearch(newGroup); - - if (i > 0) - return groups[i]; - - groups.Insert(~i, newGroup); - - return newGroup; - } - - public void RemoveGroup(ControlPointGroup group) => groups.Remove(group); } } diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 2e9164c60f..6ae01e056f 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -140,7 +140,7 @@ namespace osu.Game.Screens.Edit.Timing private void addNew() { - selectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.CreateGroup(clock.CurrentTime); + selectedGroup.Value = Beatmap.Value.Beatmap.ControlPointInfo.GroupAt(clock.CurrentTime, true); } } } From cd4b7c04e9a18668680016c9b45ee1d4c3f1976d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Oct 2019 11:34:41 +0900 Subject: [PATCH 044/177] Add test coverage --- .../NonVisual/ControlPointInfoTest.cs | 227 ++++++++++++++++++ 1 file changed, 227 insertions(+) create mode 100644 osu.Game.Tests/NonVisual/ControlPointInfoTest.cs diff --git a/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs new file mode 100644 index 0000000000..a51b90851c --- /dev/null +++ b/osu.Game.Tests/NonVisual/ControlPointInfoTest.cs @@ -0,0 +1,227 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System.Linq; +using NUnit.Framework; +using osu.Game.Beatmaps.ControlPoints; + +namespace osu.Game.Tests.NonVisual +{ + [TestFixture] + public class ControlPointInfoTest + { + [Test] + public void TestAdd() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new TimingControlPoint()); + cpi.Add(1000, new TimingControlPoint { BeatLength = 500 }); + + Assert.That(cpi.Groups.Count, Is.EqualTo(2)); + Assert.That(cpi.TimingPoints.Count, Is.EqualTo(2)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(2)); + } + + [Test] + public void TestAddRedundantTiming() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new TimingControlPoint()); // is *not* redundant, special exception for first timing point. + cpi.Add(1000, new TimingControlPoint()); // is redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + Assert.That(cpi.TimingPoints.Count, Is.EqualTo(1)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1)); + } + + [Test] + public void TestAddRedundantDifficulty() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new DifficultyControlPoint()); // is redundant + cpi.Add(1000, new DifficultyControlPoint()); // is redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(0)); + Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0)); + + cpi.Add(1000, new DifficultyControlPoint { SpeedMultiplier = 2 }); // is not redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1)); + } + + [Test] + public void TestAddRedundantSample() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new SampleControlPoint()); // is redundant + cpi.Add(1000, new SampleControlPoint()); // is redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(0)); + Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0)); + + cpi.Add(1000, new SampleControlPoint { SampleVolume = 50 }); // is not redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + Assert.That(cpi.SamplePoints.Count, Is.EqualTo(1)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1)); + } + + [Test] + public void TestAddRedundantEffect() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new EffectControlPoint()); // is redundant + cpi.Add(1000, new EffectControlPoint()); // is redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(0)); + Assert.That(cpi.TimingPoints.Count, Is.EqualTo(0)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(0)); + + cpi.Add(1000, new EffectControlPoint { KiaiMode = true }); // is not redundant + + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + Assert.That(cpi.EffectPoints.Count, Is.EqualTo(1)); + Assert.That(cpi.AllControlPoints.Count(), Is.EqualTo(1)); + } + + [Test] + public void TestAddGroup() + { + var cpi = new ControlPointInfo(); + + var group = cpi.GroupAt(1000, true); + var group2 = cpi.GroupAt(1000, true); + + Assert.That(group, Is.EqualTo(group2)); + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + } + + [Test] + public void TestGroupAtLookupOnly() + { + var cpi = new ControlPointInfo(); + + var group = cpi.GroupAt(5000, true); + Assert.That(group, Is.Not.Null); + + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + Assert.That(cpi.GroupAt(1000), Is.Null); + Assert.That(cpi.GroupAt(5000), Is.Not.Null); + } + + [Test] + public void TestAddRemoveGroup() + { + var cpi = new ControlPointInfo(); + + var group = cpi.GroupAt(1000, true); + + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + + cpi.RemoveGroup(group); + + Assert.That(cpi.Groups.Count, Is.EqualTo(0)); + } + + [Test] + public void TestAddControlPointToGroup() + { + var cpi = new ControlPointInfo(); + + var group = cpi.GroupAt(1000, true); + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + + // usually redundant, but adding to group forces it to be added + group.Add(new DifficultyControlPoint()); + + Assert.That(group.ControlPoints.Count, Is.EqualTo(1)); + Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1)); + } + + [Test] + public void TestAddDuplicateControlPointToGroup() + { + var cpi = new ControlPointInfo(); + + var group = cpi.GroupAt(1000, true); + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + + group.Add(new DifficultyControlPoint()); + group.Add(new DifficultyControlPoint { SpeedMultiplier = 2 }); + + Assert.That(group.ControlPoints.Count, Is.EqualTo(1)); + Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(1)); + Assert.That(cpi.DifficultyPoints.First().SpeedMultiplier, Is.EqualTo(2)); + } + + [Test] + public void TestRemoveControlPointFromGroup() + { + var cpi = new ControlPointInfo(); + + var group = cpi.GroupAt(1000, true); + Assert.That(cpi.Groups.Count, Is.EqualTo(1)); + + var difficultyPoint = new DifficultyControlPoint(); + + group.Add(difficultyPoint); + group.Remove(difficultyPoint); + + Assert.That(group.ControlPoints.Count, Is.EqualTo(0)); + Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0)); + Assert.That(cpi.AllControlPoints.Count, Is.EqualTo(0)); + } + + [Test] + public void TestOrdering() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new TimingControlPoint()); + cpi.Add(1000, new TimingControlPoint { BeatLength = 500 }); + cpi.Add(10000, new TimingControlPoint { BeatLength = 200 }); + cpi.Add(5000, new TimingControlPoint { BeatLength = 100 }); + cpi.Add(3000, new DifficultyControlPoint { SpeedMultiplier = 2 }); + cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SpeedMultiplier = 4 }); + cpi.GroupAt(1000).Add(new SampleControlPoint { SampleVolume = 0 }); + cpi.GroupAt(8000, true).Add(new EffectControlPoint { KiaiMode = true }); + + Assert.That(cpi.AllControlPoints.Count, Is.EqualTo(8)); + + Assert.That(cpi.Groups, Is.Ordered.Ascending.By(nameof(ControlPointGroup.Time))); + + Assert.That(cpi.AllControlPoints, Is.Ordered.Ascending.By(nameof(ControlPoint.Time))); + Assert.That(cpi.TimingPoints, Is.Ordered.Ascending.By(nameof(ControlPoint.Time))); + } + + [Test] + public void TestClear() + { + var cpi = new ControlPointInfo(); + + cpi.Add(0, new TimingControlPoint()); + cpi.Add(1000, new TimingControlPoint { BeatLength = 500 }); + cpi.Add(10000, new TimingControlPoint { BeatLength = 200 }); + cpi.Add(5000, new TimingControlPoint { BeatLength = 100 }); + cpi.Add(3000, new DifficultyControlPoint { SpeedMultiplier = 2 }); + cpi.GroupAt(7000, true).Add(new DifficultyControlPoint { SpeedMultiplier = 4 }); + cpi.GroupAt(1000).Add(new SampleControlPoint { SampleVolume = 0 }); + cpi.GroupAt(8000, true).Add(new EffectControlPoint { KiaiMode = true }); + + cpi.Clear(); + + Assert.That(cpi.Groups.Count, Is.EqualTo(0)); + Assert.That(cpi.DifficultyPoints.Count, Is.EqualTo(0)); + Assert.That(cpi.AllControlPoints.Count, Is.EqualTo(0)); + } + } +} From 43ad4a3a3c978b4305a48576ec3769f91d7f929e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Oct 2019 12:31:38 +0900 Subject: [PATCH 045/177] Tidy up string output --- osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs | 5 +++++ osu.Game/Screens/Edit/Timing/DifficultySection.cs | 2 +- osu.Game/Screens/Edit/Timing/EffectSection.cs | 4 ++-- osu.Game/Screens/Edit/Timing/SampleSection.cs | 4 ++-- osu.Game/Screens/Edit/Timing/TimingSection.cs | 2 +- 5 files changed, 11 insertions(+), 6 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index f8c84c79dd..e3451892bf 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -24,6 +24,11 @@ namespace osu.Game.Beatmaps.ControlPoints set => beatLength = MathHelper.Clamp(value, 6, 60000); } + /// + /// The BPM at this control point. + /// + public double BPM => 60000 / BeatLength; + private double beatLength = DEFAULT_BEAT_LENGTH; public override bool EquivalentTo(ControlPoint other) => diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs index 150c11f5ee..df61300451 100644 --- a/osu.Game/Screens/Edit/Timing/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Timing/DifficultySection.cs @@ -21,7 +21,7 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier}"; }); + ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier::0.##}x"; }); } protected override DifficultyControlPoint CreatePoint() diff --git a/osu.Game/Screens/Edit/Timing/EffectSection.cs b/osu.Game/Screens/Edit/Timing/EffectSection.cs index ff8817147a..57bee44177 100644 --- a/osu.Game/Screens/Edit/Timing/EffectSection.cs +++ b/osu.Game/Screens/Edit/Timing/EffectSection.cs @@ -25,8 +25,8 @@ namespace osu.Game.Screens.Edit.Timing ControlPoint.BindValueChanged(point => { - kiai.Text = $"Kiai: {point.NewValue?.KiaiMode}"; - omitBarLine.Text = $"Skip Bar Line: {point.NewValue?.OmitFirstBarLine}"; + kiai.Text = $"Kiai: {(point.NewValue?.KiaiMode == true ? "on" : "off")}"; + omitBarLine.Text = $"Skip Bar Line: {(point.NewValue?.OmitFirstBarLine == true ? "on" : "off")}"; }); } diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs index 0d6bc74057..d623ad08ae 100644 --- a/osu.Game/Screens/Edit/Timing/SampleSection.cs +++ b/osu.Game/Screens/Edit/Timing/SampleSection.cs @@ -37,8 +37,8 @@ namespace osu.Game.Screens.Edit.Timing ControlPoint.BindValueChanged(point => { bank.Text = $"Bank: {point.NewValue?.SampleBank}"; - volume.Text = $"Volume: {point.NewValue?.SampleVolume}"; + volume.Text = $"Volume: {point.NewValue?.SampleVolume}%"; }); } } -} \ No newline at end of file +} diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index dcbb6f8bbf..4f2cf82a65 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -25,7 +25,7 @@ namespace osu.Game.Screens.Edit.Timing ControlPoint.BindValueChanged(point => { - bpm.Text = $"BPM: {point.NewValue?.BeatLength}"; + bpm.Text = $"BPM: {point.NewValue?.BPM:0.##}"; timeSignature.Text = $"Signature: {point.NewValue?.TimeSignature}"; }); } From ee5591d7d502244c61ca8cb2ee515eccc9403086 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Oct 2019 12:42:17 +0900 Subject: [PATCH 046/177] Add missing license headers --- osu.Game/Screens/Edit/Timing/DifficultySection.cs | 3 +++ osu.Game/Screens/Edit/Timing/EffectSection.cs | 3 +++ osu.Game/Screens/Edit/Timing/SampleSection.cs | 3 +++ osu.Game/Screens/Edit/Timing/TimingSection.cs | 3 +++ 4 files changed, 12 insertions(+) diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs index df61300451..bd363c3158 100644 --- a/osu.Game/Screens/Edit/Timing/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Timing/DifficultySection.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Framework.Allocation; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; diff --git a/osu.Game/Screens/Edit/Timing/EffectSection.cs b/osu.Game/Screens/Edit/Timing/EffectSection.cs index 57bee44177..bafdfd0b0d 100644 --- a/osu.Game/Screens/Edit/Timing/EffectSection.cs +++ b/osu.Game/Screens/Edit/Timing/EffectSection.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Framework.Allocation; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs index d623ad08ae..0477ad4e78 100644 --- a/osu.Game/Screens/Edit/Timing/SampleSection.cs +++ b/osu.Game/Screens/Edit/Timing/SampleSection.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Framework.Allocation; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 4f2cf82a65..8609da4c4d 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -1,3 +1,6 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + using osu.Framework.Allocation; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; From 9acdcc912924ecc83df4bddd80ff59347cdfef94 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Oct 2019 14:44:45 +0900 Subject: [PATCH 047/177] Make all control point attributes bindable Properties are left intact for compatibility reasons. --- .../ControlPoints/DifficultyControlPoint.cs | 19 +++++++---- .../ControlPoints/EffectControlPoint.cs | 26 +++++++++++++-- .../ControlPoints/SampleControlPoint.cs | 27 ++++++++++++++-- .../ControlPoints/TimingControlPoint.cs | 32 ++++++++++++++----- ...egacyDifficultyCalculatorBeatmapDecoder.cs | 6 +++- 5 files changed, 90 insertions(+), 20 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index 42651fd0ca..7726eb0245 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -1,24 +1,31 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osuTK; +using osu.Framework.Bindables; namespace osu.Game.Beatmaps.ControlPoints { public class DifficultyControlPoint : ControlPoint { + /// + /// The speed multiplier at this control point. + /// + public readonly BindableDouble SpeedMultiplierBindable = new BindableDouble(1) + { + MinValue = 0.1, + MaxValue = 10 + }; + /// /// The speed multiplier at this control point. /// public double SpeedMultiplier { - get => speedMultiplier; - set => speedMultiplier = MathHelper.Clamp(value, 0.1, 10); + get => SpeedMultiplierBindable.Value; + set => SpeedMultiplierBindable.Value = value; } - private double speedMultiplier = 1; - public override bool EquivalentTo(ControlPoint other) => - other is DifficultyControlPoint otherTyped && otherTyped.SpeedMultiplier.Equals(speedMultiplier); + other is DifficultyControlPoint otherTyped && otherTyped.SpeedMultiplier.Equals(SpeedMultiplier); } } diff --git a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs index 928f2a51ad..369b93ff3d 100644 --- a/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/EffectControlPoint.cs @@ -1,19 +1,39 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; + namespace osu.Game.Beatmaps.ControlPoints { public class EffectControlPoint : ControlPoint { /// - /// Whether this control point enables Kiai mode. + /// Whether the first bar line of this control point is ignored. /// - public bool KiaiMode; + public readonly BindableBool OmitFirstBarLineBindable = new BindableBool(); /// /// Whether the first bar line of this control point is ignored. /// - public bool OmitFirstBarLine; + public bool OmitFirstBarLine + { + get => OmitFirstBarLineBindable.Value; + set => OmitFirstBarLineBindable.Value = value; + } + + /// + /// Whether this control point enables Kiai mode. + /// + public readonly BindableBool KiaiModeBindable = new BindableBool(); + + /// + /// Whether this control point enables Kiai mode. + /// + public bool KiaiMode + { + get => KiaiModeBindable.Value; + set => KiaiModeBindable.Value = value; + } public override bool EquivalentTo(ControlPoint other) => other is EffectControlPoint otherTyped && diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index 07f5aa6c90..37607c716e 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using osu.Framework.Bindables; using osu.Game.Audio; namespace osu.Game.Beatmaps.ControlPoints @@ -12,12 +13,34 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The default sample bank at this control point. /// - public string SampleBank = DEFAULT_BANK; + public readonly Bindable SampleBankBindable = new Bindable(DEFAULT_BANK); + + /// + /// The speed multiplier at this control point. + /// + public string SampleBank + { + get => SampleBankBindable.Value; + set => SampleBankBindable.Value = value; + } + + /// + /// The default sample bank at this control point. + /// + public readonly BindableInt SampleVolumeBindable = new BindableInt(100) + { + MinValue = 0, + MaxValue = 100 + }; /// /// The default sample volume at this control point. /// - public int SampleVolume = 100; + public int SampleVolume + { + get => SampleVolumeBindable.Value; + set => SampleVolumeBindable.Value = value; + } /// /// Create a SampleInfo based on the sample settings in this control point. diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index f8c84c79dd..d25d08c5bd 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -1,7 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osuTK; +using osu.Framework.Bindables; using osu.Game.Beatmaps.Timing; namespace osu.Game.Beatmaps.ControlPoints @@ -11,23 +11,39 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time signature at this control point. /// - public TimeSignatures TimeSignature = TimeSignatures.SimpleQuadruple; + public readonly Bindable TimeSignatureBindable = new Bindable(TimeSignatures.SimpleQuadruple); + + /// + /// The time signature at this control point. + /// + public TimeSignatures TimeSignature + { + get => TimeSignatureBindable.Value; + set => TimeSignatureBindable.Value = value; + } public const double DEFAULT_BEAT_LENGTH = 1000; /// /// The beat length at this control point. /// - public virtual double BeatLength + public readonly BindableDouble BeatLengthBindable = new BindableDouble(DEFAULT_BEAT_LENGTH) { - get => beatLength; - set => beatLength = MathHelper.Clamp(value, 6, 60000); - } + MinValue = 6, + MaxValue = 60000 + }; - private double beatLength = DEFAULT_BEAT_LENGTH; + /// + /// The beat length at this control point. + /// + public double BeatLength + { + get => BeatLengthBindable.Value; + set => BeatLengthBindable.Value = value; + } public override bool EquivalentTo(ControlPoint other) => other is TimingControlPoint otherTyped - && TimeSignature == otherTyped.TimeSignature && beatLength.Equals(otherTyped.beatLength); + && TimeSignature == otherTyped.TimeSignature && BeatLength.Equals(otherTyped.BeatLength); } } diff --git a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs index 238187bf8f..a16d391b99 100644 --- a/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs +++ b/osu.Game/Beatmaps/Formats/LegacyDifficultyCalculatorBeatmapDecoder.cs @@ -32,7 +32,11 @@ namespace osu.Game.Beatmaps.Formats private class LegacyDifficultyCalculatorControlPoint : TimingControlPoint { - public override double BeatLength { get; set; } = DEFAULT_BEAT_LENGTH; + public LegacyDifficultyCalculatorControlPoint() + { + BeatLengthBindable.MinValue = double.MinValue; + BeatLengthBindable.MaxValue = double.MaxValue; + } } } } From d33b31f0c5100850be2ae873ca46ab4fdb71fffd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Oct 2019 15:33:08 +0900 Subject: [PATCH 048/177] Expose Current bindable in LabelledComponents Adds a `LabelledDrawable` class for usages where bindables are not present. --- .../TestSceneLabelledComponent.cs | 14 +- .../UserInterface/TestSceneLabelledTextBox.cs | 3 +- osu.Game.Tournament/Screens/SetupScreen.cs | 2 +- .../UserInterfaceV2/LabelledComponent.cs | 124 ++-------------- .../UserInterfaceV2/LabelledDrawable.cs | 132 ++++++++++++++++++ .../UserInterfaceV2/LabelledSwitchButton.cs | 2 +- .../UserInterfaceV2/LabelledTextBox.cs | 2 +- 7 files changed, 151 insertions(+), 128 deletions(-) create mode 100644 osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs index 700adad9cb..8179f92ffc 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledComponent.cs @@ -11,7 +11,7 @@ using osuTK.Graphics; namespace osu.Game.Tests.Visual.UserInterface { - public class TestSceneLabelledComponent : OsuTestScene + public class TestSceneLabelledDrawable : OsuTestScene { [TestCase(false)] [TestCase(true)] @@ -25,7 +25,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("create component", () => { - LabelledComponent component; + LabelledDrawable component; Child = new Container { @@ -33,7 +33,7 @@ namespace osu.Game.Tests.Visual.UserInterface Origin = Anchor.Centre, Width = 500, AutoSizeAxes = Axes.Y, - Child = component = padded ? (LabelledComponent)new PaddedLabelledComponent() : new NonPaddedLabelledComponent(), + Child = component = padded ? (LabelledDrawable)new PaddedLabelledDrawable() : new NonPaddedLabelledDrawable(), }; component.Label = "a sample component"; @@ -41,9 +41,9 @@ namespace osu.Game.Tests.Visual.UserInterface }); } - private class PaddedLabelledComponent : LabelledComponent + private class PaddedLabelledDrawable : LabelledDrawable { - public PaddedLabelledComponent() + public PaddedLabelledDrawable() : base(true) { } @@ -57,9 +57,9 @@ namespace osu.Game.Tests.Visual.UserInterface }; } - private class NonPaddedLabelledComponent : LabelledComponent + private class NonPaddedLabelledDrawable : LabelledDrawable { - public NonPaddedLabelledComponent() + public NonPaddedLabelledDrawable() : base(false) { } diff --git a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs index 53a2bfabbc..8208b55952 100644 --- a/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs +++ b/osu.Game.Tests/Visual/UserInterface/TestSceneLabelledTextBox.cs @@ -7,7 +7,6 @@ using NUnit.Framework; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterfaceV2; namespace osu.Game.Tests.Visual.UserInterface @@ -28,7 +27,7 @@ namespace osu.Game.Tests.Visual.UserInterface { AddStep("create component", () => { - LabelledComponent component; + LabelledTextBox component; Child = new Container { diff --git a/osu.Game.Tournament/Screens/SetupScreen.cs b/osu.Game.Tournament/Screens/SetupScreen.cs index 091a837745..a67daa2756 100644 --- a/osu.Game.Tournament/Screens/SetupScreen.cs +++ b/osu.Game.Tournament/Screens/SetupScreen.cs @@ -89,7 +89,7 @@ namespace osu.Game.Tournament.Screens }; } - private class ActionableInfo : LabelledComponent + private class ActionableInfo : LabelledDrawable { private OsuButton button; diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs index 2e659825b7..1819b36667 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledComponent.cs @@ -1,132 +1,24 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; -using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Shapes; -using osu.Game.Graphics.Containers; -using osuTK; +using osu.Framework.Graphics.UserInterface; namespace osu.Game.Graphics.UserInterfaceV2 { - public abstract class LabelledComponent : CompositeDrawable - where T : Drawable + public abstract class LabelledComponent : LabelledDrawable, IHasCurrentValue + where T : Drawable, IHasCurrentValue { - protected const float CONTENT_PADDING_VERTICAL = 10; - protected const float CONTENT_PADDING_HORIZONTAL = 15; - protected const float CORNER_RADIUS = 15; - - /// - /// The component that is being displayed. - /// - protected readonly T Component; - - private readonly OsuTextFlowContainer labelText; - private readonly OsuTextFlowContainer descriptionText; - - /// - /// Creates a new . - /// - /// Whether the component should be padded or should be expanded to the bounds of this . protected LabelledComponent(bool padded) + : base(padded) { - RelativeSizeAxes = Axes.X; - AutoSizeAxes = Axes.Y; - - CornerRadius = CORNER_RADIUS; - Masking = true; - - InternalChildren = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = OsuColour.FromHex("1c2125"), - }, - new FillFlowContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Direction = FillDirection.Vertical, - Padding = padded - ? new MarginPadding { Horizontal = CONTENT_PADDING_HORIZONTAL, Vertical = CONTENT_PADDING_VERTICAL } - : new MarginPadding { Left = CONTENT_PADDING_HORIZONTAL }, - Spacing = new Vector2(0, 12), - Children = new Drawable[] - { - new GridContainer - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Content = new[] - { - new Drawable[] - { - labelText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(weight: FontWeight.Bold)) - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.CentreLeft, - AutoSizeAxes = Axes.Both, - Padding = new MarginPadding { Right = 20 } - }, - new Container - { - Anchor = Anchor.CentreRight, - Origin = Anchor.CentreRight, - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Child = Component = CreateComponent().With(d => - { - d.Anchor = Anchor.CentreRight; - d.Origin = Anchor.CentreRight; - }) - } - }, - }, - RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, - ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } - }, - descriptionText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold, italics: true)) - { - RelativeSizeAxes = Axes.X, - AutoSizeAxes = Axes.Y, - Padding = new MarginPadding { Bottom = padded ? 0 : CONTENT_PADDING_VERTICAL }, - Alpha = 0, - } - } - } - }; } - [BackgroundDependencyLoader] - private void load(OsuColour osuColour) + public Bindable Current { - descriptionText.Colour = osuColour.Yellow; + get => Component.Current; + set => Component.Current = value; } - - public string Label - { - set => labelText.Text = value; - } - - public string Description - { - set - { - descriptionText.Text = value; - - if (!string.IsNullOrEmpty(value)) - descriptionText.Show(); - else - descriptionText.Hide(); - } - } - - /// - /// Creates the component that should be displayed. - /// - /// The component. - protected abstract T CreateComponent(); } } diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs new file mode 100644 index 0000000000..f44bd72aee --- /dev/null +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledDrawable.cs @@ -0,0 +1,132 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Shapes; +using osu.Game.Graphics.Containers; +using osuTK; + +namespace osu.Game.Graphics.UserInterfaceV2 +{ + public abstract class LabelledDrawable : CompositeDrawable + where T : Drawable + { + protected const float CONTENT_PADDING_VERTICAL = 10; + protected const float CONTENT_PADDING_HORIZONTAL = 15; + protected const float CORNER_RADIUS = 15; + + /// + /// The component that is being displayed. + /// + protected readonly T Component; + + private readonly OsuTextFlowContainer labelText; + private readonly OsuTextFlowContainer descriptionText; + + /// + /// Creates a new . + /// + /// Whether the component should be padded or should be expanded to the bounds of this . + protected LabelledDrawable(bool padded) + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + CornerRadius = CORNER_RADIUS; + Masking = true; + + InternalChildren = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = OsuColour.FromHex("1c2125"), + }, + new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Direction = FillDirection.Vertical, + Padding = padded + ? new MarginPadding { Horizontal = CONTENT_PADDING_HORIZONTAL, Vertical = CONTENT_PADDING_VERTICAL } + : new MarginPadding { Left = CONTENT_PADDING_HORIZONTAL }, + Spacing = new Vector2(0, 12), + Children = new Drawable[] + { + new GridContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Content = new[] + { + new Drawable[] + { + labelText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(weight: FontWeight.Bold)) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + AutoSizeAxes = Axes.Both, + Padding = new MarginPadding { Right = 20 } + }, + new Container + { + Anchor = Anchor.CentreRight, + Origin = Anchor.CentreRight, + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Child = Component = CreateComponent().With(d => + { + d.Anchor = Anchor.CentreRight; + d.Origin = Anchor.CentreRight; + }) + } + }, + }, + RowDimensions = new[] { new Dimension(GridSizeMode.AutoSize) }, + ColumnDimensions = new[] { new Dimension(GridSizeMode.AutoSize) } + }, + descriptionText = new OsuTextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold, italics: true)) + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Padding = new MarginPadding { Bottom = padded ? 0 : CONTENT_PADDING_VERTICAL }, + Alpha = 0, + } + } + } + }; + } + + [BackgroundDependencyLoader] + private void load(OsuColour osuColour) + { + descriptionText.Colour = osuColour.Yellow; + } + + public string Label + { + set => labelText.Text = value; + } + + public string Description + { + set + { + descriptionText.Text = value; + + if (!string.IsNullOrEmpty(value)) + descriptionText.Show(); + else + descriptionText.Hide(); + } + } + + /// + /// Creates the component that should be displayed. + /// + /// The component. + protected abstract T CreateComponent(); + } +} diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledSwitchButton.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledSwitchButton.cs index c973f1d13e..c374d80830 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledSwitchButton.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledSwitchButton.cs @@ -3,7 +3,7 @@ namespace osu.Game.Graphics.UserInterfaceV2 { - public class LabelledSwitchButton : LabelledComponent + public class LabelledSwitchButton : LabelledComponent { public LabelledSwitchButton() : base(true) diff --git a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs index 50d2a14482..2cbe095d0b 100644 --- a/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs +++ b/osu.Game/Graphics/UserInterfaceV2/LabelledTextBox.cs @@ -8,7 +8,7 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Graphics.UserInterfaceV2 { - public class LabelledTextBox : LabelledComponent + public class LabelledTextBox : LabelledComponent { public event TextBox.OnCommitHandler OnCommit; From 0a11cbf656aef28930ad01e1306ed907958d1832 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Oct 2019 16:20:33 +0900 Subject: [PATCH 049/177] Make OsuButton correctly block hover events --- osu.Game/Graphics/UserInterface/OsuButton.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index c1810800a0..4124d2ad58 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -59,7 +59,7 @@ namespace osu.Game.Graphics.UserInterface protected override bool OnHover(HoverEvent e) { hover.FadeIn(200); - return base.OnHover(e); + return true; } protected override void OnHoverLost(HoverLostEvent e) From 9c3e54909ca56882d230d4b4ebbd39d36fda96bb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Oct 2019 16:20:54 +0900 Subject: [PATCH 050/177] Ensure tooltips of RowAttributes are up-to-date --- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 8 ++++---- osu.Game/Screens/Edit/Timing/RowAttribute.cs | 7 ++++--- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 729e631d87..96e3ab48f2 100644 --- a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs +++ b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs @@ -128,17 +128,17 @@ namespace osu.Game.Screens.Edit.Timing switch (controlPoint) { case TimingControlPoint timing: - return new RowAttribute("timing", $"{60000 / timing.BeatLength:n1}bpm {timing.TimeSignature}"); + return new RowAttribute("timing", () => $"{60000 / timing.BeatLength:n1}bpm {timing.TimeSignature}"); case DifficultyControlPoint difficulty: - return new RowAttribute("difficulty", $"{difficulty.SpeedMultiplier:n2}x"); + return new RowAttribute("difficulty", () => $"{difficulty.SpeedMultiplier:n2}x"); case EffectControlPoint effect: - return new RowAttribute("effect", $"{(effect.KiaiMode ? "Kiai " : "")}{(effect.OmitFirstBarLine ? "NoBarLine " : "")}"); + return new RowAttribute("effect", () => $"{(effect.KiaiMode ? "Kiai " : "")}{(effect.OmitFirstBarLine ? "NoBarLine " : "")}"); case SampleControlPoint sample: - return new RowAttribute("sample", $"{sample.SampleBank} {sample.SampleVolume}%"); + return new RowAttribute("sample", () => $"{sample.SampleBank} {sample.SampleVolume}%"); } return null; diff --git a/osu.Game/Screens/Edit/Timing/RowAttribute.cs b/osu.Game/Screens/Edit/Timing/RowAttribute.cs index 8716382142..be8f693683 100644 --- a/osu.Game/Screens/Edit/Timing/RowAttribute.cs +++ b/osu.Game/Screens/Edit/Timing/RowAttribute.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -14,9 +15,9 @@ namespace osu.Game.Screens.Edit.Timing public class RowAttribute : CompositeDrawable, IHasTooltip { private readonly string header; - private readonly string content; + private readonly Func content; - public RowAttribute(string header, string content) + public RowAttribute(string header, Func content) { this.header = header; this.content = content; @@ -54,6 +55,6 @@ namespace osu.Game.Screens.Edit.Timing }; } - public string TooltipText => content; + public string TooltipText => content(); } } From f761eddec754ef130297d12b4e383e9988d55de2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Oct 2019 16:21:14 +0900 Subject: [PATCH 051/177] Add default bindable values --- osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs | 2 ++ osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs | 5 +++-- osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs | 3 ++- 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs index 7726eb0245..8b21098a51 100644 --- a/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/DifficultyControlPoint.cs @@ -12,6 +12,8 @@ namespace osu.Game.Beatmaps.ControlPoints /// public readonly BindableDouble SpeedMultiplierBindable = new BindableDouble(1) { + Precision = 0.1, + Default = 1, MinValue = 0.1, MaxValue = 10 }; diff --git a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs index 37607c716e..42865c686c 100644 --- a/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/SampleControlPoint.cs @@ -13,7 +13,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The default sample bank at this control point. /// - public readonly Bindable SampleBankBindable = new Bindable(DEFAULT_BANK); + public readonly Bindable SampleBankBindable = new Bindable(DEFAULT_BANK) { Default = DEFAULT_BANK }; /// /// The speed multiplier at this control point. @@ -30,7 +30,8 @@ namespace osu.Game.Beatmaps.ControlPoints public readonly BindableInt SampleVolumeBindable = new BindableInt(100) { MinValue = 0, - MaxValue = 100 + MaxValue = 100, + Default = 100 }; /// diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index fb548e60aa..51b3377394 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -11,7 +11,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// /// The time signature at this control point. /// - public readonly Bindable TimeSignatureBindable = new Bindable(TimeSignatures.SimpleQuadruple); + public readonly Bindable TimeSignatureBindable = new Bindable(TimeSignatures.SimpleQuadruple) { Default = TimeSignatures.SimpleQuadruple }; /// /// The time signature at this control point. @@ -29,6 +29,7 @@ namespace osu.Game.Beatmaps.ControlPoints /// public readonly BindableDouble BeatLengthBindable = new BindableDouble(DEFAULT_BEAT_LENGTH) { + Default = DEFAULT_BEAT_LENGTH, MinValue = 6, MaxValue = 60000 }; From 522572eacefefa13ae0f4524402984ce141932f6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 28 Oct 2019 16:21:31 +0900 Subject: [PATCH 052/177] Add ability to adjust all control point attributes --- .../Screens/Edit/Timing/DifficultySection.cs | 20 +++++-- osu.Game/Screens/Edit/Timing/EffectSection.cs | 17 +++--- osu.Game/Screens/Edit/Timing/SampleSection.cs | 28 ++++++--- osu.Game/Screens/Edit/Timing/TimingSection.cs | 59 ++++++++++++++++--- 4 files changed, 97 insertions(+), 27 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs index bd363c3158..d3e1b2a84f 100644 --- a/osu.Game/Screens/Edit/Timing/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Timing/DifficultySection.cs @@ -2,21 +2,27 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Sprites; +using osu.Game.Overlays.Settings; namespace osu.Game.Screens.Edit.Timing { internal class DifficultySection : Section { - private OsuSpriteText multiplier; + private SettingsSlider multiplier; [BackgroundDependencyLoader] private void load() { Flow.AddRange(new[] { - multiplier = new OsuSpriteText(), + multiplier = new SettingsSlider + { + LabelText = "Speed Multiplier", + Bindable = new DifficultyControlPoint().SpeedMultiplierBindable, + RelativeSizeAxes = Axes.X, + } }); } @@ -24,7 +30,13 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier::0.##}x"; }); + ControlPoint.BindValueChanged(point => + { + if (point.NewValue != null) + { + multiplier.Bindable = point.NewValue.SpeedMultiplierBindable; + } + }, true); } protected override DifficultyControlPoint CreatePoint() diff --git a/osu.Game/Screens/Edit/Timing/EffectSection.cs b/osu.Game/Screens/Edit/Timing/EffectSection.cs index bafdfd0b0d..fcd733cf00 100644 --- a/osu.Game/Screens/Edit/Timing/EffectSection.cs +++ b/osu.Game/Screens/Edit/Timing/EffectSection.cs @@ -3,22 +3,22 @@ using osu.Framework.Allocation; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterfaceV2; namespace osu.Game.Screens.Edit.Timing { internal class EffectSection : Section { - private OsuSpriteText kiai; - private OsuSpriteText omitBarLine; + private LabelledSwitchButton kiai; + private LabelledSwitchButton omitBarLine; [BackgroundDependencyLoader] private void load() { Flow.AddRange(new[] { - kiai = new OsuSpriteText(), - omitBarLine = new OsuSpriteText(), + kiai = new LabelledSwitchButton { Label = "Kiai Time" }, + omitBarLine = new LabelledSwitchButton { Label = "Skip Bar Line" }, }); } @@ -28,8 +28,11 @@ namespace osu.Game.Screens.Edit.Timing ControlPoint.BindValueChanged(point => { - kiai.Text = $"Kiai: {(point.NewValue?.KiaiMode == true ? "on" : "off")}"; - omitBarLine.Text = $"Skip Bar Line: {(point.NewValue?.OmitFirstBarLine == true ? "on" : "off")}"; + if (point.NewValue != null) + { + kiai.Current = point.NewValue.KiaiModeBindable; + omitBarLine.Current = point.NewValue.OmitFirstBarLineBindable; + } }); } diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs index 0477ad4e78..816f90d19e 100644 --- a/osu.Game/Screens/Edit/Timing/SampleSection.cs +++ b/osu.Game/Screens/Edit/Timing/SampleSection.cs @@ -2,23 +2,32 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterfaceV2; +using osu.Game.Overlays.Settings; namespace osu.Game.Screens.Edit.Timing { internal class SampleSection : Section { - private OsuSpriteText bank; - private OsuSpriteText volume; + private LabelledTextBox bank; + private SettingsSlider volume; [BackgroundDependencyLoader] private void load() { - Flow.AddRange(new[] + Flow.AddRange(new Drawable[] { - bank = new OsuSpriteText(), - volume = new OsuSpriteText(), + bank = new LabelledTextBox + { + Label = "Bank Name", + }, + volume = new SettingsSlider + { + Bindable = new SampleControlPoint().SampleVolumeBindable, + LabelText = "Volume", + } }); } @@ -39,8 +48,11 @@ namespace osu.Game.Screens.Edit.Timing ControlPoint.BindValueChanged(point => { - bank.Text = $"Bank: {point.NewValue?.SampleBank}"; - volume.Text = $"Volume: {point.NewValue?.SampleVolume}%"; + if (point.NewValue != null) + { + bank.Current = point.NewValue.SampleBankBindable; + volume.Bindable = point.NewValue.SampleVolumeBindable; + } }); } } diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 8609da4c4d..9aecbe9160 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -2,23 +2,33 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Graphics.Sprites; +using osu.Game.Beatmaps.Timing; +using osu.Game.Overlays.Settings; namespace osu.Game.Screens.Edit.Timing { internal class TimingSection : Section { - private OsuSpriteText bpm; - private OsuSpriteText timeSignature; + private SettingsSlider bpm; + private SettingsEnumDropdown timeSignature; [BackgroundDependencyLoader] private void load() { - Flow.AddRange(new[] + Flow.AddRange(new Drawable[] { - bpm = new OsuSpriteText(), - timeSignature = new OsuSpriteText(), + bpm = new BPMSlider + { + Bindable = new TimingControlPoint().BeatLengthBindable, + LabelText = "BPM", + }, + timeSignature = new SettingsEnumDropdown + { + LabelText = "Time Signature" + }, }); } @@ -28,8 +38,11 @@ namespace osu.Game.Screens.Edit.Timing ControlPoint.BindValueChanged(point => { - bpm.Text = $"BPM: {point.NewValue?.BPM:0.##}"; - timeSignature.Text = $"Signature: {point.NewValue?.TimeSignature}"; + if (point.NewValue != null) + { + bpm.Bindable = point.NewValue.BeatLengthBindable; + timeSignature.Bindable = point.NewValue.TimeSignatureBindable; + } }); } @@ -43,5 +56,35 @@ namespace osu.Game.Screens.Edit.Timing TimeSignature = reference.TimeSignature }; } + + private class BPMSlider : SettingsSlider + { + private readonly BindableDouble beatLengthBindable = new BindableDouble(); + + private BindableDouble bpmBindable; + + public override Bindable Bindable + { + get => base.Bindable; + set + { + // incoming will be beatlength + + beatLengthBindable.UnbindBindings(); + beatLengthBindable.BindTo(value); + + base.Bindable = bpmBindable = new BindableDouble(beatLengthToBpm(beatLengthBindable.Value)) + { + MinValue = beatLengthToBpm(beatLengthBindable.MaxValue), + MaxValue = beatLengthToBpm(beatLengthBindable.MinValue), + Default = beatLengthToBpm(beatLengthBindable.Default), + }; + + bpmBindable.BindValueChanged(bpm => beatLengthBindable.Value = beatLengthToBpm(bpm.NewValue)); + } + } + + private double beatLengthToBpm(double beatLength) => 60000 / beatLength; + } } } From 95ff48c123cc19c7388c1140f2dc93513898244d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 30 Oct 2019 14:38:06 +0900 Subject: [PATCH 053/177] Don't log cancelled join requests --- osu.Game/Screens/Multi/RoomManager.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs index 6f473aaafa..8ceff7edef 100644 --- a/osu.Game/Screens/Multi/RoomManager.cs +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -98,7 +98,8 @@ namespace osu.Game.Screens.Multi currentJoinRoomRequest.Failure += exception => { - Logger.Log($"Failed to join room: {exception}", level: LogLevel.Important); + if (!(exception is OperationCanceledException)) + Logger.Log($"Failed to join room: {exception}", level: LogLevel.Important); onError?.Invoke(exception.ToString()); }; From b6457f0ce957342b9d76c2f36b37f85bccf640ea Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 30 Oct 2019 14:41:54 +0900 Subject: [PATCH 054/177] Cancel room joins on part --- osu.Game/Screens/Multi/RoomManager.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Multi/RoomManager.cs b/osu.Game/Screens/Multi/RoomManager.cs index 8ceff7edef..cdaba85b9e 100644 --- a/osu.Game/Screens/Multi/RoomManager.cs +++ b/osu.Game/Screens/Multi/RoomManager.cs @@ -87,9 +87,8 @@ namespace osu.Game.Screens.Multi public void JoinRoom(Room room, Action onSuccess = null, Action onError = null) { currentJoinRoomRequest?.Cancel(); - currentJoinRoomRequest = null; - currentJoinRoomRequest = new JoinRoomRequest(room, api.LocalUser.Value); + currentJoinRoomRequest.Success += () => { joinedRoom = room; @@ -108,6 +107,8 @@ namespace osu.Game.Screens.Multi public void PartRoom() { + currentJoinRoomRequest?.Cancel(); + if (joinedRoom == null) return; From f56d9fe50cf2933a1fd94afc90df7082ebaff331 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 30 Oct 2019 14:42:14 +0900 Subject: [PATCH 055/177] Forcefully part room when multiplayer exits --- osu.Game/Screens/Multi/Multiplayer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 90806bab6e..5945e9de13 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -212,6 +212,8 @@ namespace osu.Game.Screens.Multi public override bool OnExiting(IScreen next) { + roomManager.PartRoom(); + if (screenStack.CurrentScreen != null && !(screenStack.CurrentScreen is LoungeSubScreen)) { screenStack.Exit(); From 5899bbd8a63d8a4035c180f0c507fc7b3676cda3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Oct 2019 18:44:07 +0900 Subject: [PATCH 056/177] Fix merge regressions --- osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs | 6 +++--- osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs | 1 + 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs index cf084e838e..2ecc516919 100644 --- a/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs +++ b/osu.Game.Tests/Beatmaps/Formats/LegacyBeatmapDecoderTest.cs @@ -167,9 +167,9 @@ namespace osu.Game.Tests.Beatmaps.Formats var controlPoints = beatmap.ControlPointInfo; Assert.AreEqual(4, controlPoints.TimingPoints.Count); - Assert.AreEqual(6, controlPoints.DifficultyPoints.Count); + Assert.AreEqual(5, controlPoints.DifficultyPoints.Count); Assert.AreEqual(34, controlPoints.SamplePoints.Count); - Assert.AreEqual(9, controlPoints.EffectPoints.Count); + Assert.AreEqual(8, controlPoints.EffectPoints.Count); var timingPoint = controlPoints.TimingPointAt(0); Assert.AreEqual(956, timingPoint.Time); @@ -191,7 +191,7 @@ namespace osu.Game.Tests.Beatmaps.Formats Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); difficultyPoint = controlPoints.DifficultyPointAt(48428); - Assert.AreEqual(956, difficultyPoint.Time); + Assert.AreEqual(0, difficultyPoint.Time); Assert.AreEqual(1.0, difficultyPoint.SpeedMultiplier); difficultyPoint = controlPoints.DifficultyPointAt(116999); diff --git a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs index d9328d1636..51b3377394 100644 --- a/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs +++ b/osu.Game/Beatmaps/ControlPoints/TimingControlPoint.cs @@ -34,6 +34,7 @@ namespace osu.Game.Beatmaps.ControlPoints MaxValue = 60000 }; + /// /// The beat length at this control point. /// public double BeatLength From 35be8f9dfbab66ba527631ba2b100f1c9c48f35f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 30 Oct 2019 19:33:54 +0900 Subject: [PATCH 057/177] Share framework file-exclusion function --- osu.Game/IO/Archives/ZipArchiveReader.cs | 20 ++------------------ 1 file changed, 2 insertions(+), 18 deletions(-) diff --git a/osu.Game/IO/Archives/ZipArchiveReader.cs b/osu.Game/IO/Archives/ZipArchiveReader.cs index 9033e7529d..35f38ea7e8 100644 --- a/osu.Game/IO/Archives/ZipArchiveReader.cs +++ b/osu.Game/IO/Archives/ZipArchiveReader.cs @@ -1,30 +1,16 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using System.Collections.Generic; using System.IO; using System.Linq; +using osu.Framework.IO.Stores; using SharpCompress.Archives.Zip; -using SharpCompress.Common; namespace osu.Game.IO.Archives { public sealed class ZipArchiveReader : ArchiveReader { - /// - /// List of substrings that indicate a file should be ignored during the import process - /// (usually due to representing no useful data and being autogenerated by the OS). - /// - private static readonly string[] filename_ignore_list = - { - // Mac-specific - "__MACOSX", - ".DS_Store", - // Windows-specific - "Thumbs.db" - }; - private readonly Stream archiveStream; private readonly ZipArchive archive; @@ -58,9 +44,7 @@ namespace osu.Game.IO.Archives archiveStream.Dispose(); } - private static bool canBeIgnored(IEntry entry) => filename_ignore_list.Any(ignoredName => entry.Key.IndexOf(ignoredName, StringComparison.OrdinalIgnoreCase) >= 0); - - public override IEnumerable Filenames => archive.Entries.Where(e => !canBeIgnored(e)).Select(e => e.Key).ToArray(); + public override IEnumerable Filenames => archive.Entries.Select(e => e.Key).ExcludeSystemFileNames(); public override Stream GetUnderlyingStream() => archiveStream; } From 32dabf80a6f896cba970220d0e2435051a2c5982 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Oct 2019 13:42:11 +0900 Subject: [PATCH 058/177] Ensure forceful exit completely exits from mutliplayer Previously it may have gotten blocked by being in a sub screen. --- osu.Game/Screens/Multi/Multiplayer.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 5945e9de13..941d7a2478 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -174,7 +174,10 @@ namespace osu.Game.Screens.Multi { // This is temporary since we don't currently have a way to force screens to be exited if (this.IsCurrentScreen()) - this.Exit(); + { + while (this.IsCurrentScreen()) + this.Exit(); + } else { this.MakeCurrent(); From 5b405abc5253480414eb421028d48c1cb019ef0b Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Oct 2019 13:43:25 +0900 Subject: [PATCH 059/177] Schedule forcefullyExit call for safety Screen state may have changed at an inopportune moment. Run on local scheduler, not API scheduler to avoid any weirdness. --- osu.Game/Screens/Multi/Multiplayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Multi/Multiplayer.cs b/osu.Game/Screens/Multi/Multiplayer.cs index 941d7a2478..86d52ff791 100644 --- a/osu.Game/Screens/Multi/Multiplayer.cs +++ b/osu.Game/Screens/Multi/Multiplayer.cs @@ -167,7 +167,7 @@ namespace osu.Game.Screens.Multi public void APIStateChanged(IAPIProvider api, APIState state) { if (state != APIState.Online) - forcefullyExit(); + Schedule(forcefullyExit); } private void forcefullyExit() From 0cd912fcd3ad364b8ade2a7d5e98685f3e3c68d5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Oct 2019 15:04:13 +0900 Subject: [PATCH 060/177] Cover all non-APIAccess APIRequest calls with exception handling --- osu.Game/Beatmaps/BeatmapManager.cs | 11 ++++-- .../DownloadableArchiveModelManager.cs | 35 ++++++++++++------- .../Changelog/ChangelogSingleBuild.cs | 12 ++++++- osu.Game/Overlays/ChangelogOverlay.cs | 19 ++++++++-- 4 files changed, 60 insertions(+), 17 deletions(-) diff --git a/osu.Game/Beatmaps/BeatmapManager.cs b/osu.Game/Beatmaps/BeatmapManager.cs index dd2044b4bc..6e485f642a 100644 --- a/osu.Game/Beatmaps/BeatmapManager.cs +++ b/osu.Game/Beatmaps/BeatmapManager.cs @@ -392,8 +392,15 @@ namespace osu.Game.Beatmaps req.Failure += e => { LogForModel(set, $"Online retrieval failed for {beatmap} ({e.Message})"); }; - // intentionally blocking to limit web request concurrency - req.Perform(api); + try + { + // intentionally blocking to limit web request concurrency + req.Perform(api); + } + catch (Exception e) + { + LogForModel(set, $"Online retrieval failed for {beatmap} ({e.Message})"); + } } } } diff --git a/osu.Game/Database/DownloadableArchiveModelManager.cs b/osu.Game/Database/DownloadableArchiveModelManager.cs index e3c6ad25e6..a81dff3475 100644 --- a/osu.Game/Database/DownloadableArchiveModelManager.cs +++ b/osu.Game/Database/DownloadableArchiveModelManager.cs @@ -86,16 +86,7 @@ namespace osu.Game.Database }, TaskCreationOptions.LongRunning); }; - request.Failure += error => - { - DownloadFailed?.Invoke(request); - - if (error is OperationCanceledException) return; - - notification.State = ProgressNotificationState.Cancelled; - Logger.Error(error, $"{HumanisedModelName.Titleize()} download failed!"); - currentDownloads.Remove(request); - }; + request.Failure += triggerFailure; notification.CancelRequested += () => { @@ -108,11 +99,31 @@ namespace osu.Game.Database currentDownloads.Add(request); PostNotification?.Invoke(notification); - Task.Factory.StartNew(() => request.Perform(api), TaskCreationOptions.LongRunning); + Task.Factory.StartNew(() => + { + try + { + request.Perform(api); + } + catch (Exception error) + { + triggerFailure(error); + } + }, TaskCreationOptions.LongRunning); DownloadBegan?.Invoke(request); - return true; + + void triggerFailure(Exception error) + { + DownloadFailed?.Invoke(request); + + if (error is OperationCanceledException) return; + + notification.State = ProgressNotificationState.Cancelled; + Logger.Error(error, $"{HumanisedModelName.Titleize()} download failed!"); + currentDownloads.Remove(request); + } } public bool IsAvailableLocally(TModel model) => CheckLocalAvailability(model, modelStore.ConsumableItems.Where(m => !m.DeletePending)); diff --git a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs index 9c3504f477..adcd33fb48 100644 --- a/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogSingleBuild.cs @@ -44,7 +44,17 @@ namespace osu.Game.Overlays.Changelog req.Failure += _ => complete = true; // This is done on a separate thread to support cancellation below - Task.Run(() => req.Perform(api)); + Task.Run(() => + { + try + { + req.Perform(api); + } + catch + { + complete = true; + } + }); while (!complete) { diff --git a/osu.Game/Overlays/ChangelogOverlay.cs b/osu.Game/Overlays/ChangelogOverlay.cs index dfe3669813..559989af5c 100644 --- a/osu.Game/Overlays/ChangelogOverlay.cs +++ b/osu.Game/Overlays/ChangelogOverlay.cs @@ -170,6 +170,7 @@ namespace osu.Game.Overlays var tcs = new TaskCompletionSource(); var req = new GetChangelogRequest(); + req.Success += res => Schedule(() => { // remap streams to builds to ensure model equality @@ -183,8 +184,22 @@ namespace osu.Game.Overlays tcs.SetResult(true); }); - req.Failure += _ => initialFetchTask = null; - req.Perform(API); + + req.Failure += _ => + { + initialFetchTask = null; + tcs.SetResult(false); + }; + + try + { + req.Perform(API); + } + catch + { + initialFetchTask = null; + tcs.SetResult(false); + } await tcs.Task; }); From 7a3ebcd0b1c77b7caaac8c88f0a722353acd4a25 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 31 Oct 2019 15:52:38 +0900 Subject: [PATCH 061/177] Fix path changes not updating tail circle --- osu.Game.Rulesets.Osu/Objects/Slider.cs | 18 ++++++++++++------ 1 file changed, 12 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Slider.cs b/osu.Game.Rulesets.Osu/Objects/Slider.cs index 010bf072e8..f60b7e67b2 100644 --- a/osu.Game.Rulesets.Osu/Objects/Slider.cs +++ b/osu.Game.Rulesets.Osu/Objects/Slider.cs @@ -37,6 +37,8 @@ namespace osu.Game.Rulesets.Osu.Objects { PathBindable.Value = value; endPositionCache.Invalidate(); + + updateNestedPositions(); } } @@ -48,14 +50,9 @@ namespace osu.Game.Rulesets.Osu.Objects set { base.Position = value; - endPositionCache.Invalidate(); - if (HeadCircle != null) - HeadCircle.Position = value; - - if (TailCircle != null) - TailCircle.Position = EndPosition; + updateNestedPositions(); } } @@ -197,6 +194,15 @@ namespace osu.Game.Rulesets.Osu.Objects } } + private void updateNestedPositions() + { + if (HeadCircle != null) + HeadCircle.Position = Position; + + if (TailCircle != null) + TailCircle.Position = EndPosition; + } + private List getNodeSamples(int nodeIndex) => nodeIndex < NodeSamples.Count ? NodeSamples[nodeIndex] : Samples; From f8187fa30125db2a93b455bd369fe123903bbb44 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Oct 2019 16:23:10 +0900 Subject: [PATCH 062/177] Don't rely on masking for bar display --- osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs index 5227bec92a..8c150e6ed1 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs @@ -33,7 +33,6 @@ namespace osu.Game.Overlays.BeatmapSet : base(value) { AutoSizeAxes = Axes.Both; - Masking = true; FillFlowContainer nameContainer; @@ -87,7 +86,6 @@ namespace osu.Game.Overlays.BeatmapSet Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, RelativeSizeAxes = Axes.X, - Height = 4f, }, new HoverClickSounds(), }; @@ -95,9 +93,10 @@ namespace osu.Game.Overlays.BeatmapSet BeatmapSet.BindValueChanged(setInfo => { var beatmapsCount = setInfo.NewValue?.Beatmaps.Count(b => b.Ruleset.Equals(Value)) ?? 0; - count.Text = beatmapsCount.ToString(); + count.Text = beatmapsCount.ToString(); count.Alpha = beatmapsCount > 0 ? 1f : 0f; + Enabled.Value = beatmapsCount > 0; }, true); @@ -120,9 +119,10 @@ namespace osu.Game.Overlays.BeatmapSet private void updateState() { var isHoveredOrActive = IsHovered || Active.Value; - name.Colour = isHoveredOrActive ? colour.GrayE : colour.GrayC; - bar.MoveToY(isHoveredOrActive ? 0f : bar.Height, 120); + bar.ResizeHeightTo(isHoveredOrActive ? 4 : 1, 200, Easing.OutQuint); + + name.Colour = isHoveredOrActive ? colour.GrayE : colour.GrayC; name.Font = name.Font.With(weight: Active.Value ? FontWeight.Bold : FontWeight.Regular); } From e23a75c64a400705b202122570ca1f41d09c0735 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 31 Oct 2019 16:23:54 +0900 Subject: [PATCH 063/177] Implement control point selection --- .../Components/PathControlPointPiece.cs | 84 +++++++++++++++---- .../Components/PathControlPointVisualiser.cs | 21 +++++ 2 files changed, 87 insertions(+), 18 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 7afb8fcf49..3b0b9bce1f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -3,6 +3,7 @@ using System; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Lines; @@ -11,18 +12,21 @@ using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Rulesets.Osu.Objects; using osuTK; +using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { public class PathControlPointPiece : BlueprintPiece { public Action ControlPointsChanged; + public readonly Bindable IsSelected = new Bindable(); + + public readonly int Index; private readonly Slider slider; - private readonly int index; - private readonly Path path; - private readonly CircularContainer marker; + private readonly Container marker; + private readonly Drawable markerRing; [Resolved] private OsuColour colours { get; set; } @@ -30,7 +34,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public PathControlPointPiece(Slider slider, int index) { this.slider = slider; - this.index = index; + Index = index; Origin = Anchor.Centre; AutoSizeAxes = Axes.Both; @@ -42,13 +46,36 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components Anchor = Anchor.Centre, PathRadius = 1 }, - marker = new CircularContainer + marker = new Container { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(10), - Masking = true, - Child = new Box { RelativeSizeAxes = Axes.Both } + AutoSizeAxes = Axes.Both, + Children = new[] + { + new Circle + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(10), + }, + markerRing = new CircularContainer + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(14), + Masking = true, + BorderThickness = 2, + BorderColour = Color4.White, + Alpha = 0, + Child = new Box + { + RelativeSizeAxes = Axes.Both, + Alpha = 0, + AlwaysPresent = true + } + } + } } }; } @@ -57,21 +84,42 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { base.Update(); - Position = slider.StackedPosition + slider.Path.ControlPoints[index]; + Position = slider.StackedPosition + slider.Path.ControlPoints[Index]; - marker.Colour = isSegmentSeparator ? colours.Red : colours.Yellow; + updateMarkerDisplay(); + updateConnectingPath(); + } + /// + /// Updates the state of the circular control point marker. + /// + private void updateMarkerDisplay() + { + markerRing.Alpha = IsSelected.Value ? 1 : 0; + + Color4 colour = isSegmentSeparator ? colours.Red : colours.Yellow; + if (IsHovered || IsSelected.Value) + colour = Color4.White; + marker.Colour = colour; + } + + /// + /// Updates the path connecting this control point to the previous one. + /// + private void updateConnectingPath() + { path.ClearVertices(); - if (index != slider.Path.ControlPoints.Length - 1) + if (Index != slider.Path.ControlPoints.Length - 1) { path.AddVertex(Vector2.Zero); - path.AddVertex(slider.Path.ControlPoints[index + 1] - slider.Path.ControlPoints[index]); + path.AddVertex(slider.Path.ControlPoints[Index + 1] - slider.Path.ControlPoints[Index]); } path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero); } + // The connecting path is excluded from positional input public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => marker.ReceivePositionalInputAt(screenSpacePos); protected override bool OnDragStart(DragStartEvent e) => true; @@ -80,7 +128,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { var newControlPoints = slider.Path.ControlPoints.ToArray(); - if (index == 0) + if (Index == 0) { // Special handling for the head - only the position of the slider changes slider.Position += e.Delta; @@ -90,13 +138,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components newControlPoints[i] -= e.Delta; } else - newControlPoints[index] += e.Delta; + newControlPoints[Index] += e.Delta; if (isSegmentSeparatorWithNext) - newControlPoints[index + 1] = newControlPoints[index]; + newControlPoints[Index + 1] = newControlPoints[Index]; if (isSegmentSeparatorWithPrevious) - newControlPoints[index - 1] = newControlPoints[index]; + newControlPoints[Index - 1] = newControlPoints[Index]; ControlPointsChanged?.Invoke(newControlPoints); @@ -107,8 +155,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private bool isSegmentSeparator => isSegmentSeparatorWithNext || isSegmentSeparatorWithPrevious; - private bool isSegmentSeparatorWithNext => index < slider.Path.ControlPoints.Length - 1 && slider.Path.ControlPoints[index + 1] == slider.Path.ControlPoints[index]; + private bool isSegmentSeparatorWithNext => Index < slider.Path.ControlPoints.Length - 1 && slider.Path.ControlPoints[Index + 1] == slider.Path.ControlPoints[Index]; - private bool isSegmentSeparatorWithPrevious => index > 0 && slider.Path.ControlPoints[index - 1] == slider.Path.ControlPoints[index]; + private bool isSegmentSeparatorWithPrevious => Index > 0 && slider.Path.ControlPoints[Index - 1] == slider.Path.ControlPoints[Index]; } } diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 0385824b27..b0e579907d 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input.Events; using osu.Game.Rulesets.Osu.Objects; using osuTK; @@ -21,6 +22,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { this.slider = slider; + RelativeSizeAxes = Axes.Both; + InternalChild = pieces = new Container { RelativeSizeAxes = Axes.Both }; } @@ -33,5 +36,23 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components while (slider.Path.ControlPoints.Length < pieces.Count) pieces.Remove(pieces[pieces.Count - 1]); } + + protected override bool OnMouseDown(MouseDownEvent e) + { + bool anySelected = false; + + foreach (var piece in pieces) + { + if (piece.IsHovered) + { + piece.IsSelected.Value = true; + anySelected = true; + } + else + piece.IsSelected.Value = false; + } + + return anySelected; + } } } From bf45fa630984d624828bbeaf34ee7498a93fc170 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Oct 2019 16:24:03 +0900 Subject: [PATCH 064/177] Use lambda function expression --- osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs index 8c150e6ed1..3fecfbdaaf 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs @@ -138,10 +138,7 @@ namespace osu.Game.Overlays.BeatmapSet return false; } - protected override void OnHoverLost(HoverLostEvent e) - { - updateState(); - } + protected override void OnHoverLost(HoverLostEvent e) => updateState(); #endregion } From cfdf7106764b904c733e001eeba71b1e6b005d97 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 31 Oct 2019 16:51:58 +0900 Subject: [PATCH 065/177] Add test --- .../TestSceneSliderSelectionBlueprint.cs | 33 +++++++++++++++++++ .../Components/PathControlPointVisualiser.cs | 16 ++++----- .../Sliders/SliderSelectionBlueprint.cs | 3 +- .../Visual/SelectionBlueprintTestScene.cs | 2 +- 4 files changed, 44 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs index 5df0b70f12..0e7d8c9c08 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs @@ -16,6 +16,7 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Tests.Visual; using osuTK; +using osuTK.Input; namespace osu.Game.Rulesets.Osu.Tests { @@ -85,6 +86,25 @@ namespace osu.Game.Rulesets.Osu.Tests checkPositions(); } + [Test] + public void TestSingleControlPointSelection() + { + moveMouseToControlPoint(0); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + checkControlPointSelected(0, true); + checkControlPointSelected(1, false); + + moveMouseToControlPoint(1); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + checkControlPointSelected(0, false); + checkControlPointSelected(1, true); + + AddStep("move mouse outside control point", () => InputManager.MoveMouseTo(drawableObject)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + checkControlPointSelected(0, false); + checkControlPointSelected(1, false); + } + private void moveHitObject() { AddStep("move hitobject", () => @@ -104,11 +124,24 @@ namespace osu.Game.Rulesets.Osu.Tests () => Precision.AlmostEquals(blueprint.TailBlueprint.CirclePiece.ScreenSpaceDrawQuad.Centre, drawableObject.TailCircle.ScreenSpaceDrawQuad.Centre)); } + private void moveMouseToControlPoint(int index) + { + AddStep($"move mouse to control point {index}", () => + { + Vector2 position = slider.Position + slider.Path.ControlPoints[index]; + InputManager.MoveMouseTo(drawableObject.Parent.ToScreenSpace(position)); + }); + } + + private void checkControlPointSelected(int index, bool selected) + => AddAssert($"control point {index} {(selected ? "selected" : "not selected")}", () => blueprint.ControlPointVisualiser.Pieces[index].IsSelected.Value == selected); + private class TestSliderBlueprint : SliderSelectionBlueprint { public new SliderBodyPiece BodyPiece => base.BodyPiece; public new TestSliderCircleBlueprint HeadBlueprint => (TestSliderCircleBlueprint)base.HeadBlueprint; public new TestSliderCircleBlueprint TailBlueprint => (TestSliderCircleBlueprint)base.TailBlueprint; + public new PathControlPointVisualiser ControlPointVisualiser => base.ControlPointVisualiser; public TestSliderBlueprint(DrawableSlider slider) : base(slider) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index b0e579907d..6efa0ef550 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -14,9 +14,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { public Action ControlPointsChanged; - private readonly Slider slider; + internal Container Pieces { get; } - private readonly Container pieces; + private readonly Slider slider; public PathControlPointVisualiser(Slider slider) { @@ -24,24 +24,24 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components RelativeSizeAxes = Axes.Both; - InternalChild = pieces = new Container { RelativeSizeAxes = Axes.Both }; + InternalChild = Pieces = new Container { RelativeSizeAxes = Axes.Both }; } protected override void Update() { base.Update(); - while (slider.Path.ControlPoints.Length > pieces.Count) - pieces.Add(new PathControlPointPiece(slider, pieces.Count) { ControlPointsChanged = c => ControlPointsChanged?.Invoke(c) }); - while (slider.Path.ControlPoints.Length < pieces.Count) - pieces.Remove(pieces[pieces.Count - 1]); + while (slider.Path.ControlPoints.Length > Pieces.Count) + Pieces.Add(new PathControlPointPiece(slider, Pieces.Count) { ControlPointsChanged = c => ControlPointsChanged?.Invoke(c) }); + while (slider.Path.ControlPoints.Length < Pieces.Count) + Pieces.Remove(Pieces[Pieces.Count - 1]); } protected override bool OnMouseDown(MouseDownEvent e) { bool anySelected = false; - foreach (var piece in pieces) + foreach (var piece in Pieces) { if (piece.IsHovered) { diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index b09f598bcc..ba502d3a7c 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -18,6 +18,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders protected readonly SliderBodyPiece BodyPiece; protected readonly SliderCircleSelectionBlueprint HeadBlueprint; protected readonly SliderCircleSelectionBlueprint TailBlueprint; + protected readonly PathControlPointVisualiser ControlPointVisualiser; [Resolved(CanBeNull = true)] private HitObjectComposer composer { get; set; } @@ -32,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders BodyPiece = new SliderBodyPiece(), HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start), TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End), - new PathControlPointVisualiser(sliderObject) { ControlPointsChanged = onNewControlPoints }, + ControlPointVisualiser = new PathControlPointVisualiser(sliderObject) { ControlPointsChanged = onNewControlPoints }, }; } diff --git a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs index f53c12b047..3233ee160d 100644 --- a/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs +++ b/osu.Game/Tests/Visual/SelectionBlueprintTestScene.cs @@ -8,7 +8,7 @@ using osu.Game.Rulesets.Edit; namespace osu.Game.Tests.Visual { - public abstract class SelectionBlueprintTestScene : OsuTestScene + public abstract class SelectionBlueprintTestScene : ManualInputManagerTestScene { protected override Container Content => content ?? base.Content; private readonly Container content; From fe93df718675d16c931a67b903e4fbeee7fe6dcc Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Oct 2019 16:52:56 +0900 Subject: [PATCH 066/177] Remove unnecessary using statement --- osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs index 6c7a3e4108..286971bc90 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneBeatmapSetOverlay.cs @@ -3,7 +3,6 @@ using NUnit.Framework; using osu.Framework.Allocation; -using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Overlays; using osu.Game.Overlays.BeatmapSet; From 4f04abf2825d374f3deefd4bc013606f50d2400f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Oct 2019 16:54:28 +0900 Subject: [PATCH 067/177] Fix tabs to match design (should not show pinhair line) --- osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs index 3fecfbdaaf..cdea49afe7 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapRulesetTabItem.cs @@ -120,7 +120,7 @@ namespace osu.Game.Overlays.BeatmapSet { var isHoveredOrActive = IsHovered || Active.Value; - bar.ResizeHeightTo(isHoveredOrActive ? 4 : 1, 200, Easing.OutQuint); + bar.ResizeHeightTo(isHoveredOrActive ? 4 : 0, 200, Easing.OutQuint); name.Colour = isHoveredOrActive ? colour.GrayE : colour.GrayC; name.Font = name.Font.With(weight: Active.Value ? FontWeight.Bold : FontWeight.Regular); From 3e3ff812291f343d1847ecd037914e38ddf70d50 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Oct 2019 17:13:00 +0900 Subject: [PATCH 068/177] Reorder methods --- osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs | 55 +++++++++---------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs index bffe779da1..bf2a92cd4f 100644 --- a/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs +++ b/osu.Game/Overlays/BeatmapSet/BeatmapPicker.cs @@ -45,38 +45,10 @@ namespace osu.Game.Overlays.BeatmapSet if (value == beatmapSet) return; beatmapSet = value; - updateDisplay(); } } - private void updateDisplay() - { - Difficulties.Clear(); - - if (BeatmapSet != null) - { - Difficulties.ChildrenEnumerable = BeatmapSet.Beatmaps.Where(b => b.Ruleset.Equals(ruleset.Value)).OrderBy(b => b.StarDifficulty).Select(b => new DifficultySelectorButton(b) - { - State = DifficultySelectorState.NotSelected, - OnHovered = beatmap => - { - showBeatmap(beatmap); - starRating.Text = beatmap.StarDifficulty.ToString("Star Difficulty 0.##"); - starRating.FadeIn(100); - }, - OnClicked = beatmap => { Beatmap.Value = beatmap; }, - }); - } - - starRating.FadeOut(100); - Beatmap.Value = Difficulties.FirstOrDefault()?.Beatmap; - plays.Value = BeatmapSet?.OnlineInfo.PlayCount ?? 0; - favourites.Value = BeatmapSet?.OnlineInfo.FavouriteCount ?? 0; - - updateDifficultyButtons(); - } - public BeatmapPicker() { RelativeSizeAxes = Axes.X; @@ -169,6 +141,33 @@ namespace osu.Game.Overlays.BeatmapSet Beatmap.TriggerChange(); } + private void updateDisplay() + { + Difficulties.Clear(); + + if (BeatmapSet != null) + { + Difficulties.ChildrenEnumerable = BeatmapSet.Beatmaps.Where(b => b.Ruleset.Equals(ruleset.Value)).OrderBy(b => b.StarDifficulty).Select(b => new DifficultySelectorButton(b) + { + State = DifficultySelectorState.NotSelected, + OnHovered = beatmap => + { + showBeatmap(beatmap); + starRating.Text = beatmap.StarDifficulty.ToString("Star Difficulty 0.##"); + starRating.FadeIn(100); + }, + OnClicked = beatmap => { Beatmap.Value = beatmap; }, + }); + } + + starRating.FadeOut(100); + Beatmap.Value = Difficulties.FirstOrDefault()?.Beatmap; + plays.Value = BeatmapSet?.OnlineInfo.PlayCount ?? 0; + favourites.Value = BeatmapSet?.OnlineInfo.FavouriteCount ?? 0; + + updateDifficultyButtons(); + } + private void showBeatmap(BeatmapInfo beatmap) { version.Text = beatmap?.Version; From 8d50b155e8022f43bca1a9c824ee8697aa4acf70 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 31 Oct 2019 17:13:10 +0900 Subject: [PATCH 069/177] Make selection happen on click only --- .../Components/PathControlPointPiece.cs | 25 ++++++++++++++-- .../Components/PathControlPointVisualiser.cs | 30 ++++++++++--------- 2 files changed, 39 insertions(+), 16 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 3b0b9bce1f..bd6a905b38 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -18,9 +18,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { public class PathControlPointPiece : BlueprintPiece { + public Action RequestSelection; public Action ControlPointsChanged; - public readonly Bindable IsSelected = new Bindable(); + public readonly Bindable IsSelected = new Bindable(); public readonly int Index; private readonly Slider slider; @@ -28,6 +29,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private readonly Container marker; private readonly Drawable markerRing; + private bool isClicked; + [Resolved] private OsuColour colours { get; set; } @@ -98,7 +101,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components markerRing.Alpha = IsSelected.Value ? 1 : 0; Color4 colour = isSegmentSeparator ? colours.Red : colours.Yellow; - if (IsHovered || IsSelected.Value) + if (IsHovered || isClicked || IsSelected.Value) colour = Color4.White; marker.Colour = colour; } @@ -122,6 +125,24 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components // The connecting path is excluded from positional input public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => marker.ReceivePositionalInputAt(screenSpacePos); + protected override bool OnMouseDown(MouseDownEvent e) + { + isClicked = true; + return true; + } + + protected override bool OnMouseUp(MouseUpEvent e) + { + isClicked = false; + return true; + } + + protected override bool OnClick(ClickEvent e) + { + RequestSelection?.Invoke(Index); + return true; + } + protected override bool OnDragStart(DragStartEvent e) => true; protected override bool OnDrag(DragEvent e) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 6efa0ef550..160168eb37 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -32,27 +32,29 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components base.Update(); while (slider.Path.ControlPoints.Length > Pieces.Count) - Pieces.Add(new PathControlPointPiece(slider, Pieces.Count) { ControlPointsChanged = c => ControlPointsChanged?.Invoke(c) }); + { + Pieces.Add(new PathControlPointPiece(slider, Pieces.Count) + { + ControlPointsChanged = c => ControlPointsChanged?.Invoke(c), + RequestSelection = selectPiece + }); + } + while (slider.Path.ControlPoints.Length < Pieces.Count) Pieces.Remove(Pieces[Pieces.Count - 1]); } - protected override bool OnMouseDown(MouseDownEvent e) + protected override bool OnClick(ClickEvent e) { - bool anySelected = false; - foreach (var piece in Pieces) - { - if (piece.IsHovered) - { - piece.IsSelected.Value = true; - anySelected = true; - } - else - piece.IsSelected.Value = false; - } + piece.IsSelected.Value = false; + return false; + } - return anySelected; + private void selectPiece(int index) + { + foreach (var piece in Pieces) + piece.IsSelected.Value = piece.Index == index; } } } From ce19b2ed36b04186014123222a12067e9301c927 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Oct 2019 17:16:26 +0900 Subject: [PATCH 070/177] Avoid using CreateChildDependencies with a *child* bindable Don't do this. --- osu.Game/Overlays/BeatmapSet/Header.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/osu.Game/Overlays/BeatmapSet/Header.cs b/osu.Game/Overlays/BeatmapSet/Header.cs index 0e3d29c25b..7b42e7e459 100644 --- a/osu.Game/Overlays/BeatmapSet/Header.cs +++ b/osu.Game/Overlays/BeatmapSet/Header.cs @@ -50,6 +50,9 @@ namespace osu.Game.Overlays.BeatmapSet private readonly LoadingAnimation loading; + [Cached(typeof(IBindable))] + private readonly Bindable ruleset = new Bindable(); + public Header() { ExternalLinkButton externalLink; @@ -80,6 +83,7 @@ namespace osu.Game.Overlays.BeatmapSet }, RulesetSelector = new BeatmapRulesetSelector { + Current = ruleset, Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, } @@ -222,13 +226,6 @@ namespace osu.Game.Overlays.BeatmapSet }; } - protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) - { - var dependencies = new DependencyContainer(base.CreateChildDependencies(parent)); - dependencies.CacheAs>(RulesetSelector.Current); - return dependencies; - } - [BackgroundDependencyLoader] private void load(OsuColour colours) { From 20aeb7aaffe4fe48bfcfa820a49f7765dc544bce Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 31 Oct 2019 17:25:30 +0900 Subject: [PATCH 071/177] Implement multiple selection --- .../TestSceneSliderSelectionBlueprint.cs | 68 +++++++++++++++++++ .../Components/PathControlPointVisualiser.cs | 22 ++++-- 2 files changed, 86 insertions(+), 4 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs index 0e7d8c9c08..dde2aa53e0 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneSliderSelectionBlueprint.cs @@ -93,11 +93,79 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("click", () => InputManager.Click(MouseButton.Left)); checkControlPointSelected(0, true); checkControlPointSelected(1, false); + } + + [Test] + public void TestSingleControlPointDeselectionViaOtherControlPoint() + { + moveMouseToControlPoint(0); + AddStep("click", () => InputManager.Click(MouseButton.Left)); moveMouseToControlPoint(1); AddStep("click", () => InputManager.Click(MouseButton.Left)); checkControlPointSelected(0, false); checkControlPointSelected(1, true); + } + + [Test] + public void TestSingleControlPointDeselectionViaClickOutside() + { + moveMouseToControlPoint(0); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + + AddStep("move mouse outside control point", () => InputManager.MoveMouseTo(drawableObject)); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + checkControlPointSelected(0, false); + checkControlPointSelected(1, false); + } + + [Test] + public void TestMultipleControlPointSelection() + { + moveMouseToControlPoint(0); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + moveMouseToControlPoint(1); + AddStep("ctrl + click", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Click(MouseButton.Left); + InputManager.ReleaseKey(Key.ControlLeft); + }); + checkControlPointSelected(0, true); + checkControlPointSelected(1, true); + } + + [Test] + public void TestMultipleControlPointDeselectionViaOtherControlPoint() + { + moveMouseToControlPoint(0); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + moveMouseToControlPoint(1); + AddStep("ctrl + click", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Click(MouseButton.Left); + InputManager.ReleaseKey(Key.ControlLeft); + }); + + moveMouseToControlPoint(2); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + checkControlPointSelected(0, false); + checkControlPointSelected(1, false); + } + + [Test] + public void TestMultipleControlPointDeselectionViaClickOutside() + { + moveMouseToControlPoint(0); + AddStep("click", () => InputManager.Click(MouseButton.Left)); + moveMouseToControlPoint(1); + AddStep("ctrl + click", () => + { + InputManager.PressKey(Key.ControlLeft); + InputManager.Click(MouseButton.Left); + InputManager.ReleaseKey(Key.ControlLeft); + }); AddStep("move mouse outside control point", () => InputManager.MoveMouseTo(drawableObject)); AddStep("click", () => InputManager.Click(MouseButton.Left)); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 160168eb37..bbe771d8b0 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -4,6 +4,7 @@ using System; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; +using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Rulesets.Osu.Objects; using osuTK; @@ -14,10 +15,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { public Action ControlPointsChanged; - internal Container Pieces { get; } - + internal readonly Container Pieces; private readonly Slider slider; + private InputManager inputManager; + public PathControlPointVisualiser(Slider slider) { this.slider = slider; @@ -27,6 +29,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components InternalChild = Pieces = new Container { RelativeSizeAxes = Axes.Both }; } + protected override void LoadComplete() + { + base.LoadComplete(); + + inputManager = GetContainingInputManager(); + } + protected override void Update() { base.Update(); @@ -53,8 +62,13 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private void selectPiece(int index) { - foreach (var piece in Pieces) - piece.IsSelected.Value = piece.Index == index; + if (inputManager.CurrentState.Keyboard.ControlPressed) + Pieces[index].IsSelected.Value = true; + else + { + foreach (var piece in Pieces) + piece.IsSelected.Value = piece.Index == index; + } } } } From 43b2cbb865f031eacb431a7d8ccb5fd4fbd04954 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 31 Oct 2019 16:25:26 +0900 Subject: [PATCH 072/177] Implement slider control point deletion --- .../Components/PathControlPointVisualiser.cs | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index bbe771d8b0..b578c75474 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -2,12 +2,16 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; using osu.Framework.Input.Events; using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Screens.Edit.Compose; using osuTK; +using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { @@ -20,6 +24,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private InputManager inputManager; + [Resolved(CanBeNull = true)] + private IPlacementHandler placementHandler { get; set; } + public PathControlPointVisualiser(Slider slider) { this.slider = slider; @@ -70,5 +77,44 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components piece.IsSelected.Value = piece.Index == index; } } + + protected override bool OnKeyDown(KeyDownEvent e) + { + switch (e.Key) + { + case Key.Delete: + var newControlPoints = new List(); + + foreach (var piece in pieces) + { + if (!piece.IsSelected.Value) + newControlPoints.Add(slider.Path.ControlPoints[piece.Index]); + } + + // Ensure that there are any points to be deleted + if (newControlPoints.Count == slider.Path.ControlPoints.Length) + return false; + + // If there are 0 remaining control points, treat the slider as being deleted + if (newControlPoints.Count == 0) + { + placementHandler?.Delete(slider); + return true; + } + + // Make control points relative + Vector2 first = newControlPoints[0]; + for (int i = 0; i < newControlPoints.Count; i++) + newControlPoints[i] = newControlPoints[i] - first; + + // The slider's position defines the position of the first control point, and all further control points are relative to that point + slider.Position = slider.Position + first; + ControlPointsChanged?.Invoke(newControlPoints.ToArray()); + + return true; + } + + return false; + } } } From 83abb845b648def4b42b2cebace3f5ace3c78e01 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Oct 2019 17:50:06 +0900 Subject: [PATCH 073/177] Fix code quality --- osu.Game/Graphics/Containers/LinkFlowContainer.cs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 66911d9615..efd4aae23e 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -74,7 +74,13 @@ namespace osu.Game.Graphics.Containers { RelativeSizeAxes = Axes.Both, TooltipText = tooltipText ?? (url != text ? url : string.Empty), - Action = action ?? (() => game.HandleLink(url, linkType, linkArgument)), + Action = () => + { + if (action != null) + action(); + else + game.HandleLink(url, linkType, linkArgument); + }, }); return drawables; From 78194cfa6e193c53cef9a4417a8cbbbe7d7283f6 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 31 Oct 2019 17:54:06 +0900 Subject: [PATCH 074/177] Implement slider control point deletions --- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index b578c75474..17eb236ae0 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -85,7 +85,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components case Key.Delete: var newControlPoints = new List(); - foreach (var piece in pieces) + foreach (var piece in Pieces) { if (!piece.IsSelected.Value) newControlPoints.Add(slider.Path.ControlPoints[piece.Index]); From 18f374eec653612784227dfcbe7d6aaf914e5c5b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 31 Oct 2019 17:58:33 +0900 Subject: [PATCH 075/177] Deselect deleted control points --- .../Sliders/Components/PathControlPointVisualiser.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 17eb236ae0..a6ee928c90 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -109,6 +109,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components // The slider's position defines the position of the first control point, and all further control points are relative to that point slider.Position = slider.Position + first; + + // Since pieces are re-used, they will not point to the deleted control points while remaining selected + foreach (var piece in Pieces) + piece.IsSelected.Value = false; + ControlPointsChanged?.Invoke(newControlPoints.ToArray()); return true; From 9f28b1905b50b19511d10b698d1d97da0a2267f9 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 31 Oct 2019 18:15:19 +0900 Subject: [PATCH 076/177] Expose composer method to update hitobject --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 23 +++++++++++++++------ 1 file changed, 17 insertions(+), 6 deletions(-) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 5922bfba78..1c942e52ce 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -148,7 +148,7 @@ namespace osu.Game.Rulesets.Edit EditorBeatmap = new EditorBeatmap(playableBeatmap); EditorBeatmap.HitObjectAdded += addHitObject; EditorBeatmap.HitObjectRemoved += removeHitObject; - EditorBeatmap.StartTimeChanged += updateHitObject; + EditorBeatmap.StartTimeChanged += UpdateHitObject; var dependencies = new DependencyContainer(parent); dependencies.CacheAs(EditorBeatmap); @@ -225,11 +225,7 @@ namespace osu.Game.Rulesets.Edit private ScheduledDelegate scheduledUpdate; - private void addHitObject(HitObject hitObject) => updateHitObject(hitObject); - - private void removeHitObject(HitObject hitObject) => updateHitObject(null); - - private void updateHitObject([CanBeNull] HitObject hitObject) + public override void UpdateHitObject(HitObject hitObject) { scheduledUpdate?.Cancel(); scheduledUpdate = Schedule(() => @@ -240,6 +236,10 @@ namespace osu.Game.Rulesets.Edit }); } + private void addHitObject(HitObject hitObject) => UpdateHitObject(hitObject); + + private void removeHitObject(HitObject hitObject) => UpdateHitObject(null); + public override IEnumerable HitObjects => drawableRulesetWrapper.Playfield.AllHitObjects; public override bool CursorInPlacementArea => drawableRulesetWrapper.Playfield.ReceivePositionalInputAt(inputManager.CurrentState.Mouse.Position); @@ -351,11 +351,22 @@ namespace osu.Game.Rulesets.Edit [CanBeNull] protected virtual DistanceSnapGrid CreateDistanceSnapGrid([NotNull] IEnumerable selectedHitObjects) => null; + /// + /// Updates a , invoking and re-processing the beatmap. + /// + /// The to update. + public abstract void UpdateHitObject([CanBeNull] HitObject hitObject); + public abstract (Vector2 position, double time) GetSnappedPosition(Vector2 position, double time); + public abstract float GetBeatSnapDistanceAt(double referenceTime); + public abstract float DurationToDistance(double referenceTime, double duration); + public abstract double DistanceToDuration(double referenceTime, float distance); + public abstract double GetSnappedDurationFromDistance(double referenceTime, float distance); + public abstract float GetSnappedDistanceFromDistance(double referenceTime, float distance); } } From 41ae66d517e7a005f5f17e3faf1e583eb26b2151 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 31 Oct 2019 18:24:38 +0900 Subject: [PATCH 077/177] Update slider when control points change --- .../Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 ++ osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 10 ++++++++++ 2 files changed, 12 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index b09f598bcc..585a477aba 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -49,6 +49,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders var snappedDistance = composer?.GetSnappedDistanceFromDistance(HitObject.StartTime, (float)unsnappedPath.Distance) ?? (float)unsnappedPath.Distance; HitObject.Path = new SliderPath(unsnappedPath.Type, controlPoints, snappedDistance); + + UpdateHitObject(); } public override Vector2 SelectionPoint => HeadBlueprint.SelectionPoint; diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 44f38acfd4..0701513933 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -3,10 +3,12 @@ using System; using osu.Framework; +using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Game.Graphics.UserInterface; +using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osuTK; @@ -36,6 +38,9 @@ namespace osu.Game.Rulesets.Edit public override bool HandlePositionalInput => ShouldBeAlive; public override bool RemoveWhenNotAlive => false; + [Resolved(CanBeNull = true)] + private HitObjectComposer composer { get; set; } + protected SelectionBlueprint(DrawableHitObject drawableObject) { DrawableObject = drawableObject; @@ -89,6 +94,11 @@ namespace osu.Game.Rulesets.Edit public bool IsSelected => State == SelectionState.Selected; + /// + /// Updates the , invoking and re-processing the beatmap. + /// + protected void UpdateHitObject() => composer?.UpdateHitObject(DrawableObject.HitObject); + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => DrawableObject.ReceivePositionalInputAt(screenSpacePos); /// From 11447023eb21ad6f251a95d1310d16bf7852c003 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Oct 2019 18:34:41 +0900 Subject: [PATCH 078/177] Improve splitting out of link handling --- .../Graphics/Containers/LinkFlowContainer.cs | 29 +++++++++--------- osu.Game/Online/Chat/MessageFormatter.cs | 19 ++++++------ osu.Game/OsuGame.cs | 30 +++++++------------ osu.Game/Overlays/Changelog/ChangelogBuild.cs | 4 +-- .../Screens/Multi/Components/BeatmapTitle.cs | 2 +- osu.iOS/AppDelegate.cs | 2 +- 6 files changed, 40 insertions(+), 46 deletions(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index efd4aae23e..b5ffd1c6ad 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -44,46 +44,47 @@ namespace osu.Game.Graphics.Containers foreach (var link in links) { AddText(text.Substring(previousLinkEnd, link.Index - previousLinkEnd)); - AddLink(text.Substring(link.Index, link.Length), link.Url, link.Action, link.Argument); + AddLink(text.Substring(link.Index, link.Length), link.Action, link.Argument ?? link.Url); previousLinkEnd = link.Index + link.Length; } AddText(text.Substring(previousLinkEnd)); } - public IEnumerable AddLink(string text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null, Action creationParameters = null) - => createLink(AddText(text, creationParameters), text, url, linkType, linkArgument, tooltipText); + public void AddLink(string text, string url, Action creationParameters = null) => + createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.External, url), text); - public IEnumerable AddLink(string text, Action action, string tooltipText = null, Action creationParameters = null) - => createLink(AddText(text, creationParameters), text, tooltipText: tooltipText, action: action); + public void AddLink(string text, Action action, string tooltipText = null, Action creationParameters = null) + => createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.Custom, null), tooltipText, action); - public IEnumerable AddLink(IEnumerable text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null) + public void AddLink(string text, LinkAction action, string argument, string tooltipText = null, Action creationParameters = null) + => createLink(AddText(text, creationParameters), new LinkDetails(action, argument), null); + + public void AddLink(IEnumerable text, LinkAction action = LinkAction.External, string linkArgument = null, string tooltipText = null) { foreach (var t in text) AddArbitraryDrawable(t); - return createLink(text, null, url, linkType, linkArgument, tooltipText); + createLink(text, new LinkDetails(action, linkArgument), tooltipText); } - public IEnumerable AddUserLink(User user, Action creationParameters = null) - => createLink(AddText(user.Username, creationParameters), user.Username, null, LinkAction.OpenUserProfile, user.Id.ToString(), "View profile"); + public void AddUserLink(User user, Action creationParameters = null) + => createLink(AddText(user.Username, creationParameters), new LinkDetails(LinkAction.OpenUserProfile, user.Id.ToString()), "View Profile"); - private IEnumerable createLink(IEnumerable drawables, string text, string url = null, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null, Action action = null) + private void createLink(IEnumerable drawables, LinkDetails link, string tooltipText, Action action = null) { AddInternal(new DrawableLinkCompiler(drawables.OfType().ToList()) { RelativeSizeAxes = Axes.Both, - TooltipText = tooltipText ?? (url != text ? url : string.Empty), + TooltipText = tooltipText, Action = () => { if (action != null) action(); else - game.HandleLink(url, linkType, linkArgument); + game.HandleLink(link); }, }); - - return drawables; } // We want the compilers to always be visible no matter where they are, so RelativeSizeAxes is used. diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 31a324d0ea..717de18c14 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -255,17 +255,17 @@ namespace osu.Game.Online.Chat OriginalText = Text = text; } } + } - public class LinkDetails + public class LinkDetails + { + public LinkAction Action; + public string Argument; + + public LinkDetails(LinkAction action, string argument) { - public LinkAction Action; - public string Argument; - - public LinkDetails(LinkAction action, string argument) - { - Action = action; - Argument = argument; - } + Action = action; + Argument = argument; } } @@ -279,6 +279,7 @@ namespace osu.Game.Online.Chat JoinMultiplayerMatch, Spectate, OpenUserProfile, + Custom } public class Link : IComparable diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index c028896cff..b55cc41454 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -43,7 +43,6 @@ using osu.Game.Scoring; using osu.Game.Screens.Select; using osu.Game.Utils; using LogLevel = osu.Framework.Logging.LogLevel; -using static osu.Game.Online.Chat.MessageFormatter; namespace osu.Game { @@ -216,7 +215,9 @@ namespace osu.Game private ExternalLinkOpener externalLinkOpener; - public void HandleLink(string url, LinkAction linkType, string linkArgument) + public void HandleLink(string url) => HandleLink(MessageFormatter.GetLinkDetails(url)); + + public void HandleLink(LinkDetails link) { Action showNotImplementedError = () => notifications?.Post(new SimpleNotification { @@ -224,27 +225,27 @@ namespace osu.Game Icon = FontAwesome.Solid.LifeRing, }); - switch (linkType) + switch (link.Action) { case LinkAction.OpenBeatmap: // TODO: proper query params handling - if (linkArgument != null && int.TryParse(linkArgument.Contains('?') ? linkArgument.Split('?')[0] : linkArgument, out int beatmapId)) + if (link.Argument != null && int.TryParse(link.Argument.Contains('?') ? link.Argument.Split('?')[0] : link.Argument, out int beatmapId)) ShowBeatmap(beatmapId); break; case LinkAction.OpenBeatmapSet: - if (int.TryParse(linkArgument, out int setId)) + if (int.TryParse(link.Argument, out int setId)) ShowBeatmapSet(setId); break; case LinkAction.OpenChannel: try { - channelManager.OpenChannel(linkArgument); + channelManager.OpenChannel(link.Argument); } catch (ChannelNotFoundException) { - Logger.Log($"The requested channel \"{linkArgument}\" does not exist"); + Logger.Log($"The requested channel \"{link.Argument}\" does not exist"); } break; @@ -256,28 +257,19 @@ namespace osu.Game break; case LinkAction.External: - OpenUrlExternally(url); + OpenUrlExternally(link.Argument); break; case LinkAction.OpenUserProfile: - if (long.TryParse(linkArgument, out long userId)) + if (long.TryParse(link.Argument, out long userId)) ShowUser(userId); break; default: - throw new NotImplementedException($"This {nameof(LinkAction)} ({linkType.ToString()}) is missing an associated action."); + throw new NotImplementedException($"This {nameof(LinkAction)} ({link.Action.ToString()}) is missing an associated action."); } } - public void HandleUrl(string url) - { - Logger.Log($"Request to handle url: {url}"); - - LinkDetails linkDetails = GetLinkDetails(url); - - Schedule(() => HandleLink(url, linkDetails.Action, linkDetails.Argument)); - } - public void OpenUrlExternally(string url) { if (url.StartsWith("/")) diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs index bce1be5941..d8488b21ab 100644 --- a/osu.Game/Overlays/Changelog/ChangelogBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs @@ -110,7 +110,7 @@ namespace osu.Game.Overlays.Changelog t.Font = fontLarge; t.Colour = entryColour; }); - title.AddLink($"{entry.Repository.Replace("ppy/", "")}#{entry.GithubPullRequestId}", entry.GithubUrl, Online.Chat.LinkAction.External, + title.AddLink($"{entry.Repository.Replace("ppy/", "")}#{entry.GithubPullRequestId}", entry.GithubUrl, creationParameters: t => { t.Font = fontLarge; @@ -140,7 +140,7 @@ namespace osu.Game.Overlays.Changelog t.Colour = entryColour; }); else if (entry.GithubUser.GithubUrl != null) - title.AddLink(entry.GithubUser.DisplayName, entry.GithubUser.GithubUrl, Online.Chat.LinkAction.External, null, null, t => + title.AddLink(entry.GithubUser.DisplayName, entry.GithubUser.GithubUrl, t => { t.Font = fontMedium; t.Colour = entryColour; diff --git a/osu.Game/Screens/Multi/Components/BeatmapTitle.cs b/osu.Game/Screens/Multi/Components/BeatmapTitle.cs index e096fb33da..f79cac7649 100644 --- a/osu.Game/Screens/Multi/Components/BeatmapTitle.cs +++ b/osu.Game/Screens/Multi/Components/BeatmapTitle.cs @@ -81,7 +81,7 @@ namespace osu.Game.Screens.Multi.Components Text = new LocalisedString((beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title)), Font = OsuFont.GetFont(size: TextSize), } - }, null, LinkAction.OpenBeatmap, beatmap.OnlineBeatmapID.ToString(), "Open beatmap"); + }, LinkAction.OpenBeatmap, beatmap.OnlineBeatmapID.ToString(), "Open beatmap"); } } } diff --git a/osu.iOS/AppDelegate.cs b/osu.iOS/AppDelegate.cs index 07e0245195..164a182ebe 100644 --- a/osu.iOS/AppDelegate.cs +++ b/osu.iOS/AppDelegate.cs @@ -21,7 +21,7 @@ namespace osu.iOS if (url.IsFileUrl) Task.Run(() => game.Import(url.Path)); else - Task.Run(() => game.HandleUrl(url.AbsoluteString)); + Task.Run(() => game.HandleLink(url.AbsoluteString)); return true; } } From f13dc93a4d867352797046d7a5b46a9c941eaf06 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Oct 2019 18:49:13 +0900 Subject: [PATCH 079/177] Fix external link tooltip regression --- osu.Game/Graphics/Containers/LinkFlowContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index b5ffd1c6ad..61391b7102 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -52,7 +52,7 @@ namespace osu.Game.Graphics.Containers } public void AddLink(string text, string url, Action creationParameters = null) => - createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.External, url), text); + createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.External, url), url); public void AddLink(string text, Action action, string tooltipText = null, Action creationParameters = null) => createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.Custom, null), tooltipText, action); From e3ca64bb8c6dad982b579d126fbaefa402629c04 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Thu, 31 Oct 2019 19:18:54 +0900 Subject: [PATCH 080/177] Remove iOS "import from stable" for now --- osu.Game/Database/ArchiveModelManager.cs | 2 +- osu.iOS/Info.plist | 8 +++----- osu.iOS/OsuGameIOS.cs | 2 -- 3 files changed, 4 insertions(+), 8 deletions(-) diff --git a/osu.Game/Database/ArchiveModelManager.cs b/osu.Game/Database/ArchiveModelManager.cs index 3c1e07b765..9fed8e03ac 100644 --- a/osu.Game/Database/ArchiveModelManager.cs +++ b/osu.Game/Database/ArchiveModelManager.cs @@ -67,7 +67,7 @@ namespace osu.Game.Database public virtual string[] HandledExtensions => new[] { ".zip" }; - public virtual bool SupportsImportFromStable => (RuntimeInfo.IsDesktop || RuntimeInfo.OS == RuntimeInfo.Platform.iOS); + public virtual bool SupportsImportFromStable => RuntimeInfo.IsDesktop; protected readonly FileStore Files; diff --git a/osu.iOS/Info.plist b/osu.iOS/Info.plist index 67f71b64b6..5ceccdf99f 100644 --- a/osu.iOS/Info.plist +++ b/osu.iOS/Info.plist @@ -34,9 +34,9 @@ UIStatusBarHidden NSCameraUsageDescription - We don't really use the camera. - NSMicrophoneUsageDescription - We don't really use the microphone. + We don't really use the camera. + NSMicrophoneUsageDescription + We don't really use the microphone. UISupportedInterfaceOrientations UIInterfaceOrientationPortrait @@ -111,8 +111,6 @@ - UIFileSharingEnabled - CFBundleURLTypes diff --git a/osu.iOS/OsuGameIOS.cs b/osu.iOS/OsuGameIOS.cs index ac66357fc9..6cf18df9a6 100644 --- a/osu.iOS/OsuGameIOS.cs +++ b/osu.iOS/OsuGameIOS.cs @@ -3,14 +3,12 @@ using System; using Foundation; -using osu.Framework.Platform; using osu.Game; namespace osu.iOS { public class OsuGameIOS : OsuGame { - public override Storage GetStorageForStableInstall() => Storage; public override Version AssemblyVersion => new Version(NSBundle.MainBundle.InfoDictionary["CFBundleVersion"].ToString()); } } From ba65d0e4ee712fcc1f87b3f07205db05c1406c26 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Thu, 31 Oct 2019 12:39:09 +0000 Subject: [PATCH 081/177] Force ordering of issue templates --- .github/ISSUE_TEMPLATE/{mobile-issues.md => 00-mobile-issues.md} | 0 .github/ISSUE_TEMPLATE/{bug-issues.md => 01-bug-issues.md} | 0 .github/ISSUE_TEMPLATE/{crash-issues.md => 02-crash-issues.md} | 0 .../{missing-for-live-issues.md => 03-missing-for-live.md} | 0 .../{feature-request-issues.md => 04-feature-request-issues.md} | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename .github/ISSUE_TEMPLATE/{mobile-issues.md => 00-mobile-issues.md} (100%) rename .github/ISSUE_TEMPLATE/{bug-issues.md => 01-bug-issues.md} (100%) rename .github/ISSUE_TEMPLATE/{crash-issues.md => 02-crash-issues.md} (100%) rename .github/ISSUE_TEMPLATE/{missing-for-live-issues.md => 03-missing-for-live.md} (100%) rename .github/ISSUE_TEMPLATE/{feature-request-issues.md => 04-feature-request-issues.md} (100%) diff --git a/.github/ISSUE_TEMPLATE/mobile-issues.md b/.github/ISSUE_TEMPLATE/00-mobile-issues.md similarity index 100% rename from .github/ISSUE_TEMPLATE/mobile-issues.md rename to .github/ISSUE_TEMPLATE/00-mobile-issues.md diff --git a/.github/ISSUE_TEMPLATE/bug-issues.md b/.github/ISSUE_TEMPLATE/01-bug-issues.md similarity index 100% rename from .github/ISSUE_TEMPLATE/bug-issues.md rename to .github/ISSUE_TEMPLATE/01-bug-issues.md diff --git a/.github/ISSUE_TEMPLATE/crash-issues.md b/.github/ISSUE_TEMPLATE/02-crash-issues.md similarity index 100% rename from .github/ISSUE_TEMPLATE/crash-issues.md rename to .github/ISSUE_TEMPLATE/02-crash-issues.md diff --git a/.github/ISSUE_TEMPLATE/missing-for-live-issues.md b/.github/ISSUE_TEMPLATE/03-missing-for-live.md similarity index 100% rename from .github/ISSUE_TEMPLATE/missing-for-live-issues.md rename to .github/ISSUE_TEMPLATE/03-missing-for-live.md diff --git a/.github/ISSUE_TEMPLATE/feature-request-issues.md b/.github/ISSUE_TEMPLATE/04-feature-request-issues.md similarity index 100% rename from .github/ISSUE_TEMPLATE/feature-request-issues.md rename to .github/ISSUE_TEMPLATE/04-feature-request-issues.md From 08b1631dbbfe496c0df15ad8aa2632dfa20b8e9f Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Thu, 31 Oct 2019 12:42:25 +0000 Subject: [PATCH 082/177] Add issue template config --- .github/ISSUE_TEMPLATE/config.yml | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .github/ISSUE_TEMPLATE/config.yml diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..69baeee60c --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1,5 @@ +blank_issues_enabled: false +contact_links: + - name: osu!stable issues + url: https://github.com/ppy/osu-stable-issues + about: For issues regarding osu!stable (not osu!lazer), open them here. From 3ada1510d04f635ae36050060b8534f66c5e2a08 Mon Sep 17 00:00:00 2001 From: Jai Sharma Date: Thu, 31 Oct 2019 13:09:36 +0000 Subject: [PATCH 083/177] Remove 'missing-for-live.md' --- ...ture-request-issues.md => 03-feature-request-issues.md} | 0 .github/ISSUE_TEMPLATE/03-missing-for-live.md | 7 ------- 2 files changed, 7 deletions(-) rename .github/ISSUE_TEMPLATE/{04-feature-request-issues.md => 03-feature-request-issues.md} (100%) delete mode 100644 .github/ISSUE_TEMPLATE/03-missing-for-live.md diff --git a/.github/ISSUE_TEMPLATE/04-feature-request-issues.md b/.github/ISSUE_TEMPLATE/03-feature-request-issues.md similarity index 100% rename from .github/ISSUE_TEMPLATE/04-feature-request-issues.md rename to .github/ISSUE_TEMPLATE/03-feature-request-issues.md diff --git a/.github/ISSUE_TEMPLATE/03-missing-for-live.md b/.github/ISSUE_TEMPLATE/03-missing-for-live.md deleted file mode 100644 index 5822da9c65..0000000000 --- a/.github/ISSUE_TEMPLATE/03-missing-for-live.md +++ /dev/null @@ -1,7 +0,0 @@ ---- -name: Missing for Live -about: Features which are available in osu!stable but not yet in osu!lazer. ---- -**Describe the missing feature:** - -**Proposal designs of the feature:** From 944380906324d53936b67b04b30e9a01097bdb42 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 11:22:32 +0900 Subject: [PATCH 084/177] Protect against requests to show overlays before the target overlay is ready --- osu.Game/OsuGame.cs | 77 ++++++++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 22 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b55cc41454..fb9a7f7965 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -215,16 +215,20 @@ namespace osu.Game private ExternalLinkOpener externalLinkOpener; - public void HandleLink(string url) => HandleLink(MessageFormatter.GetLinkDetails(url)); + /// + /// Handle an arbitrary URL. Displays via in-game overlays where possible. + /// This can be called from a non-thread-safe non-game-loaded state. + /// + /// The URL to load. + public void HandleLink(string url) => Schedule(() => HandleLink(MessageFormatter.GetLinkDetails(url))); + /// + /// Handle a specific . + /// This can be called from a non-thread-safe non-game-loaded state. + /// + /// The link to load. public void HandleLink(LinkDetails link) { - Action showNotImplementedError = () => notifications?.Post(new SimpleNotification - { - Text = @"This link type is not yet supported!", - Icon = FontAwesome.Solid.LifeRing, - }); - switch (link.Action) { case LinkAction.OpenBeatmap: @@ -239,21 +243,17 @@ namespace osu.Game break; case LinkAction.OpenChannel: - try - { - channelManager.OpenChannel(link.Argument); - } - catch (ChannelNotFoundException) - { - Logger.Log($"The requested channel \"{link.Argument}\" does not exist"); - } - + ShowChannel(link.Argument); break; case LinkAction.OpenEditorTimestamp: case LinkAction.JoinMultiplayerMatch: case LinkAction.Spectate: - showNotImplementedError.Invoke(); + waitForReady(() => notifications, _ => notifications?.Post(new SimpleNotification + { + Text = @"This link type is not yet supported!", + Icon = FontAwesome.Solid.LifeRing, + })); break; case LinkAction.External: @@ -270,31 +270,47 @@ namespace osu.Game } } - public void OpenUrlExternally(string url) + public void OpenUrlExternally(string url) => waitForReady(() => externalLinkOpener, _ => { if (url.StartsWith("/")) url = $"{API.Endpoint}{url}"; externalLinkOpener.OpenUrlExternally(url); - } + }); + + /// + /// Open a specific channel in chat. + /// + /// The channel to display. + public void ShowChannel(string channel) => waitForReady(() => channelManager, _ => + { + try + { + channelManager.OpenChannel(channel); + } + catch (ChannelNotFoundException) + { + Logger.Log($"The requested channel \"{channel}\" does not exist"); + } + }); /// /// Show a beatmap set as an overlay. /// /// The set to display. - public void ShowBeatmapSet(int setId) => beatmapSetOverlay.FetchAndShowBeatmapSet(setId); + public void ShowBeatmapSet(int setId) => waitForReady(() => beatmapSetOverlay, _ => beatmapSetOverlay.FetchAndShowBeatmapSet(setId)); /// /// Show a user's profile as an overlay. /// /// The user to display. - public void ShowUser(long userId) => userProfile.ShowUser(userId); + public void ShowUser(long userId) => waitForReady(() => userProfile, _ => userProfile.ShowUser(userId)); /// /// Show a beatmap's set as an overlay, displaying the given beatmap. /// /// The beatmap to show. - public void ShowBeatmap(int beatmapId) => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId); + public void ShowBeatmap(int beatmapId) => waitForReady(() => beatmapSetOverlay, _ => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId)); /// /// Present a beatmap at song select immediately. @@ -452,6 +468,23 @@ namespace osu.Game performFromMainMenuTask = Schedule(() => performFromMainMenu(action, taskName)); } + /// + /// Wait for the game (and target component) to become loaded and then run an action. + /// + /// A function to retrieve a (potentially not-yet-constructed) target instance. + /// The action to perform on the instance when load is confirmed. + /// The type of the target instance. + private void waitForReady(Func retrieveInstance, Action action) + where T : Drawable + { + var instance = retrieveInstance(); + + if (ScreenStack == null || ScreenStack.CurrentScreen is StartupScreen || instance?.IsLoaded != true) + Schedule(() => waitForReady(retrieveInstance, action)); + else + action(instance); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 898520935ec5c26994ac209ccd8229eb362b880e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 11:40:51 +0900 Subject: [PATCH 085/177] Move link handling code to OsuGame This allows for future calls from arguments / associations --- .../Graphics/Containers/LinkFlowContainer.cs | 92 +++++-------------- osu.Game/Online/Chat/MessageFormatter.cs | 25 ++--- osu.Game/OsuGame.cs | 55 +++++++++++ osu.Game/Overlays/Changelog/ChangelogBuild.cs | 4 +- .../Screens/Multi/Components/BeatmapTitle.cs | 2 +- 5 files changed, 92 insertions(+), 86 deletions(-) diff --git a/osu.Game/Graphics/Containers/LinkFlowContainer.cs b/osu.Game/Graphics/Containers/LinkFlowContainer.cs index 15068d81c0..61391b7102 100644 --- a/osu.Game/Graphics/Containers/LinkFlowContainer.cs +++ b/osu.Game/Graphics/Containers/LinkFlowContainer.cs @@ -8,9 +8,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics.Sprites; using System.Collections.Generic; using osu.Framework.Graphics; -using osu.Framework.Logging; -using osu.Game.Overlays; -using osu.Game.Overlays.Notifications; using osu.Game.Users; namespace osu.Game.Graphics.Containers @@ -23,21 +20,12 @@ namespace osu.Game.Graphics.Containers } private OsuGame game; - private ChannelManager channelManager; - private Action showNotImplementedError; [BackgroundDependencyLoader(true)] - private void load(OsuGame game, NotificationOverlay notifications, ChannelManager channelManager) + private void load(OsuGame game) { // will be null in tests this.game = game; - this.channelManager = channelManager; - - showNotImplementedError = () => notifications?.Post(new SimpleNotification - { - Text = @"This link type is not yet supported!", - Icon = FontAwesome.Solid.LifeRing, - }); } public void AddLinks(string text, List links) @@ -56,85 +44,47 @@ namespace osu.Game.Graphics.Containers foreach (var link in links) { AddText(text.Substring(previousLinkEnd, link.Index - previousLinkEnd)); - AddLink(text.Substring(link.Index, link.Length), link.Url, link.Action, link.Argument); + AddLink(text.Substring(link.Index, link.Length), link.Action, link.Argument ?? link.Url); previousLinkEnd = link.Index + link.Length; } AddText(text.Substring(previousLinkEnd)); } - public IEnumerable AddLink(string text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null, Action creationParameters = null) - => createLink(AddText(text, creationParameters), text, url, linkType, linkArgument, tooltipText); + public void AddLink(string text, string url, Action creationParameters = null) => + createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.External, url), url); - public IEnumerable AddLink(string text, Action action, string tooltipText = null, Action creationParameters = null) - => createLink(AddText(text, creationParameters), text, tooltipText: tooltipText, action: action); + public void AddLink(string text, Action action, string tooltipText = null, Action creationParameters = null) + => createLink(AddText(text, creationParameters), new LinkDetails(LinkAction.Custom, null), tooltipText, action); - public IEnumerable AddLink(IEnumerable text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null) + public void AddLink(string text, LinkAction action, string argument, string tooltipText = null, Action creationParameters = null) + => createLink(AddText(text, creationParameters), new LinkDetails(action, argument), null); + + public void AddLink(IEnumerable text, LinkAction action = LinkAction.External, string linkArgument = null, string tooltipText = null) { foreach (var t in text) AddArbitraryDrawable(t); - return createLink(text, null, url, linkType, linkArgument, tooltipText); + createLink(text, new LinkDetails(action, linkArgument), tooltipText); } - public IEnumerable AddUserLink(User user, Action creationParameters = null) - => createLink(AddText(user.Username, creationParameters), user.Username, null, LinkAction.OpenUserProfile, user.Id.ToString(), "View profile"); + public void AddUserLink(User user, Action creationParameters = null) + => createLink(AddText(user.Username, creationParameters), new LinkDetails(LinkAction.OpenUserProfile, user.Id.ToString()), "View Profile"); - private IEnumerable createLink(IEnumerable drawables, string text, string url = null, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null, Action action = null) + private void createLink(IEnumerable drawables, LinkDetails link, string tooltipText, Action action = null) { AddInternal(new DrawableLinkCompiler(drawables.OfType().ToList()) { RelativeSizeAxes = Axes.Both, - TooltipText = tooltipText ?? (url != text ? url : string.Empty), - Action = action ?? (() => + TooltipText = tooltipText, + Action = () => { - switch (linkType) - { - case LinkAction.OpenBeatmap: - // TODO: proper query params handling - if (linkArgument != null && int.TryParse(linkArgument.Contains('?') ? linkArgument.Split('?')[0] : linkArgument, out int beatmapId)) - game?.ShowBeatmap(beatmapId); - break; - - case LinkAction.OpenBeatmapSet: - if (int.TryParse(linkArgument, out int setId)) - game?.ShowBeatmapSet(setId); - break; - - case LinkAction.OpenChannel: - try - { - channelManager?.OpenChannel(linkArgument); - } - catch (ChannelNotFoundException) - { - Logger.Log($"The requested channel \"{linkArgument}\" does not exist"); - } - - break; - - case LinkAction.OpenEditorTimestamp: - case LinkAction.JoinMultiplayerMatch: - case LinkAction.Spectate: - showNotImplementedError?.Invoke(); - break; - - case LinkAction.External: - game?.OpenUrlExternally(url); - break; - - case LinkAction.OpenUserProfile: - if (long.TryParse(linkArgument, out long userId)) - game?.ShowUser(userId); - break; - - default: - throw new NotImplementedException($"This {nameof(LinkAction)} ({linkType.ToString()}) is missing an associated action."); - } - }), + if (action != null) + action(); + else + game.HandleLink(link); + }, }); - - return drawables; } // We want the compilers to always be visible no matter where they are, so RelativeSizeAxes is used. diff --git a/osu.Game/Online/Chat/MessageFormatter.cs b/osu.Game/Online/Chat/MessageFormatter.cs index 3ffff281f8..717de18c14 100644 --- a/osu.Game/Online/Chat/MessageFormatter.cs +++ b/osu.Game/Online/Chat/MessageFormatter.cs @@ -81,7 +81,7 @@ namespace osu.Game.Online.Chat //since we just changed the line display text, offset any already processed links. result.Links.ForEach(l => l.Index -= l.Index > index ? m.Length - displayText.Length : 0); - var details = getLinkDetails(linkText); + var details = GetLinkDetails(linkText); result.Links.Add(new Link(linkText, index, displayText.Length, linkActionOverride ?? details.Action, details.Argument)); //adjust the offset for processing the current matches group. @@ -98,7 +98,7 @@ namespace osu.Game.Online.Chat var linkText = m.Groups["link"].Value; var indexLength = linkText.Length; - var details = getLinkDetails(linkText); + var details = GetLinkDetails(linkText); var link = new Link(linkText, index, indexLength, details.Action, details.Argument); // sometimes an already-processed formatted link can reduce to a simple URL, too @@ -109,7 +109,7 @@ namespace osu.Game.Online.Chat } } - private static LinkDetails getLinkDetails(string url) + public static LinkDetails GetLinkDetails(string url) { var args = url.Split(new[] { '/' }, StringSplitOptions.RemoveEmptyEntries); args[0] = args[0].TrimEnd(':'); @@ -255,17 +255,17 @@ namespace osu.Game.Online.Chat OriginalText = Text = text; } } + } - public class LinkDetails + public class LinkDetails + { + public LinkAction Action; + public string Argument; + + public LinkDetails(LinkAction action, string argument) { - public LinkAction Action; - public string Argument; - - public LinkDetails(LinkAction action, string argument) - { - Action = action; - Argument = argument; - } + Action = action; + Argument = argument; } } @@ -279,6 +279,7 @@ namespace osu.Game.Online.Chat JoinMultiplayerMatch, Spectate, OpenUserProfile, + Custom } public class Link : IComparable diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index 4dcc181bea..b55cc41454 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -215,6 +215,61 @@ namespace osu.Game private ExternalLinkOpener externalLinkOpener; + public void HandleLink(string url) => HandleLink(MessageFormatter.GetLinkDetails(url)); + + public void HandleLink(LinkDetails link) + { + Action showNotImplementedError = () => notifications?.Post(new SimpleNotification + { + Text = @"This link type is not yet supported!", + Icon = FontAwesome.Solid.LifeRing, + }); + + switch (link.Action) + { + case LinkAction.OpenBeatmap: + // TODO: proper query params handling + if (link.Argument != null && int.TryParse(link.Argument.Contains('?') ? link.Argument.Split('?')[0] : link.Argument, out int beatmapId)) + ShowBeatmap(beatmapId); + break; + + case LinkAction.OpenBeatmapSet: + if (int.TryParse(link.Argument, out int setId)) + ShowBeatmapSet(setId); + break; + + case LinkAction.OpenChannel: + try + { + channelManager.OpenChannel(link.Argument); + } + catch (ChannelNotFoundException) + { + Logger.Log($"The requested channel \"{link.Argument}\" does not exist"); + } + + break; + + case LinkAction.OpenEditorTimestamp: + case LinkAction.JoinMultiplayerMatch: + case LinkAction.Spectate: + showNotImplementedError.Invoke(); + break; + + case LinkAction.External: + OpenUrlExternally(link.Argument); + break; + + case LinkAction.OpenUserProfile: + if (long.TryParse(link.Argument, out long userId)) + ShowUser(userId); + break; + + default: + throw new NotImplementedException($"This {nameof(LinkAction)} ({link.Action.ToString()}) is missing an associated action."); + } + } + public void OpenUrlExternally(string url) { if (url.StartsWith("/")) diff --git a/osu.Game/Overlays/Changelog/ChangelogBuild.cs b/osu.Game/Overlays/Changelog/ChangelogBuild.cs index bce1be5941..d8488b21ab 100644 --- a/osu.Game/Overlays/Changelog/ChangelogBuild.cs +++ b/osu.Game/Overlays/Changelog/ChangelogBuild.cs @@ -110,7 +110,7 @@ namespace osu.Game.Overlays.Changelog t.Font = fontLarge; t.Colour = entryColour; }); - title.AddLink($"{entry.Repository.Replace("ppy/", "")}#{entry.GithubPullRequestId}", entry.GithubUrl, Online.Chat.LinkAction.External, + title.AddLink($"{entry.Repository.Replace("ppy/", "")}#{entry.GithubPullRequestId}", entry.GithubUrl, creationParameters: t => { t.Font = fontLarge; @@ -140,7 +140,7 @@ namespace osu.Game.Overlays.Changelog t.Colour = entryColour; }); else if (entry.GithubUser.GithubUrl != null) - title.AddLink(entry.GithubUser.DisplayName, entry.GithubUser.GithubUrl, Online.Chat.LinkAction.External, null, null, t => + title.AddLink(entry.GithubUser.DisplayName, entry.GithubUser.GithubUrl, t => { t.Font = fontMedium; t.Colour = entryColour; diff --git a/osu.Game/Screens/Multi/Components/BeatmapTitle.cs b/osu.Game/Screens/Multi/Components/BeatmapTitle.cs index e096fb33da..f79cac7649 100644 --- a/osu.Game/Screens/Multi/Components/BeatmapTitle.cs +++ b/osu.Game/Screens/Multi/Components/BeatmapTitle.cs @@ -81,7 +81,7 @@ namespace osu.Game.Screens.Multi.Components Text = new LocalisedString((beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title)), Font = OsuFont.GetFont(size: TextSize), } - }, null, LinkAction.OpenBeatmap, beatmap.OnlineBeatmapID.ToString(), "Open beatmap"); + }, LinkAction.OpenBeatmap, beatmap.OnlineBeatmapID.ToString(), "Open beatmap"); } } } From f038c579f01b89327c57f0ff307c0917957c9667 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 11:22:32 +0900 Subject: [PATCH 086/177] Protect against requests to show overlays before the target overlay is ready --- osu.Game/OsuGame.cs | 77 ++++++++++++++++++++++++++++++++------------- 1 file changed, 55 insertions(+), 22 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index b55cc41454..fb9a7f7965 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -215,16 +215,20 @@ namespace osu.Game private ExternalLinkOpener externalLinkOpener; - public void HandleLink(string url) => HandleLink(MessageFormatter.GetLinkDetails(url)); + /// + /// Handle an arbitrary URL. Displays via in-game overlays where possible. + /// This can be called from a non-thread-safe non-game-loaded state. + /// + /// The URL to load. + public void HandleLink(string url) => Schedule(() => HandleLink(MessageFormatter.GetLinkDetails(url))); + /// + /// Handle a specific . + /// This can be called from a non-thread-safe non-game-loaded state. + /// + /// The link to load. public void HandleLink(LinkDetails link) { - Action showNotImplementedError = () => notifications?.Post(new SimpleNotification - { - Text = @"This link type is not yet supported!", - Icon = FontAwesome.Solid.LifeRing, - }); - switch (link.Action) { case LinkAction.OpenBeatmap: @@ -239,21 +243,17 @@ namespace osu.Game break; case LinkAction.OpenChannel: - try - { - channelManager.OpenChannel(link.Argument); - } - catch (ChannelNotFoundException) - { - Logger.Log($"The requested channel \"{link.Argument}\" does not exist"); - } - + ShowChannel(link.Argument); break; case LinkAction.OpenEditorTimestamp: case LinkAction.JoinMultiplayerMatch: case LinkAction.Spectate: - showNotImplementedError.Invoke(); + waitForReady(() => notifications, _ => notifications?.Post(new SimpleNotification + { + Text = @"This link type is not yet supported!", + Icon = FontAwesome.Solid.LifeRing, + })); break; case LinkAction.External: @@ -270,31 +270,47 @@ namespace osu.Game } } - public void OpenUrlExternally(string url) + public void OpenUrlExternally(string url) => waitForReady(() => externalLinkOpener, _ => { if (url.StartsWith("/")) url = $"{API.Endpoint}{url}"; externalLinkOpener.OpenUrlExternally(url); - } + }); + + /// + /// Open a specific channel in chat. + /// + /// The channel to display. + public void ShowChannel(string channel) => waitForReady(() => channelManager, _ => + { + try + { + channelManager.OpenChannel(channel); + } + catch (ChannelNotFoundException) + { + Logger.Log($"The requested channel \"{channel}\" does not exist"); + } + }); /// /// Show a beatmap set as an overlay. /// /// The set to display. - public void ShowBeatmapSet(int setId) => beatmapSetOverlay.FetchAndShowBeatmapSet(setId); + public void ShowBeatmapSet(int setId) => waitForReady(() => beatmapSetOverlay, _ => beatmapSetOverlay.FetchAndShowBeatmapSet(setId)); /// /// Show a user's profile as an overlay. /// /// The user to display. - public void ShowUser(long userId) => userProfile.ShowUser(userId); + public void ShowUser(long userId) => waitForReady(() => userProfile, _ => userProfile.ShowUser(userId)); /// /// Show a beatmap's set as an overlay, displaying the given beatmap. /// /// The beatmap to show. - public void ShowBeatmap(int beatmapId) => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId); + public void ShowBeatmap(int beatmapId) => waitForReady(() => beatmapSetOverlay, _ => beatmapSetOverlay.FetchAndShowBeatmap(beatmapId)); /// /// Present a beatmap at song select immediately. @@ -452,6 +468,23 @@ namespace osu.Game performFromMainMenuTask = Schedule(() => performFromMainMenu(action, taskName)); } + /// + /// Wait for the game (and target component) to become loaded and then run an action. + /// + /// A function to retrieve a (potentially not-yet-constructed) target instance. + /// The action to perform on the instance when load is confirmed. + /// The type of the target instance. + private void waitForReady(Func retrieveInstance, Action action) + where T : Drawable + { + var instance = retrieveInstance(); + + if (ScreenStack == null || ScreenStack.CurrentScreen is StartupScreen || instance?.IsLoaded != true) + Schedule(() => waitForReady(retrieveInstance, action)); + else + action(instance); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From 0171b2ae7c0882e2d0ad7e499d4e4369b580af6b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 12:10:03 +0900 Subject: [PATCH 087/177] Fix scrolling hitobjects expiring too soon --- .../UI/Scrolling/ScrollingHitObjectContainer.cs | 16 ++-------------- 1 file changed, 2 insertions(+), 14 deletions(-) diff --git a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs index e00597dd56..857929ff9e 100644 --- a/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs +++ b/osu.Game/Rulesets/UI/Scrolling/ScrollingHitObjectContainer.cs @@ -13,12 +13,6 @@ namespace osu.Game.Rulesets.UI.Scrolling { public class ScrollingHitObjectContainer : HitObjectContainer { - /// - /// A multiplier applied to the length of the scrolling area to determine a safe default lifetime end for hitobjects. - /// This is only used to limit the lifetime end within reason, as proper lifetime management should be implemented on hitobjects themselves. - /// - private const float safe_lifetime_end_multiplier = 2; - private readonly IBindable timeRange = new BindableDouble(); private readonly IBindable direction = new Bindable(); @@ -123,28 +117,22 @@ namespace osu.Game.Rulesets.UI.Scrolling if (cached.IsValid) return; - double endTime = hitObject.HitObject.StartTime; - if (hitObject.HitObject is IHasEndTime e) { - endTime = e.EndTime; - switch (direction.Value) { case ScrollingDirection.Up: case ScrollingDirection.Down: - hitObject.Height = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, endTime, timeRange.Value, scrollLength); + hitObject.Height = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, e.EndTime, timeRange.Value, scrollLength); break; case ScrollingDirection.Left: case ScrollingDirection.Right: - hitObject.Width = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, endTime, timeRange.Value, scrollLength); + hitObject.Width = scrollingInfo.Algorithm.GetLength(hitObject.HitObject.StartTime, e.EndTime, timeRange.Value, scrollLength); break; } } - hitObject.LifetimeEnd = scrollingInfo.Algorithm.TimeAt(scrollLength * safe_lifetime_end_multiplier, endTime, timeRange.Value, scrollLength); - foreach (var obj in hitObject.NestedHitObjects) { computeInitialStateRecursive(obj); From ac02bb005d982603c55002d8212af592453a584d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 14:11:18 +0900 Subject: [PATCH 088/177] Fix GameplayClockContainer operating on beatmap's track after scren exited --- osu.Game/Screens/Play/GameplayClockContainer.cs | 15 ++++++++++++++- osu.Game/Screens/Play/Player.cs | 4 ++++ 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index 6a03271b86..f2efbe6073 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -8,6 +8,7 @@ using System.Threading.Tasks; using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Audio; +using osu.Framework.Audio.Track; using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -29,7 +30,7 @@ namespace osu.Game.Screens.Play /// /// The original source (usually a 's track). /// - private readonly IAdjustableClock sourceClock; + private IAdjustableClock sourceClock; public readonly BindableBool IsPaused = new BindableBool(); @@ -153,6 +154,18 @@ namespace osu.Game.Screens.Play IsPaused.Value = true; } + /// + /// Changes the backing clock to avoid using the originally provided beatmap's track. + /// + public void StopUsingBeatmapClock() + { + if (sourceClock != beatmap.Track) + return; + + sourceClock = new TrackVirtual(beatmap.Track.Length); + adjustableClock.ChangeSource(sourceClock); + } + public void ResetLocalAdjustments() { // In the case of replays, we may have changed the playback rate. diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 0b363eac4d..4820c62da3 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -527,6 +527,10 @@ namespace osu.Game.Screens.Play GameplayClockContainer.ResetLocalAdjustments(); + // GameplayClockContainer performs seeks / start / stop operations on the beatmap's track. + // as we are no longer the current screen, we cannot guarantee the track is still usable. + GameplayClockContainer.StopUsingBeatmapClock(); + fadeOut(); return base.OnExiting(next); } From dcc8f6e82799b3fe7055068b446780ad5ed76113 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 14:43:52 +0900 Subject: [PATCH 089/177] Better group cancel conditional --- osu.Game/Screens/Play/Player.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 7eccde4555..280ebca651 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -446,9 +446,9 @@ namespace osu.Game.Screens.Play if (IsResuming) { DrawableRuleset.CancelResume(); + IsResuming = false; } - IsResuming = false; GameplayClockContainer.Stop(); PauseOverlay.Show(); lastPauseActionTime = GameplayClockContainer.GameplayClock.CurrentTime; From 05002ea3e8cd308e46e8147f909920ebd56300b3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 14:50:38 +0900 Subject: [PATCH 090/177] Move Show/Hide code to PopIn/PopOut --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index 8347d255fa..aecfac3b70 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -38,9 +38,10 @@ namespace osu.Game.Rulesets.Osu.UI }); } - public override void Show() + protected override void PopIn() { - base.Show(); + base.PopIn(); + GameplayCursor.ActiveCursor.Hide(); cursorScaleContainer.MoveTo(GameplayCursor.ActiveCursor.Position); clickToResumeCursor.Appear(); @@ -55,13 +56,13 @@ namespace osu.Game.Rulesets.Osu.UI } } - public override void Hide() + protected override void PopOut() { + base.PopOut(); + localCursorContainer?.Expire(); localCursorContainer = null; - GameplayCursor.ActiveCursor.Show(); - - base.Hide(); + GameplayCursor?.ActiveCursor.Show(); } protected override bool OnHover(HoverEvent e) => true; From 14fec6f1f35e4cf9e9a0e65a535e407c68ba9ed5 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 15:06:36 +0900 Subject: [PATCH 091/177] Move ReplayDownloadButton to correct namespace --- .../Visual/Gameplay/TestSceneReplayDownloadButton.cs | 2 +- osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs | 1 + .../{Play => Ranking/Pages}/ReplayDownloadButton.cs | 8 ++++---- 3 files changed, 6 insertions(+), 5 deletions(-) rename osu.Game/Screens/{Play => Ranking/Pages}/ReplayDownloadButton.cs (98%) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 0dfcda122f..4c870e04f5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -7,11 +7,11 @@ using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; -using osu.Game.Screens.Play; using osu.Game.Users; using osuTK; using System; using System.Collections.Generic; +using osu.Game.Screens.Ranking.Pages; namespace osu.Game.Tests.Visual.Gameplay { diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs index f3c8f89db7..c4b42f6356 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs @@ -26,6 +26,7 @@ namespace osu.Game.Tests.Visual.Gameplay typeof(Results), typeof(ResultsPage), typeof(ScoreResultsPage), + typeof(ReplayDownloadButton), typeof(LocalLeaderboardPage) }; diff --git a/osu.Game/Screens/Play/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/Pages/ReplayDownloadButton.cs similarity index 98% rename from osu.Game/Screens/Play/ReplayDownloadButton.cs rename to osu.Game/Screens/Ranking/Pages/ReplayDownloadButton.cs index 290e00f287..c8f4ecd2d5 100644 --- a/osu.Game/Screens/Play/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/Pages/ReplayDownloadButton.cs @@ -4,12 +4,12 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Online; -using osu.Game.Scoring; -using osu.Game.Online.API.Requests.Responses; using osu.Game.Graphics.UserInterface; +using osu.Game.Online; +using osu.Game.Online.API.Requests.Responses; +using osu.Game.Scoring; -namespace osu.Game.Screens.Play +namespace osu.Game.Screens.Ranking.Pages { public class ReplayDownloadButton : DownloadTrackingComposite { From e23ea94383e293c26764125637ef4627232f436f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 15:33:38 +0900 Subject: [PATCH 092/177] Add one more level of null check --- osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs index aecfac3b70..3b18e41f30 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuResumeOverlay.cs @@ -62,7 +62,7 @@ namespace osu.Game.Rulesets.Osu.UI localCursorContainer?.Expire(); localCursorContainer = null; - GameplayCursor?.ActiveCursor.Show(); + GameplayCursor?.ActiveCursor?.Show(); } protected override bool OnHover(HoverEvent e) => true; From 84d17f3702bdf25f37a1bfffa34a216a8ab031f0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 15:32:06 +0900 Subject: [PATCH 093/177] Add retry button --- .../Gameplay/TestSceneReplayDownloadButton.cs | 2 - .../Visual/Gameplay/TestSceneResults.cs | 2 +- osu.Game/Screens/Play/Player.cs | 8 +-- .../Ranking/Pages/ReplayDownloadButton.cs | 2 + osu.Game/Screens/Ranking/Pages/RetryButton.cs | 59 +++++++++++++++++++ .../Screens/Ranking/Pages/ScoreResultsPage.cs | 11 +++- 6 files changed, 75 insertions(+), 9 deletions(-) create mode 100644 osu.Game/Screens/Ranking/Pages/RetryButton.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs index 4c870e04f5..7b22fedbd5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneReplayDownloadButton.cs @@ -8,7 +8,6 @@ using osu.Game.Online.API.Requests.Responses; using osu.Game.Rulesets.Osu; using osu.Game.Scoring; using osu.Game.Users; -using osuTK; using System; using System.Collections.Generic; using osu.Game.Screens.Ranking.Pages; @@ -42,7 +41,6 @@ namespace osu.Game.Tests.Visual.Gameplay { Anchor = Anchor.Centre, Origin = Anchor.Centre, - Size = new Vector2(80, 40), }; }); } diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs index c4b42f6356..bf26892539 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs @@ -22,10 +22,10 @@ namespace osu.Game.Tests.Visual.Gameplay public override IReadOnlyList RequiredTypes => new[] { - typeof(ScoreInfo), typeof(Results), typeof(ResultsPage), typeof(ScoreResultsPage), + typeof(RetryButton), typeof(ReplayDownloadButton), typeof(LocalLeaderboardPage) }; diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 0b363eac4d..38068ec751 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -30,6 +30,7 @@ using osu.Game.Users; namespace osu.Game.Screens.Play { + [Cached] public class Player : ScreenWithBeatmapBackground { public override bool AllowBackButton => false; // handled by HoldForMenuButton @@ -313,12 +314,11 @@ namespace osu.Game.Screens.Play public void Restart() { - if (!this.IsCurrentScreen()) return; - sampleRestart?.Play(); - RestartRequested?.Invoke(); - performImmediateExit(); + + if (this.IsCurrentScreen()) + performImmediateExit(); } private ScheduledDelegate completionProgressDelegate; diff --git a/osu.Game/Screens/Ranking/Pages/ReplayDownloadButton.cs b/osu.Game/Screens/Ranking/Pages/ReplayDownloadButton.cs index c8f4ecd2d5..73c647d6fa 100644 --- a/osu.Game/Screens/Ranking/Pages/ReplayDownloadButton.cs +++ b/osu.Game/Screens/Ranking/Pages/ReplayDownloadButton.cs @@ -8,6 +8,7 @@ using osu.Game.Graphics.UserInterface; using osu.Game.Online; using osu.Game.Online.API.Requests.Responses; using osu.Game.Scoring; +using osuTK; namespace osu.Game.Screens.Ranking.Pages { @@ -33,6 +34,7 @@ namespace osu.Game.Screens.Ranking.Pages public ReplayDownloadButton(ScoreInfo score) : base(score) { + Size = new Vector2(50, 30); } [BackgroundDependencyLoader(true)] diff --git a/osu.Game/Screens/Ranking/Pages/RetryButton.cs b/osu.Game/Screens/Ranking/Pages/RetryButton.cs new file mode 100644 index 0000000000..c4f63b265d --- /dev/null +++ b/osu.Game/Screens/Ranking/Pages/RetryButton.cs @@ -0,0 +1,59 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Shapes; +using osu.Framework.Graphics.Sprites; +using osu.Framework.Screens; +using osu.Game.Graphics; +using osu.Game.Graphics.UserInterface; +using osu.Game.Screens.Play; +using osuTK; + +namespace osu.Game.Screens.Ranking.Pages +{ + public class RetryButton : OsuAnimatedButton + { + private readonly Box background; + + [Resolved(canBeNull: true)] + private Player player { get; set; } + + public RetryButton() + { + Size = new Vector2(50, 30); + + Children = new Drawable[] + { + background = new Box + { + RelativeSizeAxes = Axes.Both, + Depth = float.MaxValue + }, + new SpriteIcon + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Size = new Vector2(13), + Icon = FontAwesome.Solid.ArrowCircleLeft, + }, + }; + + TooltipText = "Retry"; + } + + [BackgroundDependencyLoader] + private void load(OsuColour colours) + { + background.Colour = colours.Green; + + if (player != null) + Action = () => + { + player.Restart(); + player.MakeCurrent(); + }; + } + } +} diff --git a/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs index 56ae069a26..27cea99f1c 100644 --- a/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs +++ b/osu.Game/Screens/Ranking/Pages/ScoreResultsPage.cs @@ -169,12 +169,19 @@ namespace osu.Game.Screens.Ranking.Pages }, }, }, - new ReplayDownloadButton(score) + new FillFlowContainer { Anchor = Anchor.BottomCentre, Origin = Anchor.BottomCentre, Margin = new MarginPadding { Bottom = 10 }, - Size = new Vector2(50, 30), + Spacing = new Vector2(5), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Children = new Drawable[] + { + new ReplayDownloadButton(score), + new RetryButton() + } }, }; From c532f7765737b6dfd9e7609eba0dde8fddc324ba Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 15:49:36 +0900 Subject: [PATCH 094/177] Add hold-to-retry support to results --- osu.Game/Screens/Ranking/Results.cs | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index cac26b3dbf..a7ff1fe6a0 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -19,6 +19,7 @@ using osu.Game.Graphics; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics.Sprites; using osu.Game.Scoring; +using osu.Game.Screens.Play; namespace osu.Game.Screens.Ranking { @@ -34,6 +35,9 @@ namespace osu.Game.Screens.Ranking private ResultModeTabControl modeChangeButtons; + [Resolved(canBeNull: true)] + private Player player { get; set; } + public override bool DisallowExternalBeatmapRulesetChanges => true; protected readonly ScoreInfo Score; @@ -100,10 +104,7 @@ namespace osu.Game.Screens.Ranking public override bool OnExiting(IScreen next) { - allCircles.ForEach(c => - { - c.ScaleTo(0, transition_time, Easing.OutSine); - }); + allCircles.ForEach(c => { c.ScaleTo(0, transition_time, Easing.OutSine); }); Background.ScaleTo(1f, transition_time / 4, Easing.OutQuint); @@ -253,7 +254,17 @@ namespace osu.Game.Screens.Ranking } } } - } + }, + new HotkeyRetryOverlay + { + Action = () => + { + if (!this.IsCurrentScreen()) return; + + player.Restart(); + player.MakeCurrent(); + }, + }, }; var pages = CreateResultPages(); From 14453da1d2b254af738c322f9fd9ba25bb078b0e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 15:51:10 +0900 Subject: [PATCH 095/177] Centralise MakeCurrent call --- osu.Game/Screens/Play/Player.cs | 2 ++ osu.Game/Screens/Ranking/Pages/RetryButton.cs | 7 +------ osu.Game/Screens/Ranking/Results.cs | 1 - 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 38068ec751..769688e829 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -319,6 +319,8 @@ namespace osu.Game.Screens.Play if (this.IsCurrentScreen()) performImmediateExit(); + else + this.MakeCurrent(); } private ScheduledDelegate completionProgressDelegate; diff --git a/osu.Game/Screens/Ranking/Pages/RetryButton.cs b/osu.Game/Screens/Ranking/Pages/RetryButton.cs index c4f63b265d..2a281224c1 100644 --- a/osu.Game/Screens/Ranking/Pages/RetryButton.cs +++ b/osu.Game/Screens/Ranking/Pages/RetryButton.cs @@ -5,7 +5,6 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Screens; using osu.Game.Graphics; using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Play; @@ -49,11 +48,7 @@ namespace osu.Game.Screens.Ranking.Pages background.Colour = colours.Green; if (player != null) - Action = () => - { - player.Restart(); - player.MakeCurrent(); - }; + Action = () => player.Restart(); } } } diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index a7ff1fe6a0..ca2ee5adae 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -262,7 +262,6 @@ namespace osu.Game.Screens.Ranking if (!this.IsCurrentScreen()) return; player.Restart(); - player.MakeCurrent(); }, }, }; From daa0ebe2b57916f5408cdd5b3dbc1b0b6591e9f8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 15:51:45 +0900 Subject: [PATCH 096/177] Add xmldoc --- osu.Game/Screens/Play/Player.cs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index 769688e829..f97114f929 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -312,6 +312,10 @@ namespace osu.Game.Screens.Play this.Exit(); } + /// + /// Restart gameplay via a parent . + /// This can be called from a child screen in order to trigger the restart process. + /// public void Restart() { sampleRestart?.Play(); From 156d0ae9b91c1bd6ea03a7b753807b0bcb4260a6 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 16:08:44 +0900 Subject: [PATCH 097/177] Remove braces Co-Authored-By: Salman Ahmed --- osu.Game/Screens/Ranking/Results.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index ca2ee5adae..d89932e105 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -104,7 +104,7 @@ namespace osu.Game.Screens.Ranking public override bool OnExiting(IScreen next) { - allCircles.ForEach(c => { c.ScaleTo(0, transition_time, Easing.OutSine); }); + allCircles.ForEach(c => c.ScaleTo(0, transition_time, Easing.OutSine)); Background.ScaleTo(1f, transition_time / 4, Easing.OutQuint); From ddd58ea3da88aedcb7123cd7921f656f4b5c9498 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 1 Nov 2019 19:40:45 +0900 Subject: [PATCH 098/177] Use LongRunningLoad on network load components --- osu.Game/Audio/PreviewTrack.cs | 1 + osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs | 1 + osu.Game/Users/Drawables/DrawableAvatar.cs | 1 + 3 files changed, 3 insertions(+) diff --git a/osu.Game/Audio/PreviewTrack.cs b/osu.Game/Audio/PreviewTrack.cs index 22ce7d4711..ff667249a7 100644 --- a/osu.Game/Audio/PreviewTrack.cs +++ b/osu.Game/Audio/PreviewTrack.cs @@ -9,6 +9,7 @@ using osu.Framework.Threading; namespace osu.Game.Audio { + [LongRunningLoad] public abstract class PreviewTrack : Component { /// diff --git a/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs b/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs index d0db7765c2..5245bc319d 100644 --- a/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs +++ b/osu.Game/Beatmaps/Drawables/BeatmapSetCover.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics.Textures; namespace osu.Game.Beatmaps.Drawables { + [LongRunningLoad] public class BeatmapSetCover : Sprite { private readonly BeatmapSetInfo set; diff --git a/osu.Game/Users/Drawables/DrawableAvatar.cs b/osu.Game/Users/Drawables/DrawableAvatar.cs index ee3cf6331b..ee9af15863 100644 --- a/osu.Game/Users/Drawables/DrawableAvatar.cs +++ b/osu.Game/Users/Drawables/DrawableAvatar.cs @@ -13,6 +13,7 @@ using osu.Game.Graphics.Containers; namespace osu.Game.Users.Drawables { + [LongRunningLoad] public class DrawableAvatar : Container { /// From d9a91100fb6448d33ec26896da0fd946d4b566dd Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Fri, 1 Nov 2019 22:46:13 +0700 Subject: [PATCH 099/177] Add tint to user's score container background --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index 9387482f14..e5a8f565aa 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -21,6 +21,7 @@ using osu.Game.Users.Drawables; using osuTK; using osuTK.Graphics; using Humanizer; +using osu.Game.Online.API; namespace osu.Game.Online.Leaderboards { @@ -59,7 +60,7 @@ namespace osu.Game.Online.Leaderboards } [BackgroundDependencyLoader] - private void load() + private void load(IAPIProvider api, OsuColour colour) { var user = score.User; @@ -100,7 +101,7 @@ namespace osu.Game.Online.Leaderboards background = new Box { RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, + Colour = user.Id == api.LocalUser.Value.Id ? colour.YellowLight : Color4.Black, Alpha = background_alpha, }, }, From ce3b34a76878ee47c9c7ed93098e00c285c420e8 Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Fri, 1 Nov 2019 23:00:55 +0700 Subject: [PATCH 100/177] Fix UserTopScoreContainer is also tinted --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 6 ++++-- .../Screens/Select/Leaderboards/UserTopScoreContainer.cs | 2 +- 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index e5a8f565aa..f8d7c0b697 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -38,6 +38,7 @@ namespace osu.Game.Online.Leaderboards private readonly ScoreInfo score; private readonly int rank; + private readonly bool canHighlighted; private Box background; private Container content; @@ -50,10 +51,11 @@ namespace osu.Game.Online.Leaderboards private List statisticsLabels; - public LeaderboardScore(ScoreInfo score, int rank) + public LeaderboardScore(ScoreInfo score, int rank, bool canHighlighted = true) { this.score = score; this.rank = rank; + this.canHighlighted = canHighlighted; RelativeSizeAxes = Axes.X; Height = HEIGHT; @@ -101,7 +103,7 @@ namespace osu.Game.Online.Leaderboards background = new Box { RelativeSizeAxes = Axes.Both, - Colour = user.Id == api.LocalUser.Value.Id ? colour.YellowLight : Color4.Black, + Colour = user.Id == api.LocalUser.Value.Id && canHighlighted ? colour.YellowLight : Color4.Black, Alpha = background_alpha, }, }, diff --git a/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs b/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs index da8f676cd0..a787eb5629 100644 --- a/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs +++ b/osu.Game/Screens/Select/Leaderboards/UserTopScoreContainer.cs @@ -77,7 +77,7 @@ namespace osu.Game.Screens.Select.Leaderboards if (newScore == null) return; - LoadComponentAsync(new LeaderboardScore(newScore.Score, newScore.Position) + LoadComponentAsync(new LeaderboardScore(newScore.Score, newScore.Position, false) { Action = () => ScoreSelected?.Invoke(newScore.Score) }, drawableScore => From 8da15f68974258cf63c27a96ed92ed19195ae28c Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Fri, 1 Nov 2019 23:19:15 +0700 Subject: [PATCH 101/177] Fix all score are highlighted on local scope --- osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs index 337d46ecdd..3ef1fe5bc5 100644 --- a/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs +++ b/osu.Game/Screens/Select/Leaderboards/BeatmapLeaderboard.cs @@ -179,7 +179,7 @@ namespace osu.Game.Screens.Select.Leaderboards return req; } - protected override LeaderboardScore CreateDrawableScore(ScoreInfo model, int index) => new LeaderboardScore(model, index) + protected override LeaderboardScore CreateDrawableScore(ScoreInfo model, int index) => new LeaderboardScore(model, index, IsOnlineScope) { Action = () => ScoreSelected?.Invoke(model) }; From 5be7d439ae2f149772372bdce9356c1d88163764 Mon Sep 17 00:00:00 2001 From: Dan Balasescu Date: Sat, 2 Nov 2019 10:32:23 +0900 Subject: [PATCH 102/177] Add null check for nullable dependency --- osu.Game/Screens/Ranking/Results.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index d89932e105..3640197dad 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -261,7 +261,7 @@ namespace osu.Game.Screens.Ranking { if (!this.IsCurrentScreen()) return; - player.Restart(); + player?.Restart(); }, }, }; From fe3583b6eec4575a9bde01ef3454452071bdd161 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 3 Nov 2019 13:16:54 +0900 Subject: [PATCH 103/177] Move schedule call --- osu.Game/OsuGame.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/OsuGame.cs b/osu.Game/OsuGame.cs index fb9a7f7965..1f823e6eba 100644 --- a/osu.Game/OsuGame.cs +++ b/osu.Game/OsuGame.cs @@ -220,14 +220,14 @@ namespace osu.Game /// This can be called from a non-thread-safe non-game-loaded state. /// /// The URL to load. - public void HandleLink(string url) => Schedule(() => HandleLink(MessageFormatter.GetLinkDetails(url))); + public void HandleLink(string url) => HandleLink(MessageFormatter.GetLinkDetails(url)); /// /// Handle a specific . /// This can be called from a non-thread-safe non-game-loaded state. /// /// The link to load. - public void HandleLink(LinkDetails link) + public void HandleLink(LinkDetails link) => Schedule(() => { switch (link.Action) { @@ -268,7 +268,7 @@ namespace osu.Game default: throw new NotImplementedException($"This {nameof(LinkAction)} ({link.Action.ToString()}) is missing an associated action."); } - } + }); public void OpenUrlExternally(string url) => waitForReady(() => externalLinkOpener, _ => { From 4dbdfcdd3c601a547887b9b563c06692feb6c53f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 3 Nov 2019 18:41:29 +0900 Subject: [PATCH 104/177] Change control to toggle rather than always select --- .../Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs | 2 +- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index bd6a905b38..e2ea6a12d7 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public Action RequestSelection; public Action ControlPointsChanged; - public readonly Bindable IsSelected = new Bindable(); + public readonly BindableBool IsSelected = new BindableBool(); public readonly int Index; private readonly Slider slider; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index bbe771d8b0..b70c11427a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -63,7 +63,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private void selectPiece(int index) { if (inputManager.CurrentState.Keyboard.ControlPressed) - Pieces[index].IsSelected.Value = true; + Pieces[index].IsSelected.Toggle(); else { foreach (var piece in Pieces) From bcf8a6d514d6c44df4f3bcd4fca97a51464c7297 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Sun, 3 Nov 2019 19:59:37 +0900 Subject: [PATCH 105/177] Fix slider creation regressing with path selection changes --- .../Sliders/Components/PathControlPointPiece.cs | 13 +++++++++---- .../Components/PathControlPointVisualiser.cs | 14 ++++++++++---- .../Blueprints/Sliders/SliderPlacementBlueprint.cs | 2 +- .../Blueprints/Sliders/SliderSelectionBlueprint.cs | 2 +- 4 files changed, 21 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index e2ea6a12d7..cfcd266c7f 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -128,19 +128,24 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components protected override bool OnMouseDown(MouseDownEvent e) { isClicked = true; - return true; + return false; } protected override bool OnMouseUp(MouseUpEvent e) { isClicked = false; - return true; + return false; } protected override bool OnClick(ClickEvent e) { - RequestSelection?.Invoke(Index); - return true; + if (RequestSelection != null) + { + RequestSelection.Invoke(Index); + return true; + } + + return false; } protected override bool OnDragStart(DragStartEvent e) => true; diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index b70c11427a..c0fc5ccb0a 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -17,12 +17,14 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components internal readonly Container Pieces; private readonly Slider slider; + private readonly bool allowSelection; private InputManager inputManager; - public PathControlPointVisualiser(Slider slider) + public PathControlPointVisualiser(Slider slider, bool allowSelection) { this.slider = slider; + this.allowSelection = allowSelection; RelativeSizeAxes = Axes.Both; @@ -42,11 +44,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components while (slider.Path.ControlPoints.Length > Pieces.Count) { - Pieces.Add(new PathControlPointPiece(slider, Pieces.Count) + var piece = new PathControlPointPiece(slider, Pieces.Count) { ControlPointsChanged = c => ControlPointsChanged?.Invoke(c), - RequestSelection = selectPiece - }); + }; + + if (allowSelection) + piece.RequestSelection = selectPiece; + + Pieces.Add(piece); } while (slider.Path.ControlPoints.Length < Pieces.Count) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs index 6f5309c2c2..9c0afada29 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderPlacementBlueprint.cs @@ -51,7 +51,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders bodyPiece = new SliderBodyPiece(), headCirclePiece = new HitCirclePiece(), tailCirclePiece = new HitCirclePiece(), - new PathControlPointVisualiser(HitObject) { ControlPointsChanged = _ => updateSlider() }, + new PathControlPointVisualiser(HitObject, false) { ControlPointsChanged = _ => updateSlider() }, }; setState(PlacementState.Initial); diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs index f612ba9dfc..25362820a3 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/SliderSelectionBlueprint.cs @@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders BodyPiece = new SliderBodyPiece(), HeadBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.Start), TailBlueprint = CreateCircleSelectionBlueprint(slider, SliderPosition.End), - ControlPointVisualiser = new PathControlPointVisualiser(sliderObject) { ControlPointsChanged = onNewControlPoints }, + ControlPointVisualiser = new PathControlPointVisualiser(sliderObject, true) { ControlPointsChanged = onNewControlPoints }, }; } From 1a7b803d4db52d824eb7899ca0e479e2cebed1a1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 4 Nov 2019 08:39:51 +0900 Subject: [PATCH 106/177] Change colour to green to match web implementation --- osu.Game/Online/Leaderboards/LeaderboardScore.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Online/Leaderboards/LeaderboardScore.cs b/osu.Game/Online/Leaderboards/LeaderboardScore.cs index f8d7c0b697..623db07938 100644 --- a/osu.Game/Online/Leaderboards/LeaderboardScore.cs +++ b/osu.Game/Online/Leaderboards/LeaderboardScore.cs @@ -38,7 +38,7 @@ namespace osu.Game.Online.Leaderboards private readonly ScoreInfo score; private readonly int rank; - private readonly bool canHighlighted; + private readonly bool allowHighlight; private Box background; private Container content; @@ -51,11 +51,11 @@ namespace osu.Game.Online.Leaderboards private List statisticsLabels; - public LeaderboardScore(ScoreInfo score, int rank, bool canHighlighted = true) + public LeaderboardScore(ScoreInfo score, int rank, bool allowHighlight = true) { this.score = score; this.rank = rank; - this.canHighlighted = canHighlighted; + this.allowHighlight = allowHighlight; RelativeSizeAxes = Axes.X; Height = HEIGHT; @@ -103,7 +103,7 @@ namespace osu.Game.Online.Leaderboards background = new Box { RelativeSizeAxes = Axes.Both, - Colour = user.Id == api.LocalUser.Value.Id && canHighlighted ? colour.YellowLight : Color4.Black, + Colour = user.Id == api.LocalUser.Value.Id && allowHighlight ? colour.Green : Color4.Black, Alpha = background_alpha, }, }, From 3ec9580ba8b6724a2ae418933690e5a305444b4b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Nov 2019 01:52:26 +0100 Subject: [PATCH 107/177] Add test for retry overlay presence Add visual tests checking presence (or lack thereof) of the hold-to-retry overlay in the results screen. --- .../Visual/Gameplay/TestSceneResults.cs | 98 +++++++++++++++---- 1 file changed, 80 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs index bf26892539..7790126db5 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneResults.cs @@ -3,11 +3,16 @@ using System; using System.Collections.Generic; +using System.Linq; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Rulesets.Scoring; using osu.Game.Scoring; +using osu.Game.Screens; using osu.Game.Screens.Play; using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking.Pages; @@ -27,7 +32,8 @@ namespace osu.Game.Tests.Visual.Gameplay typeof(ScoreResultsPage), typeof(RetryButton), typeof(ReplayDownloadButton), - typeof(LocalLeaderboardPage) + typeof(LocalLeaderboardPage), + typeof(TestPlayer) }; [BackgroundDependencyLoader] @@ -43,26 +49,82 @@ namespace osu.Game.Tests.Visual.Gameplay var beatmapInfo = beatmaps.QueryBeatmap(b => b.RulesetID == 0); if (beatmapInfo != null) Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo); + } - LoadScreen(new SoloResults(new ScoreInfo + private TestSoloResults createResultsScreen() => new TestSoloResults(new ScoreInfo + { + TotalScore = 2845370, + Accuracy = 0.98, + MaxCombo = 123, + Rank = ScoreRank.A, + Date = DateTimeOffset.Now, + Statistics = new Dictionary { - TotalScore = 2845370, - Accuracy = 0.98, - MaxCombo = 123, - Rank = ScoreRank.A, - Date = DateTimeOffset.Now, - Statistics = new Dictionary + { HitResult.Great, 50 }, + { HitResult.Good, 20 }, + { HitResult.Meh, 50 }, + { HitResult.Miss, 1 } + }, + User = new User + { + Username = "peppy", + } + }); + + [Test] + public void ResultsWithoutPlayer() + { + TestSoloResults screen = null; + + AddStep("load results", () => Child = new OsuScreenStack(screen = createResultsScreen()) + { + RelativeSizeAxes = Axes.Both + }); + AddUntilStep("wait for loaded", () => screen.IsLoaded); + AddAssert("retry overlay not present", () => screen.RetryOverlay == null); + } + + [Test] + public void ResultsWithPlayer() + { + TestSoloResults screen = null; + + AddStep("load results", () => Child = new TestResultsContainer(screen = createResultsScreen())); + AddUntilStep("wait for loaded", () => screen.IsLoaded); + AddAssert("retry overlay present", () => screen.RetryOverlay != null); + } + + private class TestResultsContainer : Container + { + [Cached(typeof(Player))] + private readonly Player player = new TestPlayer(); + + public TestResultsContainer(IScreen screen) + { + RelativeSizeAxes = Axes.Both; + + InternalChild = new OsuScreenStack(screen) { - { HitResult.Great, 50 }, - { HitResult.Good, 20 }, - { HitResult.Meh, 50 }, - { HitResult.Miss, 1 } - }, - User = new User - { - Username = "peppy", - } - })); + RelativeSizeAxes = Axes.Both, + }; + } + } + + private class TestSoloResults : SoloResults + { + public HotkeyRetryOverlay RetryOverlay; + + public TestSoloResults(ScoreInfo score) + : base(score) + { + } + + protected override void LoadComplete() + { + base.LoadComplete(); + + RetryOverlay = InternalChildren.OfType().SingleOrDefault(); + } } } } From 539f8ad6dd952dd40943db2eea26d1c400fc74f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Mon, 4 Nov 2019 01:57:31 +0100 Subject: [PATCH 108/177] Remove overlay when viewing leaderboard scores Do not add the hold-to-retry hotkey overlay if the user has navigated to the results screen from the leaderboard and not from gameplay. --- osu.Game/Screens/Ranking/Results.cs | 269 ++++++++++++++-------------- 1 file changed, 135 insertions(+), 134 deletions(-) diff --git a/osu.Game/Screens/Ranking/Results.cs b/osu.Game/Screens/Ranking/Results.cs index 3640197dad..d063988b3f 100644 --- a/osu.Game/Screens/Ranking/Results.cs +++ b/osu.Game/Screens/Ranking/Results.cs @@ -116,146 +116,147 @@ namespace osu.Game.Screens.Ranking [BackgroundDependencyLoader] private void load(OsuColour colours) { - InternalChildren = new Drawable[] + InternalChild = new AspectContainer { - new AspectContainer + RelativeSizeAxes = Axes.Y, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Height = overscan, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Y, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Height = overscan, - Children = new Drawable[] + circleOuterBackground = new CircularContainer { - circleOuterBackground = new CircularContainer + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + Children = new Drawable[] { - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Masking = true, - Children = new Drawable[] + new Box { - new Box - { - Alpha = 0.2f, - RelativeSizeAxes = Axes.Both, - Colour = Color4.Black, - } - } - }, - circleOuter = new CircularContainer - { - Size = new Vector2(circle_outer_scale), - EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.Black.Opacity(0.4f), - Type = EdgeEffectType.Shadow, - Radius = 15, - }, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - }, - backgroundParallax = new ParallaxContainer - { - RelativeSizeAxes = Axes.Both, - ParallaxAmount = 0.01f, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Children = new Drawable[] - { - new Sprite - { - RelativeSizeAxes = Axes.Both, - Alpha = 0.2f, - Texture = Beatmap.Value.Background, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - FillMode = FillMode.Fill - } - } - }, - modeChangeButtons = new ResultModeTabControl - { - Anchor = Anchor.BottomCentre, - Origin = Anchor.BottomCentre, - RelativeSizeAxes = Axes.X, - Height = 50, - Margin = new MarginPadding { Bottom = 110 }, - }, - new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.BottomCentre, - Text = $"{Score.MaxCombo}x", - RelativePositionAxes = Axes.X, - Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 40), - X = 0.1f, - Colour = colours.BlueDarker, - }, - new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.TopCentre, - Text = "max combo", - Font = OsuFont.GetFont(size: 20), - RelativePositionAxes = Axes.X, - X = 0.1f, - Colour = colours.Gray6, - }, - new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.BottomCentre, - Text = $"{Score.Accuracy:P2}", - Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 40), - RelativePositionAxes = Axes.X, - X = 0.9f, - Colour = colours.BlueDarker, - }, - new OsuSpriteText - { - Anchor = Anchor.CentreLeft, - Origin = Anchor.TopCentre, - Text = "accuracy", - Font = OsuFont.GetFont(size: 20), - RelativePositionAxes = Axes.X, - X = 0.9f, - Colour = colours.Gray6, - }, - } - }, - circleInner = new CircularContainer - { - Size = new Vector2(0.6f), - EdgeEffect = new EdgeEffectParameters - { - Colour = Color4.Black.Opacity(0.4f), - Type = EdgeEffectType.Shadow, - Radius = 15, - }, - RelativeSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Masking = true, - Children = new Drawable[] - { - new Box - { - RelativeSizeAxes = Axes.Both, - Colour = Color4.White, - }, + Alpha = 0.2f, + RelativeSizeAxes = Axes.Both, + Colour = Color4.Black, } } + }, + circleOuter = new CircularContainer + { + Size = new Vector2(circle_outer_scale), + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.4f), + Type = EdgeEffectType.Shadow, + Radius = 15, + }, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }, + backgroundParallax = new ParallaxContainer + { + RelativeSizeAxes = Axes.Both, + ParallaxAmount = 0.01f, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] + { + new Sprite + { + RelativeSizeAxes = Axes.Both, + Alpha = 0.2f, + Texture = Beatmap.Value.Background, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + FillMode = FillMode.Fill + } + } + }, + modeChangeButtons = new ResultModeTabControl + { + Anchor = Anchor.BottomCentre, + Origin = Anchor.BottomCentre, + RelativeSizeAxes = Axes.X, + Height = 50, + Margin = new MarginPadding { Bottom = 110 }, + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.BottomCentre, + Text = $"{Score.MaxCombo}x", + RelativePositionAxes = Axes.X, + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 40), + X = 0.1f, + Colour = colours.BlueDarker, + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.TopCentre, + Text = "max combo", + Font = OsuFont.GetFont(size: 20), + RelativePositionAxes = Axes.X, + X = 0.1f, + Colour = colours.Gray6, + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.BottomCentre, + Text = $"{Score.Accuracy:P2}", + Font = OsuFont.GetFont(weight: FontWeight.Bold, size: 40), + RelativePositionAxes = Axes.X, + X = 0.9f, + Colour = colours.BlueDarker, + }, + new OsuSpriteText + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.TopCentre, + Text = "accuracy", + Font = OsuFont.GetFont(size: 20), + RelativePositionAxes = Axes.X, + X = 0.9f, + Colour = colours.Gray6, + }, + } + }, + circleInner = new CircularContainer + { + Size = new Vector2(0.6f), + EdgeEffect = new EdgeEffectParameters + { + Colour = Color4.Black.Opacity(0.4f), + Type = EdgeEffectType.Shadow, + Radius = 15, + }, + RelativeSizeAxes = Axes.Both, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.White, + }, + } } - }, - new HotkeyRetryOverlay + } + }; + + if (player != null) + { + AddInternal(new HotkeyRetryOverlay { Action = () => { @@ -263,8 +264,8 @@ namespace osu.Game.Screens.Ranking player?.Restart(); }, - }, - }; + }); + } var pages = CreateResultPages(); From 66253a0bd4142166cb7164bb0d9aca4712b9b6e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Nov 2019 02:21:50 +0900 Subject: [PATCH 109/177] Handle selection on mouse down instead of click This is how other elements work, and feels better with drag preselection --- .../Components/PathControlPointPiece.cs | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index cfcd266c7f..0353ba241c 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -29,8 +29,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private readonly Container marker; private readonly Drawable markerRing; - private bool isClicked; - [Resolved] private OsuColour colours { get; set; } @@ -101,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components markerRing.Alpha = IsSelected.Value ? 1 : 0; Color4 colour = isSegmentSeparator ? colours.Red : colours.Yellow; - if (IsHovered || isClicked || IsSelected.Value) + if (IsHovered || IsSelected.Value) colour = Color4.White; marker.Colour = colour; } @@ -126,18 +124,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => marker.ReceivePositionalInputAt(screenSpacePos); protected override bool OnMouseDown(MouseDownEvent e) - { - isClicked = true; - return false; - } - - protected override bool OnMouseUp(MouseUpEvent e) - { - isClicked = false; - return false; - } - - protected override bool OnClick(ClickEvent e) { if (RequestSelection != null) { @@ -148,6 +134,10 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return false; } + protected override bool OnMouseUp(MouseUpEvent e) => RequestSelection != null; + + protected override bool OnClick(ClickEvent e) => RequestSelection != null; + protected override bool OnDragStart(DragStartEvent e) => true; protected override bool OnDrag(DragEvent e) From 64d900b38770da44090c9124fb4ba1e83c39033c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 11:33:37 +0900 Subject: [PATCH 110/177] Disable input for non-selected blueprints --- osu.Game/Rulesets/Edit/SelectionBlueprint.cs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs index 0701513933..2923411ce1 100644 --- a/osu.Game/Rulesets/Edit/SelectionBlueprint.cs +++ b/osu.Game/Rulesets/Edit/SelectionBlueprint.cs @@ -82,6 +82,9 @@ namespace osu.Game.Rulesets.Edit } } + // When not selected, input is only required for the blueprint itself to receive IsHovering + protected override bool ShouldBeConsideredForInput(Drawable child) => State == SelectionState.Selected; + /// /// Selects this , causing it to become visible. /// From c8beb5296f512d8c932c248eeea651675902c8d1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 13:26:44 +0900 Subject: [PATCH 111/177] Use PlatformAction.Delete instead of Delete key --- .../Components/PathControlPointVisualiser.cs | 11 +++++++---- .../Edit/Compose/Components/SelectionHandler.cs | 17 +++++++++-------- 2 files changed, 16 insertions(+), 12 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index 1118cae344..f2a392feee 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -7,6 +7,7 @@ using osu.Framework.Allocation; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Input; +using osu.Framework.Input.Bindings; using osu.Framework.Input.Events; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose; @@ -15,7 +16,7 @@ using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { - public class PathControlPointVisualiser : CompositeDrawable + public class PathControlPointVisualiser : CompositeDrawable, IKeyBindingHandler { public Action ControlPointsChanged; @@ -84,11 +85,11 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components } } - protected override bool OnKeyDown(KeyDownEvent e) + public bool OnPressed(PlatformAction action) { - switch (e.Key) + switch (action.ActionMethod) { - case Key.Delete: + case PlatformActionMethod.Delete: var newControlPoints = new List(); foreach (var piece in Pieces) @@ -127,5 +128,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components return false; } + + public bool OnReleased(PlatformAction action) => action.ActionMethod == PlatformActionMethod.Delete; } } diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index d7821eff07..664b9e3a8c 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -8,6 +8,8 @@ using osu.Framework.Allocation; 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.Input.Events; using osu.Framework.Input.States; using osu.Game.Graphics; @@ -22,7 +24,7 @@ namespace osu.Game.Screens.Edit.Compose.Components /// /// A component which outlines s and handles movement of selections. /// - public class SelectionHandler : CompositeDrawable + public class SelectionHandler : CompositeDrawable, IKeyBindingHandler { public const float BORDER_RADIUS = 2; @@ -72,22 +74,21 @@ namespace osu.Game.Screens.Edit.Compose.Components { } - protected override bool OnKeyDown(KeyDownEvent e) + public bool OnPressed(PlatformAction action) { - if (e.Repeat) - return base.OnKeyDown(e); - - switch (e.Key) + switch (action.ActionMethod) { - case Key.Delete: + case PlatformActionMethod.Delete: foreach (var h in selectedBlueprints.ToList()) placementHandler.Delete(h.DrawableObject.HitObject); return true; } - return base.OnKeyDown(e); + return false; } + public bool OnReleased(PlatformAction action) => action.ActionMethod == PlatformActionMethod.Delete; + #endregion #region Selection Handling From 7c20a589f2dac47c55bcad3b032d9e04fc99fc6d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 13:56:21 +0900 Subject: [PATCH 112/177] Remove unused usings --- .../Blueprints/Sliders/Components/PathControlPointVisualiser.cs | 1 - osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs | 2 -- 2 files changed, 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs index f2a392feee..6962736157 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointVisualiser.cs @@ -12,7 +12,6 @@ using osu.Framework.Input.Events; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose; using osuTK; -using osuTK.Input; namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components { diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 664b9e3a8c..1722476e53 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -10,14 +10,12 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Input; using osu.Framework.Input.Bindings; -using osu.Framework.Input.Events; using osu.Framework.Input.States; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects.Drawables; using osuTK; -using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { From fe374eabe04b3c51efa732c88a71ea6802b322ed Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Nov 2019 15:47:01 +0900 Subject: [PATCH 113/177] Update framework --- osu.Android.props | 2 +- osu.Game/osu.Game.csproj | 2 +- osu.iOS.props | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Android.props b/osu.Android.props index 8b31be3f12..78c7583923 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -62,6 +62,6 @@ - + diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 0cb09d9b14..42b930b1af 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + diff --git a/osu.iOS.props b/osu.iOS.props index 719aced705..dfc9c2d60a 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -118,8 +118,8 @@ - - + + From 44d079167676370c41e2a9677b8a8d2f4dce3341 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Nov 2019 16:26:31 +0900 Subject: [PATCH 114/177] Update button usage --- osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs | 2 +- osu.Game/Graphics/UserInterface/OsuButton.cs | 2 +- osu.Game/Overlays/Settings/SidebarButton.cs | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs index a8c2362910..b6b2b2f92d 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.Editor } } - private class StartStopButton : Button + private class StartStopButton : BasicButton { private IAdjustableClock adjustableClock; private bool started; diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index 4124d2ad58..319c3dd0b4 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -17,7 +17,7 @@ namespace osu.Game.Graphics.UserInterface /// /// A button with added default sound effects. /// - public class OsuButton : Button + public class OsuButton : BasicButton { private Box hover; diff --git a/osu.Game/Overlays/Settings/SidebarButton.cs b/osu.Game/Overlays/Settings/SidebarButton.cs index a94f76e7af..cdb4dc463a 100644 --- a/osu.Game/Overlays/Settings/SidebarButton.cs +++ b/osu.Game/Overlays/Settings/SidebarButton.cs @@ -17,7 +17,7 @@ using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings { - public class SidebarButton : Button + public class SidebarButton : BasicButton { private readonly SpriteIcon drawableIcon; private readonly SpriteText headerText; From 0db34a47f8314f1d46c48f44e04e31d8a80a9077 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 17:28:42 +0900 Subject: [PATCH 115/177] Fix selecting underneath selected blueprints --- .../Compose/Components/BlueprintContainer.cs | 24 +++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index a145dea6af..7a3a303ff3 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -254,15 +254,31 @@ namespace osu.Game.Screens.Edit.Compose.Components { Debug.Assert(!clickSelectionBegan); - foreach (SelectionBlueprint blueprint in selectionBlueprints.AliveBlueprints) + bool hoveringSelected = false; + + // Make sure any already-selected blueprints aren't being hovered over + foreach (SelectionBlueprint selected in selectionHandler.SelectedBlueprints) { - if (blueprint.IsHovered) + if (selected.IsHovered) { - selectionHandler.HandleSelectionRequested(blueprint, e.CurrentState); - clickSelectionBegan = true; + hoveringSelected = true; break; } } + + // Attempt a new selection at the mouse position + if (!hoveringSelected) + { + foreach (SelectionBlueprint blueprint in selectionBlueprints.AliveBlueprints) + { + if (blueprint.IsHovered) + { + selectionHandler.HandleSelectionRequested(blueprint, e.CurrentState); + clickSelectionBegan = true; + break; + } + } + } } /// From d77882c21bcc9ebb4379fd7e6ad6d04a1f2ea451 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 17:31:52 +0900 Subject: [PATCH 116/177] Fix slider selection input handled outside path --- .../Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs index d28cf7b492..78f4c4d992 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/SliderBodyPiece.cs @@ -43,5 +43,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components Size = body.Size; OriginPosition = body.PathOffset; } + + public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => body.ReceivePositionalInputAt(screenSpacePos); } } From 861268eb1ded856d1ebf6fb21115af38e7baec49 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 14:52:33 +0900 Subject: [PATCH 117/177] Add basic structure for new follow point renderer --- .../TestSceneFollowPoints.cs | 315 ++++++++++++++++++ 1 file changed, 315 insertions(+) create mode 100644 osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs new file mode 100644 index 0000000000..c33c94c9c8 --- /dev/null +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -0,0 +1,315 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using JetBrains.Annotations; +using NUnit.Framework; +using osu.Framework.Extensions.Color4Extensions; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Graphics.Effects; +using osu.Framework.Graphics.Shapes; +using osu.Game.Beatmaps; +using osu.Game.Beatmaps.ControlPoints; +using osu.Game.Rulesets.Objects.Drawables; +using osu.Game.Rulesets.Objects.Types; +using osu.Game.Rulesets.Osu.Objects; +using osu.Game.Rulesets.Osu.Objects.Drawables; +using osu.Game.Skinning; +using osu.Game.Tests.Visual; +using osuTK; +using osuTK.Graphics; + +namespace osu.Game.Rulesets.Osu.Tests +{ + public class TestSceneFollowPoints : OsuTestScene + { + private Container hitObjectContainer; + private FollowPointRenderer followPointRenderer; + + [SetUp] + public void Setup() => Schedule(() => + { + Children = new Drawable[] + { + hitObjectContainer = new Container { RelativeSizeAxes = Axes.Both }, + followPointRenderer = new FollowPointRenderer { RelativeSizeAxes = Axes.Both } + }; + }); + + [Test] + public void TestAddSingleHitObject() + { + addObject(new HitCircle { Position = new Vector2(100, 100) }); + } + + [Test] + public void TestRemoveSingleHitObject() + { + } + + [Test] + public void TestAddMultipleHitObjects() + { + } + + [Test] + public void TestRemoveEndObject() + { + } + + [Test] + public void TestRemoveStartObject() + { + } + + [Test] + public void TestRemoveMiddleObject() + { + } + + private void addObject(HitCircle hitCircle, Action func = null) + { + AddStep("add hitobject", () => + { + hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + var drawableCircle = new DrawableHitCircle(hitCircle); + + hitObjectContainer.Add(drawableCircle); + followPointRenderer.AddFollowPoints(drawableCircle); + + func?.Invoke(drawableCircle); + }); + } + + private void removeObject(Func func) + { + AddStep("remove hitobject", () => + { + var drawableObject = func?.Invoke(); + + hitObjectContainer.Remove(drawableObject); + followPointRenderer.RemoveFollowPoints(drawableObject); + }); + } + + private class FollowPointRenderer : CompositeDrawable + { + /// + /// Adds the s around a . + /// This includes s leading into , and s exiting . + /// + /// The to add s for. + public void AddFollowPoints(DrawableHitObject hitObject) + { + var startGroup = new FollowPointGroup(hitObject); + AddInternal(startGroup); + + // Groups are sorted by their start time when added, so the index can be used to post-process other surrounding groups + int startIndex = IndexOfInternal(startGroup); + + if (startIndex < InternalChildren.Count - 1) + { + // h1 -> -> -> h2 + // hitObject nextGroup + + var nextGroup = (FollowPointGroup)InternalChildren[startIndex + 1]; + startGroup.End = nextGroup.Start; + } + + if (startIndex > 0) + { + // h1 -> -> -> h2 + // prevGroup hitObject + + var previousGroup = (FollowPointGroup)InternalChildren[startIndex - 1]; + previousGroup.End = startGroup.Start; + } + } + + /// + /// Removes the s around a . + /// This includes s leading into , and s exiting . + /// + /// The to remove s for. + public void RemoveFollowPoints(DrawableHitObject hitObject) + { + var groups = findGroups(hitObject); + + // Regardless of the position of the hitobject in the beatmap, there will always be a group leading from the hitobject + RemoveInternal(groups.start); + + if (groups.end != null) + { + // When there were two groups referencing the same hitobject, merge them by updating the end group to point to the new end (the start group was already removed) + groups.end.End = groups.start.End; + } + } + + /// + /// Finds the s with as the start and end s. + /// + /// The to find the relevant of. + /// A tuple containing the end group (the where is the end of), + /// and the start group (the where is the start of). + private (FollowPointGroup start, FollowPointGroup end) findGroups(DrawableHitObject hitObject) + { + // endGroup startGroup + // h1 -> -> -> -> -> h2 -> -> -> -> -> h3 + // hitObject + + FollowPointGroup startGroup = null; // The group which the hitobject is the start in + FollowPointGroup endGroup = null; // The group which the hitobject is the end in + + int startIndex = 0; + + for (; startIndex < InternalChildren.Count; startIndex++) + { + var group = (FollowPointGroup)InternalChildren[startIndex]; + + if (group.Start == hitObject) + { + startGroup = group; + break; + } + } + + if (startIndex > 0) + endGroup = (FollowPointGroup)InternalChildren[startIndex - 1]; + + return (startGroup, endGroup); + } + + protected override int Compare(Drawable x, Drawable y) + { + var groupX = (FollowPointGroup)x; + var groupY = (FollowPointGroup)y; + + return groupX.Start.HitObject.StartTime.CompareTo(groupY.Start.HitObject.StartTime); + } + } + + private class FollowPointGroup : CompositeDrawable + { + private const int spacing = 32; + private const double preempt = 800; + + public FollowPointGroup(DrawableHitObject start) + { + Start = start; + } + + /// + /// The which s will exit from. + /// + [NotNull] + public DrawableHitObject Start { get; } + + private DrawableHitObject end; + + /// + /// The which s will enter. + /// + [CanBeNull] + public DrawableHitObject End + { + get => end; + set + { + end = value; + refreshFollowPoints(); + } + } + + private void refreshFollowPoints() + { + ClearInternal(); + + if (End == null) + return; + + OsuHitObject osuStart = (OsuHitObject)Start.HitObject; + OsuHitObject osuEnd = (OsuHitObject)End.HitObject; + + if (osuEnd.NewCombo) + return; + + if (osuStart is Spinner || osuEnd is Spinner) + return; + + Vector2 startPosition = osuStart.EndPosition; + Vector2 endPosition = osuEnd.Position; + double startTime = (osuStart as IHasEndTime)?.EndTime ?? osuStart.StartTime; + double endTime = osuEnd.StartTime; + + Vector2 distanceVector = endPosition - startPosition; + int distance = (int)distanceVector.Length; + float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI)); + double duration = endTime - startTime; + + for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing) + { + float fraction = (float)d / distance; + Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector; + Vector2 pointEndPosition = startPosition + fraction * distanceVector; + double fadeOutTime = startTime + fraction * duration; + double fadeInTime = fadeOutTime - preempt; + + FollowPoint fp; + + AddInternal(fp = new FollowPoint + { + Position = pointStartPosition, + Rotation = rotation, + Alpha = 0, + Scale = new Vector2(1.5f * osuEnd.Scale), + }); + + using (fp.BeginAbsoluteSequence(fadeInTime)) + { + fp.FadeIn(osuEnd.TimeFadeIn); + fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out); + fp.MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out); + fp.Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn); + } + + fp.Expire(true); + } + } + } + + private class FollowPoint : CompositeDrawable + { + private const float width = 8; + + public override bool RemoveWhenNotAlive => false; + + public FollowPoint() + { + Origin = Anchor.Centre; + + InternalChild = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.FollowPoint), _ => new Container + { + Masking = true, + AutoSizeAxes = Axes.Both, + CornerRadius = width / 2, + EdgeEffect = new EdgeEffectParameters + { + Type = EdgeEffectType.Glow, + Colour = Color4.White.Opacity(0.2f), + Radius = 4, + }, + Child = new Box + { + Size = new Vector2(width), + Blending = BlendingParameters.Additive, + Origin = Anchor.Centre, + Anchor = Anchor.Centre, + Alpha = 0.5f, + } + }, confineMode: ConfineMode.NoScaling); + } + } + } +} From 7e60bc724097d12c180a6ed5ff521257dc332f5e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:12:32 +0900 Subject: [PATCH 118/177] Fix groups having 0 size --- osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index c33c94c9c8..ca7be9bb64 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -198,6 +198,7 @@ namespace osu.Game.Rulesets.Osu.Tests public FollowPointGroup(DrawableHitObject start) { Start = start; + RelativeSizeAxes = Axes.Both; } /// From c1850b2353c1e85b0153c301f0b82d980ab87211 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:12:42 +0900 Subject: [PATCH 119/177] Add some basic tests --- .../TestSceneFollowPoints.cs | 100 ++++++++++++++++-- 1 file changed, 90 insertions(+), 10 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index ca7be9bb64..66aea68a3c 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -2,6 +2,8 @@ // See the LICENCE file in the repository root for full licence text. using System; +using System.Collections.Generic; +using System.Linq; using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Extensions.Color4Extensions; @@ -40,54 +42,132 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestAddSingleHitObject() { - addObject(new HitCircle { Position = new Vector2(100, 100) }); + addObjects(() => new[] { new HitCircle { Position = new Vector2(100, 100) } }); } [Test] public void TestRemoveSingleHitObject() { + DrawableHitObject obj = null; + + addObjects(() => new[] { new HitCircle { Position = new Vector2(100, 100) } }, o => obj = o); + removeObject(() => obj); } [Test] public void TestAddMultipleHitObjects() { + addObjects(() => new[] + { + new HitCircle { Position = new Vector2(100, 100) }, + new HitCircle { Position = new Vector2(200, 200) }, + new HitCircle { Position = new Vector2(300, 300) }, + new HitCircle { Position = new Vector2(400, 400) }, + new HitCircle { Position = new Vector2(500, 500) }, + }); } [Test] public void TestRemoveEndObject() { + var objects = new List(); + + AddStep("reset", () => objects.Clear()); + + addObjects(() => new[] + { + new HitCircle { Position = new Vector2(100, 100) }, + new HitCircle { Position = new Vector2(200, 200) }, + new HitCircle { Position = new Vector2(300, 300) }, + new HitCircle { Position = new Vector2(400, 400) }, + new HitCircle { Position = new Vector2(500, 500) }, + }, o => objects.Add(o)); + + removeObject(() => objects.Last()); } [Test] public void TestRemoveStartObject() { + var objects = new List(); + + AddStep("reset", () => objects.Clear()); + + addObjects(() => new[] + { + new HitCircle { Position = new Vector2(100, 100) }, + new HitCircle { Position = new Vector2(200, 200) }, + new HitCircle { Position = new Vector2(300, 300) }, + new HitCircle { Position = new Vector2(400, 400) }, + new HitCircle { Position = new Vector2(500, 500) }, + }, o => objects.Add(o)); + + removeObject(() => objects.First()); } [Test] public void TestRemoveMiddleObject() { + var objects = new List(); + + AddStep("reset", () => objects.Clear()); + + addObjects(() => new[] + { + new HitCircle { Position = new Vector2(100, 100) }, + new HitCircle { Position = new Vector2(200, 200) }, + new HitCircle { Position = new Vector2(300, 300) }, + new HitCircle { Position = new Vector2(400, 400) }, + new HitCircle { Position = new Vector2(500, 500) }, + }, o => objects.Add(o)); + + removeObject(() => objects[2]); } - private void addObject(HitCircle hitCircle, Action func = null) + [Test] + public void TestMoveHitObject() { - AddStep("add hitobject", () => + var objects = new List(); + + AddStep("reset", () => objects.Clear()); + + addObjects(() => new[] { - hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + new HitCircle { Position = new Vector2(100, 100) }, + new HitCircle { Position = new Vector2(200, 200) }, + new HitCircle { Position = new Vector2(300, 300) }, + new HitCircle { Position = new Vector2(400, 400) }, + new HitCircle { Position = new Vector2(500, 500) }, + }, o => objects.Add(o)); - var drawableCircle = new DrawableHitCircle(hitCircle); + AddStep("move hitobject", () => ((OsuHitObject)objects[2].HitObject).Position = new Vector2(300, 100)); + } - hitObjectContainer.Add(drawableCircle); - followPointRenderer.AddFollowPoints(drawableCircle); + private void addObjects(Func ctorFunc, Action storeFunc = null) + { + AddStep("add hitobjects", () => + { + var circles = ctorFunc(); - func?.Invoke(drawableCircle); + for (int i = 0; i < circles.Length; i++) + { + circles[i].StartTime = Time.Current + 1000 + 500 * (i + 1); + circles[i].ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + + var drawableCircle = new DrawableHitCircle(circles[i]); + hitObjectContainer.Add(drawableCircle); + followPointRenderer.AddFollowPoints(drawableCircle); + + storeFunc?.Invoke(drawableCircle); + } }); } - private void removeObject(Func func) + private void removeObject(Func getFunc) { AddStep("remove hitobject", () => { - var drawableObject = func?.Invoke(); + var drawableObject = getFunc?.Invoke(); hitObjectContainer.Remove(drawableObject); followPointRenderer.RemoveFollowPoints(drawableObject); From f861d8099c1344c60f67db77ea955ad47c33013e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:13:50 +0900 Subject: [PATCH 120/177] Remove unnecessary class --- .../TestSceneFollowPoints.cs | 39 +------------------ 1 file changed, 1 insertion(+), 38 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index 66aea68a3c..ed1d8f5184 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -6,21 +6,17 @@ using System.Collections.Generic; using System.Linq; using JetBrains.Annotations; using NUnit.Framework; -using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Framework.Graphics.Effects; -using osu.Framework.Graphics.Shapes; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; -using osu.Game.Skinning; +using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; using osu.Game.Tests.Visual; using osuTK; -using osuTK.Graphics; namespace osu.Game.Rulesets.Osu.Tests { @@ -359,38 +355,5 @@ namespace osu.Game.Rulesets.Osu.Tests } } } - - private class FollowPoint : CompositeDrawable - { - private const float width = 8; - - public override bool RemoveWhenNotAlive => false; - - public FollowPoint() - { - Origin = Anchor.Centre; - - InternalChild = new SkinnableDrawable(new OsuSkinComponent(OsuSkinComponents.FollowPoint), _ => new Container - { - Masking = true, - AutoSizeAxes = Axes.Both, - CornerRadius = width / 2, - EdgeEffect = new EdgeEffectParameters - { - Type = EdgeEffectType.Glow, - Colour = Color4.White.Opacity(0.2f), - Radius = 4, - }, - Child = new Box - { - Size = new Vector2(width), - Blending = BlendingParameters.Additive, - Origin = Anchor.Centre, - Anchor = Anchor.Centre, - Alpha = 0.5f, - } - }, confineMode: ConfineMode.NoScaling); - } - } } } From 513ad96adf09130dcc613d9b15d934e257118909 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:15:13 +0900 Subject: [PATCH 121/177] Cleanup property --- .../TestSceneFollowPoints.cs | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index ed1d8f5184..43d6cc9ab9 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -268,21 +268,22 @@ namespace osu.Game.Rulesets.Osu.Tests private class FollowPointGroup : CompositeDrawable { + // Todo: These shouldn't be constants private const int spacing = 32; private const double preempt = 800; + /// + /// The which s will exit from. + /// + [NotNull] + public readonly DrawableHitObject Start; + public FollowPointGroup(DrawableHitObject start) { Start = start; RelativeSizeAxes = Axes.Both; } - /// - /// The which s will exit from. - /// - [NotNull] - public DrawableHitObject Start { get; } - private DrawableHitObject end; /// From c0badf1dce9d5a2463f40ce698b431c25ed23852 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:17:51 +0900 Subject: [PATCH 122/177] Type to DrawableOsuHitObject --- .../TestSceneFollowPoints.cs | 55 +++++++++---------- 1 file changed, 27 insertions(+), 28 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index 43d6cc9ab9..bb3e6a5044 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -10,7 +10,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; @@ -22,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Tests { public class TestSceneFollowPoints : OsuTestScene { - private Container hitObjectContainer; + private Container hitObjectContainer; private FollowPointRenderer followPointRenderer; [SetUp] @@ -30,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Tests { Children = new Drawable[] { - hitObjectContainer = new Container { RelativeSizeAxes = Axes.Both }, + hitObjectContainer = new Container { RelativeSizeAxes = Axes.Both }, followPointRenderer = new FollowPointRenderer { RelativeSizeAxes = Axes.Both } }; }); @@ -44,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestRemoveSingleHitObject() { - DrawableHitObject obj = null; + DrawableOsuHitObject obj = null; addObjects(() => new[] { new HitCircle { Position = new Vector2(100, 100) } }, o => obj = o); removeObject(() => obj); @@ -66,7 +65,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestRemoveEndObject() { - var objects = new List(); + var objects = new List(); AddStep("reset", () => objects.Clear()); @@ -85,7 +84,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestRemoveStartObject() { - var objects = new List(); + var objects = new List(); AddStep("reset", () => objects.Clear()); @@ -104,7 +103,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestRemoveMiddleObject() { - var objects = new List(); + var objects = new List(); AddStep("reset", () => objects.Clear()); @@ -123,7 +122,7 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestMoveHitObject() { - var objects = new List(); + var objects = new List(); AddStep("reset", () => objects.Clear()); @@ -136,10 +135,10 @@ namespace osu.Game.Rulesets.Osu.Tests new HitCircle { Position = new Vector2(500, 500) }, }, o => objects.Add(o)); - AddStep("move hitobject", () => ((OsuHitObject)objects[2].HitObject).Position = new Vector2(300, 100)); + AddStep("move hitobject", () => objects[2].HitObject.Position = new Vector2(300, 100)); } - private void addObjects(Func ctorFunc, Action storeFunc = null) + private void addObjects(Func ctorFunc, Action storeFunc = null) { AddStep("add hitobjects", () => { @@ -159,7 +158,7 @@ namespace osu.Game.Rulesets.Osu.Tests }); } - private void removeObject(Func getFunc) + private void removeObject(Func getFunc) { AddStep("remove hitobject", () => { @@ -173,11 +172,11 @@ namespace osu.Game.Rulesets.Osu.Tests private class FollowPointRenderer : CompositeDrawable { /// - /// Adds the s around a . + /// Adds the s around a . /// This includes s leading into , and s exiting . /// - /// The to add s for. - public void AddFollowPoints(DrawableHitObject hitObject) + /// The to add s for. + public void AddFollowPoints(DrawableOsuHitObject hitObject) { var startGroup = new FollowPointGroup(hitObject); AddInternal(startGroup); @@ -205,11 +204,11 @@ namespace osu.Game.Rulesets.Osu.Tests } /// - /// Removes the s around a . + /// Removes the s around a . /// This includes s leading into , and s exiting . /// - /// The to remove s for. - public void RemoveFollowPoints(DrawableHitObject hitObject) + /// The to remove s for. + public void RemoveFollowPoints(DrawableOsuHitObject hitObject) { var groups = findGroups(hitObject); @@ -224,12 +223,12 @@ namespace osu.Game.Rulesets.Osu.Tests } /// - /// Finds the s with as the start and end s. + /// Finds the s with as the start and end s. /// - /// The to find the relevant of. + /// The to find the relevant of. /// A tuple containing the end group (the where is the end of), /// and the start group (the where is the start of). - private (FollowPointGroup start, FollowPointGroup end) findGroups(DrawableHitObject hitObject) + private (FollowPointGroup start, FollowPointGroup end) findGroups(DrawableOsuHitObject hitObject) { // endGroup startGroup // h1 -> -> -> -> -> h2 -> -> -> -> -> h3 @@ -273,24 +272,24 @@ namespace osu.Game.Rulesets.Osu.Tests private const double preempt = 800; /// - /// The which s will exit from. + /// The which s will exit from. /// [NotNull] - public readonly DrawableHitObject Start; + public readonly DrawableOsuHitObject Start; - public FollowPointGroup(DrawableHitObject start) + public FollowPointGroup(DrawableOsuHitObject start) { Start = start; RelativeSizeAxes = Axes.Both; } - private DrawableHitObject end; + private DrawableOsuHitObject end; /// - /// The which s will enter. + /// The which s will enter. /// [CanBeNull] - public DrawableHitObject End + public DrawableOsuHitObject End { get => end; set @@ -307,8 +306,8 @@ namespace osu.Game.Rulesets.Osu.Tests if (End == null) return; - OsuHitObject osuStart = (OsuHitObject)Start.HitObject; - OsuHitObject osuEnd = (OsuHitObject)End.HitObject; + OsuHitObject osuStart = Start.HitObject; + OsuHitObject osuEnd = End.HitObject; if (osuEnd.NewCombo) return; From e0ba35db755e97abdd33a07db109dbb3ecfff3a1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:22:37 +0900 Subject: [PATCH 123/177] Implement binding to hitobjects + refreshing --- .../TestSceneFollowPoints.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index bb3e6a5044..b914a8b29d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -283,6 +283,12 @@ namespace osu.Game.Rulesets.Osu.Tests RelativeSizeAxes = Axes.Both; } + protected override void LoadComplete() + { + base.LoadComplete(); + bindHitObject(Start); + } + private DrawableOsuHitObject end; /// @@ -295,10 +301,19 @@ namespace osu.Game.Rulesets.Osu.Tests set { end = value; + + bindHitObject(end); refreshFollowPoints(); } } + private void bindHitObject(DrawableOsuHitObject drawableObject) + { + drawableObject.HitObject.StartTimeBindable.BindValueChanged(_ => refreshFollowPoints()); + drawableObject.HitObject.PositionBindable.BindValueChanged(_ => refreshFollowPoints()); + drawableObject.HitObject.DefaultsApplied += refreshFollowPoints; + } + private void refreshFollowPoints() { ClearInternal(); From 02a7f92d18375176eee9827e17aa126b708f8106 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:34:13 +0900 Subject: [PATCH 124/177] Allow constructing arbitrary hitobject types --- .../TestSceneFollowPoints.cs | 62 ++++++++++++------- 1 file changed, 39 insertions(+), 23 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index b914a8b29d..68f37e5d1d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -35,24 +35,24 @@ namespace osu.Game.Rulesets.Osu.Tests }); [Test] - public void TestAddSingleHitObject() + public void TestAddSingleHitCircle() { - addObjects(() => new[] { new HitCircle { Position = new Vector2(100, 100) } }); + addObjects(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); } [Test] - public void TestRemoveSingleHitObject() + public void TestRemoveSingleHitCircle() { DrawableOsuHitObject obj = null; - addObjects(() => new[] { new HitCircle { Position = new Vector2(100, 100) } }, o => obj = o); + addObjects(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }, o => obj = o); removeObject(() => obj); } [Test] - public void TestAddMultipleHitObjects() + public void TestAddMultipleHitCircles() { - addObjects(() => new[] + addObjects(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) }, new HitCircle { Position = new Vector2(200, 200) }, @@ -63,13 +63,13 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestRemoveEndObject() + public void TestRemoveEndHitCircle() { var objects = new List(); AddStep("reset", () => objects.Clear()); - addObjects(() => new[] + addObjects(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) }, new HitCircle { Position = new Vector2(200, 200) }, @@ -82,13 +82,13 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestRemoveStartObject() + public void TestRemoveStartHitCircle() { var objects = new List(); AddStep("reset", () => objects.Clear()); - addObjects(() => new[] + addObjects(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) }, new HitCircle { Position = new Vector2(200, 200) }, @@ -101,13 +101,13 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestRemoveMiddleObject() + public void TestRemoveMiddleHitCircle() { var objects = new List(); AddStep("reset", () => objects.Clear()); - addObjects(() => new[] + addObjects(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) }, new HitCircle { Position = new Vector2(200, 200) }, @@ -120,13 +120,13 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestMoveHitObject() + public void TestMoveHitCircle() { var objects = new List(); AddStep("reset", () => objects.Clear()); - addObjects(() => new[] + addObjects(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) }, new HitCircle { Position = new Vector2(200, 200) }, @@ -138,22 +138,38 @@ namespace osu.Game.Rulesets.Osu.Tests AddStep("move hitobject", () => objects[2].HitObject.Position = new Vector2(300, 100)); } - private void addObjects(Func ctorFunc, Action storeFunc = null) + private void addObjects(Func ctorFunc, Action storeFunc = null) { AddStep("add hitobjects", () => { - var circles = ctorFunc(); + var objects = ctorFunc(); - for (int i = 0; i < circles.Length; i++) + for (int i = 0; i < objects.Length; i++) { - circles[i].StartTime = Time.Current + 1000 + 500 * (i + 1); - circles[i].ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); + objects[i].StartTime = Time.Current + 1000 + 500 * (i + 1); + objects[i].ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty()); - var drawableCircle = new DrawableHitCircle(circles[i]); - hitObjectContainer.Add(drawableCircle); - followPointRenderer.AddFollowPoints(drawableCircle); + DrawableOsuHitObject drawableObject = null; - storeFunc?.Invoke(drawableCircle); + switch (objects[i]) + { + case HitCircle circle: + drawableObject = new DrawableHitCircle(circle); + break; + + case Slider slider: + drawableObject = new DrawableSlider(slider); + break; + + case Spinner spinner: + drawableObject = new DrawableSpinner(spinner); + break; + } + + hitObjectContainer.Add(drawableObject); + followPointRenderer.AddFollowPoints(drawableObject); + + storeFunc?.Invoke(drawableObject); } }); } From bfe7309964b772d1b5a33aa6ee89be9b5359023f Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:34:17 +0900 Subject: [PATCH 125/177] Fix nullref --- osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index 68f37e5d1d..9587c721c6 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -318,7 +318,9 @@ namespace osu.Game.Rulesets.Osu.Tests { end = value; - bindHitObject(end); + if (end != null) + bindHitObject(end); + refreshFollowPoints(); } } From ddfcda9e029e5385ca8969a58e9aac33d70fc75c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:34:24 +0900 Subject: [PATCH 126/177] Remove abstract ConnectionRenderer class --- .../Connections/ConnectionRenderer.cs | 21 ------------------- .../Connections/FollowPointRenderer.cs | 5 +++-- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 2 +- 3 files changed, 4 insertions(+), 24 deletions(-) delete mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs deleted file mode 100644 index 9106f4c7bd..0000000000 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/ConnectionRenderer.cs +++ /dev/null @@ -1,21 +0,0 @@ -// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. -// See the LICENCE file in the repository root for full licence text. - -using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Objects; -using System.Collections.Generic; - -namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections -{ - /// - /// Connects hit objects visually, for example with follow points. - /// - public abstract class ConnectionRenderer : LifetimeManagementContainer - where T : HitObject - { - /// - /// Hit objects to create connections for - /// - public abstract IEnumerable HitObjects { get; set; } - } -} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index a269b87c75..863ce869aa 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -5,11 +5,12 @@ using System; using System.Collections.Generic; using osuTK; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { - public class FollowPointRenderer : ConnectionRenderer + public class FollowPointRenderer : CompositeDrawable { private int pointDistance = 32; @@ -47,7 +48,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections private IEnumerable hitObjects; - public override IEnumerable HitObjects + public IEnumerable HitObjects { get => hitObjects; set diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index 69e53d6eea..cbb29ce387 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Osu.UI { private readonly ApproachCircleProxyContainer approachCircles; private readonly JudgementContainer judgementLayer; - private readonly ConnectionRenderer connectionLayer; + private readonly FollowPointRenderer connectionLayer; public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); From 712253ff50ec387a324cc1a0e10bb3bb948d372c Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 15:39:23 +0900 Subject: [PATCH 127/177] Replace follow point renderer with new implementation --- .../TestSceneFollowPoints.cs | 206 ------------------ .../Drawables/Connections/FollowPointGroup.cs | 120 ++++++++++ .../Connections/FollowPointRenderer.cs | 168 +++++++------- osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs | 16 +- 4 files changed, 206 insertions(+), 304 deletions(-) create mode 100644 osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index 9587c721c6..3c447e5009 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -4,13 +4,11 @@ using System; using System.Collections.Generic; using System.Linq; -using JetBrains.Annotations; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Beatmaps; using osu.Game.Beatmaps.ControlPoints; -using osu.Game.Rulesets.Objects.Types; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; @@ -184,209 +182,5 @@ namespace osu.Game.Rulesets.Osu.Tests followPointRenderer.RemoveFollowPoints(drawableObject); }); } - - private class FollowPointRenderer : CompositeDrawable - { - /// - /// Adds the s around a . - /// This includes s leading into , and s exiting . - /// - /// The to add s for. - public void AddFollowPoints(DrawableOsuHitObject hitObject) - { - var startGroup = new FollowPointGroup(hitObject); - AddInternal(startGroup); - - // Groups are sorted by their start time when added, so the index can be used to post-process other surrounding groups - int startIndex = IndexOfInternal(startGroup); - - if (startIndex < InternalChildren.Count - 1) - { - // h1 -> -> -> h2 - // hitObject nextGroup - - var nextGroup = (FollowPointGroup)InternalChildren[startIndex + 1]; - startGroup.End = nextGroup.Start; - } - - if (startIndex > 0) - { - // h1 -> -> -> h2 - // prevGroup hitObject - - var previousGroup = (FollowPointGroup)InternalChildren[startIndex - 1]; - previousGroup.End = startGroup.Start; - } - } - - /// - /// Removes the s around a . - /// This includes s leading into , and s exiting . - /// - /// The to remove s for. - public void RemoveFollowPoints(DrawableOsuHitObject hitObject) - { - var groups = findGroups(hitObject); - - // Regardless of the position of the hitobject in the beatmap, there will always be a group leading from the hitobject - RemoveInternal(groups.start); - - if (groups.end != null) - { - // When there were two groups referencing the same hitobject, merge them by updating the end group to point to the new end (the start group was already removed) - groups.end.End = groups.start.End; - } - } - - /// - /// Finds the s with as the start and end s. - /// - /// The to find the relevant of. - /// A tuple containing the end group (the where is the end of), - /// and the start group (the where is the start of). - private (FollowPointGroup start, FollowPointGroup end) findGroups(DrawableOsuHitObject hitObject) - { - // endGroup startGroup - // h1 -> -> -> -> -> h2 -> -> -> -> -> h3 - // hitObject - - FollowPointGroup startGroup = null; // The group which the hitobject is the start in - FollowPointGroup endGroup = null; // The group which the hitobject is the end in - - int startIndex = 0; - - for (; startIndex < InternalChildren.Count; startIndex++) - { - var group = (FollowPointGroup)InternalChildren[startIndex]; - - if (group.Start == hitObject) - { - startGroup = group; - break; - } - } - - if (startIndex > 0) - endGroup = (FollowPointGroup)InternalChildren[startIndex - 1]; - - return (startGroup, endGroup); - } - - protected override int Compare(Drawable x, Drawable y) - { - var groupX = (FollowPointGroup)x; - var groupY = (FollowPointGroup)y; - - return groupX.Start.HitObject.StartTime.CompareTo(groupY.Start.HitObject.StartTime); - } - } - - private class FollowPointGroup : CompositeDrawable - { - // Todo: These shouldn't be constants - private const int spacing = 32; - private const double preempt = 800; - - /// - /// The which s will exit from. - /// - [NotNull] - public readonly DrawableOsuHitObject Start; - - public FollowPointGroup(DrawableOsuHitObject start) - { - Start = start; - RelativeSizeAxes = Axes.Both; - } - - protected override void LoadComplete() - { - base.LoadComplete(); - bindHitObject(Start); - } - - private DrawableOsuHitObject end; - - /// - /// The which s will enter. - /// - [CanBeNull] - public DrawableOsuHitObject End - { - get => end; - set - { - end = value; - - if (end != null) - bindHitObject(end); - - refreshFollowPoints(); - } - } - - private void bindHitObject(DrawableOsuHitObject drawableObject) - { - drawableObject.HitObject.StartTimeBindable.BindValueChanged(_ => refreshFollowPoints()); - drawableObject.HitObject.PositionBindable.BindValueChanged(_ => refreshFollowPoints()); - drawableObject.HitObject.DefaultsApplied += refreshFollowPoints; - } - - private void refreshFollowPoints() - { - ClearInternal(); - - if (End == null) - return; - - OsuHitObject osuStart = Start.HitObject; - OsuHitObject osuEnd = End.HitObject; - - if (osuEnd.NewCombo) - return; - - if (osuStart is Spinner || osuEnd is Spinner) - return; - - Vector2 startPosition = osuStart.EndPosition; - Vector2 endPosition = osuEnd.Position; - double startTime = (osuStart as IHasEndTime)?.EndTime ?? osuStart.StartTime; - double endTime = osuEnd.StartTime; - - Vector2 distanceVector = endPosition - startPosition; - int distance = (int)distanceVector.Length; - float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI)); - double duration = endTime - startTime; - - for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing) - { - float fraction = (float)d / distance; - Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector; - Vector2 pointEndPosition = startPosition + fraction * distanceVector; - double fadeOutTime = startTime + fraction * duration; - double fadeInTime = fadeOutTime - preempt; - - FollowPoint fp; - - AddInternal(fp = new FollowPoint - { - Position = pointStartPosition, - Rotation = rotation, - Alpha = 0, - Scale = new Vector2(1.5f * osuEnd.Scale), - }); - - using (fp.BeginAbsoluteSequence(fadeInTime)) - { - fp.FadeIn(osuEnd.TimeFadeIn); - fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out); - fp.MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out); - fp.Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn); - } - - fp.Expire(true); - } - } - } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs new file mode 100644 index 0000000000..06aadcc342 --- /dev/null +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs @@ -0,0 +1,120 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using System; +using JetBrains.Annotations; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Game.Rulesets.Objects.Types; +using osuTK; + +namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections +{ + public class FollowPointGroup : CompositeDrawable + { + // Todo: These shouldn't be constants + private const int spacing = 32; + private const double preempt = 800; + + /// + /// The which s will exit from. + /// + [NotNull] + public readonly DrawableOsuHitObject Start; + + public FollowPointGroup(DrawableOsuHitObject start) + { + Start = start; + RelativeSizeAxes = Axes.Both; + } + + protected override void LoadComplete() + { + base.LoadComplete(); + bindHitObject(Start); + } + + private DrawableOsuHitObject end; + + /// + /// The which s will enter. + /// + [CanBeNull] + public DrawableOsuHitObject End + { + get => end; + set + { + end = value; + + if (end != null) + bindHitObject(end); + + refreshFollowPoints(); + } + } + + private void bindHitObject(DrawableOsuHitObject drawableObject) + { + drawableObject.HitObject.StartTimeBindable.BindValueChanged(_ => refreshFollowPoints()); + drawableObject.HitObject.PositionBindable.BindValueChanged(_ => refreshFollowPoints()); + drawableObject.HitObject.DefaultsApplied += refreshFollowPoints; + } + + private void refreshFollowPoints() + { + ClearInternal(); + + if (End == null) + return; + + OsuHitObject osuStart = Start.HitObject; + OsuHitObject osuEnd = End.HitObject; + + if (osuEnd.NewCombo) + return; + + if (osuStart is Spinner || osuEnd is Spinner) + return; + + Vector2 startPosition = osuStart.EndPosition; + Vector2 endPosition = osuEnd.Position; + double startTime = (osuStart as IHasEndTime)?.EndTime ?? osuStart.StartTime; + double endTime = osuEnd.StartTime; + + Vector2 distanceVector = endPosition - startPosition; + int distance = (int)distanceVector.Length; + float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI)); + double duration = endTime - startTime; + + for (int d = (int)(spacing * 1.5); d < distance - spacing; d += spacing) + { + float fraction = (float)d / distance; + Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector; + Vector2 pointEndPosition = startPosition + fraction * distanceVector; + double fadeOutTime = startTime + fraction * duration; + double fadeInTime = fadeOutTime - preempt; + + FollowPoint fp; + + AddInternal(fp = new FollowPoint + { + Position = pointStartPosition, + Rotation = rotation, + Alpha = 0, + Scale = new Vector2(1.5f * osuEnd.Scale), + }); + + using (fp.BeginAbsoluteSequence(fadeInTime)) + { + fp.FadeIn(osuEnd.TimeFadeIn); + fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out); + fp.MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out); + fp.Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn); + } + + fp.Expire(true); + } + } + } +} diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 863ce869aa..47c35746f0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -1,122 +1,104 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; -using System.Collections.Generic; -using osuTK; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; -using osu.Game.Rulesets.Objects.Types; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { public class FollowPointRenderer : CompositeDrawable { - private int pointDistance = 32; + /// + /// Adds the s around a . + /// This includes s leading into , and s exiting . + /// + /// The to add s for. + public void AddFollowPoints(DrawableOsuHitObject hitObject) + { + var startGroup = new FollowPointGroup(hitObject); + AddInternal(startGroup); + + // Groups are sorted by their start time when added, so the index can be used to post-process other surrounding groups + int startIndex = IndexOfInternal(startGroup); + + if (startIndex < InternalChildren.Count - 1) + { + // h1 -> -> -> h2 + // hitObject nextGroup + + var nextGroup = (FollowPointGroup)InternalChildren[startIndex + 1]; + startGroup.End = nextGroup.Start; + } + + if (startIndex > 0) + { + // h1 -> -> -> h2 + // prevGroup hitObject + + var previousGroup = (FollowPointGroup)InternalChildren[startIndex - 1]; + previousGroup.End = startGroup.Start; + } + } /// - /// Determines how much space there is between points. + /// Removes the s around a . + /// This includes s leading into , and s exiting . /// - public int PointDistance + /// The to remove s for. + public void RemoveFollowPoints(DrawableOsuHitObject hitObject) { - get => pointDistance; - set - { - if (pointDistance == value) return; + var groups = findGroups(hitObject); - pointDistance = value; - update(); + // Regardless of the position of the hitobject in the beatmap, there will always be a group leading from the hitobject + RemoveInternal(groups.start); + + if (groups.end != null) + { + // When there were two groups referencing the same hitobject, merge them by updating the end group to point to the new end (the start group was already removed) + groups.end.End = groups.start.End; } } - private int preEmpt = 800; - /// - /// Follow points to the next hitobject start appearing for this many milliseconds before an hitobject's end time. + /// Finds the s with as the start and end s. /// - public int PreEmpt + /// The to find the relevant of. + /// A tuple containing the end group (the where is the end of), + /// and the start group (the where is the start of). + private (FollowPointGroup start, FollowPointGroup end) findGroups(DrawableOsuHitObject hitObject) { - get => preEmpt; - set + // endGroup startGroup + // h1 -> -> -> -> -> h2 -> -> -> -> -> h3 + // hitObject + + FollowPointGroup startGroup = null; // The group which the hitobject is the start in + FollowPointGroup endGroup = null; // The group which the hitobject is the end in + + int startIndex = 0; + + for (; startIndex < InternalChildren.Count; startIndex++) { - if (preEmpt == value) return; + var group = (FollowPointGroup)InternalChildren[startIndex]; - preEmpt = value; - update(); - } - } - - private IEnumerable hitObjects; - - public IEnumerable HitObjects - { - get => hitObjects; - set - { - hitObjects = value; - update(); - } - } - - public override bool RemoveCompletedTransforms => false; - - private void update() - { - ClearInternal(); - - if (hitObjects == null) - return; - - OsuHitObject prevHitObject = null; - - foreach (var currHitObject in hitObjects) - { - if (prevHitObject != null && !currHitObject.NewCombo && !(prevHitObject is Spinner) && !(currHitObject is Spinner)) + if (group.Start == hitObject) { - Vector2 startPosition = prevHitObject.EndPosition; - Vector2 endPosition = currHitObject.Position; - double startTime = (prevHitObject as IHasEndTime)?.EndTime ?? prevHitObject.StartTime; - double endTime = currHitObject.StartTime; - - Vector2 distanceVector = endPosition - startPosition; - int distance = (int)distanceVector.Length; - float rotation = (float)(Math.Atan2(distanceVector.Y, distanceVector.X) * (180 / Math.PI)); - double duration = endTime - startTime; - - for (int d = (int)(PointDistance * 1.5); d < distance - PointDistance; d += PointDistance) - { - float fraction = (float)d / distance; - Vector2 pointStartPosition = startPosition + (fraction - 0.1f) * distanceVector; - Vector2 pointEndPosition = startPosition + fraction * distanceVector; - double fadeOutTime = startTime + fraction * duration; - double fadeInTime = fadeOutTime - PreEmpt; - - FollowPoint fp; - - AddInternal(fp = new FollowPoint - { - Position = pointStartPosition, - Rotation = rotation, - Alpha = 0, - Scale = new Vector2(1.5f * currHitObject.Scale), - }); - - using (fp.BeginAbsoluteSequence(fadeInTime)) - { - fp.FadeIn(currHitObject.TimeFadeIn); - fp.ScaleTo(currHitObject.Scale, currHitObject.TimeFadeIn, Easing.Out); - - fp.MoveTo(pointEndPosition, currHitObject.TimeFadeIn, Easing.Out); - - fp.Delay(fadeOutTime - fadeInTime).FadeOut(currHitObject.TimeFadeIn); - } - - fp.Expire(true); - } + startGroup = group; + break; } - - prevHitObject = currHitObject; } + + if (startIndex > 0) + endGroup = (FollowPointGroup)InternalChildren[startIndex - 1]; + + return (startGroup, endGroup); + } + + protected override int Compare(Drawable x, Drawable y) + { + var groupX = (FollowPointGroup)x; + var groupY = (FollowPointGroup)y; + + return groupX.Start.HitObject.StartTime.CompareTo(groupY.Start.HitObject.StartTime); } } } diff --git a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs index cbb29ce387..6d1ea4bbfc 100644 --- a/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs +++ b/osu.Game.Rulesets.Osu/UI/OsuPlayfield.cs @@ -9,7 +9,6 @@ using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects.Drawables; using osu.Game.Rulesets.Osu.Objects.Drawables.Connections; using osu.Game.Rulesets.UI; -using System.Linq; using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Osu.UI.Cursor; using osu.Game.Skinning; @@ -20,7 +19,7 @@ namespace osu.Game.Rulesets.Osu.UI { private readonly ApproachCircleProxyContainer approachCircles; private readonly JudgementContainer judgementLayer; - private readonly FollowPointRenderer connectionLayer; + private readonly FollowPointRenderer followPoints; public static readonly Vector2 BASE_SIZE = new Vector2(512, 384); @@ -30,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.UI { InternalChildren = new Drawable[] { - connectionLayer = new FollowPointRenderer + followPoints = new FollowPointRenderer { RelativeSizeAxes = Axes.Both, Depth = 2, @@ -64,11 +63,18 @@ namespace osu.Game.Rulesets.Osu.UI }; base.Add(h); + + followPoints.AddFollowPoints((DrawableOsuHitObject)h); } - public override void PostProcess() + public override bool Remove(DrawableHitObject h) { - connectionLayer.HitObjects = HitObjectContainer.Objects.Select(d => d.HitObject).OfType(); + bool result = base.Remove(h); + + if (result) + followPoints.RemoveFollowPoints((DrawableOsuHitObject)h); + + return result; } private void onNewResult(DrawableHitObject judgedObject, JudgementResult result) From a19e26f8aa6c8d1bbb5bf80c499704cb14f6f729 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 19:21:39 +0900 Subject: [PATCH 128/177] Improve performance of refreshes --- .../Drawables/Connections/FollowPointGroup.cs | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs index 06aadcc342..750e9559fc 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections protected override void LoadComplete() { base.LoadComplete(); - bindHitObject(Start); + bindEvents(Start); } private DrawableOsuHitObject end; @@ -48,20 +48,21 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections end = value; if (end != null) - bindHitObject(end); + bindEvents(end); - refreshFollowPoints(); + refresh(); } } - private void bindHitObject(DrawableOsuHitObject drawableObject) + private void bindEvents(DrawableOsuHitObject drawableObject) { - drawableObject.HitObject.StartTimeBindable.BindValueChanged(_ => refreshFollowPoints()); - drawableObject.HitObject.PositionBindable.BindValueChanged(_ => refreshFollowPoints()); - drawableObject.HitObject.DefaultsApplied += refreshFollowPoints; + drawableObject.HitObject.PositionBindable.BindValueChanged(_ => scheduleRefresh()); + drawableObject.HitObject.DefaultsApplied += scheduleRefresh; } - private void refreshFollowPoints() + private void scheduleRefresh() => Scheduler.AddOnce(refresh); + + private void refresh() { ClearInternal(); From 3b6064336b63f6bd00f7bdee699cb6dae2903279 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 19:22:07 +0900 Subject: [PATCH 129/177] Implement group re-ordering based on start time --- .../Drawables/Connections/FollowPointGroup.cs | 12 +- .../Connections/FollowPointRenderer.cs | 115 ++++++++---------- 2 files changed, 65 insertions(+), 62 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs index 750e9559fc..79d69acd67 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs @@ -3,6 +3,7 @@ using System; using JetBrains.Annotations; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Game.Rulesets.Objects.Types; @@ -16,16 +17,25 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections private const int spacing = 32; private const double preempt = 800; + public readonly Bindable StartTime = new Bindable(); + /// /// The which s will exit from. /// [NotNull] public readonly DrawableOsuHitObject Start; - public FollowPointGroup(DrawableOsuHitObject start) + /// + /// Creates a new . + /// + /// The which s will exit from. + public FollowPointGroup([NotNull] DrawableOsuHitObject start) { Start = start; + RelativeSizeAxes = Axes.Both; + + StartTime.BindTo(Start.HitObject.StartTimeBindable); } protected override void LoadComplete() diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 47c35746f0..892442f0a7 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -1,6 +1,9 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Collections.Generic; +using System.Linq; +using osu.Framework.Extensions; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -8,97 +11,87 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { public class FollowPointRenderer : CompositeDrawable { + private readonly List groups = new List(); + /// /// Adds the s around a . /// This includes s leading into , and s exiting . /// /// The to add s for. public void AddFollowPoints(DrawableOsuHitObject hitObject) - { - var startGroup = new FollowPointGroup(hitObject); - AddInternal(startGroup); - - // Groups are sorted by their start time when added, so the index can be used to post-process other surrounding groups - int startIndex = IndexOfInternal(startGroup); - - if (startIndex < InternalChildren.Count - 1) - { - // h1 -> -> -> h2 - // hitObject nextGroup - - var nextGroup = (FollowPointGroup)InternalChildren[startIndex + 1]; - startGroup.End = nextGroup.Start; - } - - if (startIndex > 0) - { - // h1 -> -> -> h2 - // prevGroup hitObject - - var previousGroup = (FollowPointGroup)InternalChildren[startIndex - 1]; - previousGroup.End = startGroup.Start; - } - } + => addGroup(new FollowPointGroup(hitObject).With(g => g.StartTime.BindValueChanged(_ => onStartTimeChanged(g)))); /// /// Removes the s around a . /// This includes s leading into , and s exiting . /// /// The to remove s for. - public void RemoveFollowPoints(DrawableOsuHitObject hitObject) + public void RemoveFollowPoints(DrawableOsuHitObject hitObject) => removeGroup(groups.Single(g => g.Start == hitObject)); + + /// + /// Adds a to this . + /// + /// The to add. + /// The index of in . + private int addGroup(FollowPointGroup group) { - var groups = findGroups(hitObject); + AddInternal(group); - // Regardless of the position of the hitobject in the beatmap, there will always be a group leading from the hitobject - RemoveInternal(groups.start); + // Groups are sorted by their start time when added such that the index can be used to post-process other surrounding groups + int index = groups.AddInPlace(group, Comparer.Create((g1, g2) => g1.StartTime.Value.CompareTo(g2.StartTime.Value))); - if (groups.end != null) + if (index < groups.Count - 1) { - // When there were two groups referencing the same hitobject, merge them by updating the end group to point to the new end (the start group was already removed) - groups.end.End = groups.start.End; + // Update the group's end point to the next hitobject + // h1 -> -> -> h2 + // hitObject nextGroup + + FollowPointGroup nextGroup = groups[index + 1]; + group.End = nextGroup.Start; } + + if (index > 0) + { + // Previous group's end point to the current group's start point + // h1 -> -> -> h2 + // prevGroup hitObject + + FollowPointGroup previousGroup = groups[index - 1]; + previousGroup.End = group.Start; + } + + return index; } /// - /// Finds the s with as the start and end s. + /// Removes a from this . /// - /// The to find the relevant of. - /// A tuple containing the end group (the where is the end of), - /// and the start group (the where is the start of). - private (FollowPointGroup start, FollowPointGroup end) findGroups(DrawableOsuHitObject hitObject) + /// The to remove. + /// Whether was removed. + private bool removeGroup(FollowPointGroup group) { - // endGroup startGroup - // h1 -> -> -> -> -> h2 -> -> -> -> -> h3 - // hitObject + RemoveInternal(group); - FollowPointGroup startGroup = null; // The group which the hitobject is the start in - FollowPointGroup endGroup = null; // The group which the hitobject is the end in + int index = groups.IndexOf(group); - int startIndex = 0; - - for (; startIndex < InternalChildren.Count; startIndex++) + if (index > 0) { - var group = (FollowPointGroup)InternalChildren[startIndex]; - - if (group.Start == hitObject) - { - startGroup = group; - break; - } + // Update the previous group's end point to the next group's start point + // h1 -> -> -> h2 -> -> -> h3 + // prevGroup group nextGroup + // The current group's end point is used since there may not be a next group + FollowPointGroup previousGroup = groups[index - 1]; + previousGroup.End = group.End; } - if (startIndex > 0) - endGroup = (FollowPointGroup)InternalChildren[startIndex - 1]; - - return (startGroup, endGroup); + return groups.Remove(group); } - protected override int Compare(Drawable x, Drawable y) + private void onStartTimeChanged(FollowPointGroup group) { - var groupX = (FollowPointGroup)x; - var groupY = (FollowPointGroup)y; - - return groupX.Start.HitObject.StartTime.CompareTo(groupY.Start.HitObject.StartTime); + // Naive but can be improved if performance becomes problematic + removeGroup(group); + addGroup(group); } } } From 1ef2b81041dfd9225ffb7e792b8c55bfa76d6ff1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 1 Nov 2019 22:08:50 +0900 Subject: [PATCH 130/177] Fix follow point lifetime not being updated correctly --- .../Drawables/Connections/FollowPointGroup.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs index 79d69acd67..47196f4893 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs @@ -118,13 +118,15 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections using (fp.BeginAbsoluteSequence(fadeInTime)) { - fp.FadeIn(osuEnd.TimeFadeIn); - fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out); - fp.MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out); - fp.Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn); - } + // See: Expire calls are separated due to https://github.com/ppy/osu-framework/issues/2941 - fp.Expire(true); + fp.FadeIn(osuEnd.TimeFadeIn).Expire(true); + + fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out) + .MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out) + .Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn) + .Expire(); + } } } } From f77de7d88013f30bd3567b4af2f7d223029b50e3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Nov 2019 18:25:38 +0900 Subject: [PATCH 131/177] Simplify implementation --- .../Compose/Components/BlueprintContainer.cs | 26 +++++-------------- 1 file changed, 7 insertions(+), 19 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 7a3a303ff3..02dd60eac1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -254,31 +254,19 @@ namespace osu.Game.Screens.Edit.Compose.Components { Debug.Assert(!clickSelectionBegan); - bool hoveringSelected = false; + // If a select blueprint is already hovered, disallow changes in selection. + if (selectionHandler.SelectedBlueprints.Any(s => s.IsHovered)) + return; - // Make sure any already-selected blueprints aren't being hovered over - foreach (SelectionBlueprint selected in selectionHandler.SelectedBlueprints) + foreach (SelectionBlueprint blueprint in selectionBlueprints.AliveBlueprints) { - if (selected.IsHovered) + if (blueprint.IsHovered) { - hoveringSelected = true; + selectionHandler.HandleSelectionRequested(blueprint, e.CurrentState); + clickSelectionBegan = true; break; } } - - // Attempt a new selection at the mouse position - if (!hoveringSelected) - { - foreach (SelectionBlueprint blueprint in selectionBlueprints.AliveBlueprints) - { - if (blueprint.IsHovered) - { - selectionHandler.HandleSelectionRequested(blueprint, e.CurrentState); - clickSelectionBegan = true; - break; - } - } - } } /// From f4b93ec9438f49d852bb7172ad3a6b8c368fd5d1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 5 Nov 2019 18:29:08 +0900 Subject: [PATCH 132/177] Add exception when holding control --- .../Screens/Edit/Compose/Components/BlueprintContainer.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 02dd60eac1..288c712bde 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -255,7 +255,9 @@ namespace osu.Game.Screens.Edit.Compose.Components Debug.Assert(!clickSelectionBegan); // If a select blueprint is already hovered, disallow changes in selection. - if (selectionHandler.SelectedBlueprints.Any(s => s.IsHovered)) + // Exception is made when holding control, as deselection should still be allowed. + if (!e.CurrentState.Keyboard.ControlPressed && + selectionHandler.SelectedBlueprints.Any(s => s.IsHovered)) return; foreach (SelectionBlueprint blueprint in selectionBlueprints.AliveBlueprints) From 6c58faf30cab3f8f36d2b8d77621f0bca2529f1a Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 19:31:48 +0900 Subject: [PATCH 133/177] Fix group ends potentially not being updated correctly --- .../Objects/Drawables/Connections/FollowPointRenderer.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 892442f0a7..e29cf6e128 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -49,6 +49,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections FollowPointGroup nextGroup = groups[index + 1]; group.End = nextGroup.Start; } + else + group.End = null; if (index > 0) { From f2118b0eba6fa022023ef453b11b9ed61dcf2580 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 19:31:58 +0900 Subject: [PATCH 134/177] Add automated test cases --- .../TestSceneFollowPoints.cs | 178 +++++++++++------- .../Connections/FollowPointRenderer.cs | 2 + 2 files changed, 113 insertions(+), 67 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index 3c447e5009..aac0119dc7 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -2,8 +2,6 @@ // See the LICENCE file in the repository root for full licence text. using System; -using System.Collections.Generic; -using System.Linq; using NUnit.Framework; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; @@ -27,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Tests { Children = new Drawable[] { - hitObjectContainer = new Container { RelativeSizeAxes = Axes.Both }, + hitObjectContainer = new TestHitObjectContainer { RelativeSizeAxes = Axes.Both }, followPointRenderer = new FollowPointRenderer { RelativeSizeAxes = Axes.Both } }; }); @@ -35,108 +33,97 @@ namespace osu.Game.Rulesets.Osu.Tests [Test] public void TestAddSingleHitCircle() { - addObjects(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); + addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); + + assertGroups(); } [Test] public void TestRemoveSingleHitCircle() { - DrawableOsuHitObject obj = null; + addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); - addObjects(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }, o => obj = o); - removeObject(() => obj); + removeObjectStep(() => getObject(0)); + + assertGroups(); } [Test] public void TestAddMultipleHitCircles() { - addObjects(() => new OsuHitObject[] - { - new HitCircle { Position = new Vector2(100, 100) }, - new HitCircle { Position = new Vector2(200, 200) }, - new HitCircle { Position = new Vector2(300, 300) }, - new HitCircle { Position = new Vector2(400, 400) }, - new HitCircle { Position = new Vector2(500, 500) }, - }); + addMultipleObjectsStep(); + + assertGroups(); } [Test] public void TestRemoveEndHitCircle() { - var objects = new List(); + addMultipleObjectsStep(); - AddStep("reset", () => objects.Clear()); + removeObjectStep(() => getObject(4)); - addObjects(() => new OsuHitObject[] - { - new HitCircle { Position = new Vector2(100, 100) }, - new HitCircle { Position = new Vector2(200, 200) }, - new HitCircle { Position = new Vector2(300, 300) }, - new HitCircle { Position = new Vector2(400, 400) }, - new HitCircle { Position = new Vector2(500, 500) }, - }, o => objects.Add(o)); - - removeObject(() => objects.Last()); + assertGroups(); } [Test] public void TestRemoveStartHitCircle() { - var objects = new List(); + addMultipleObjectsStep(); - AddStep("reset", () => objects.Clear()); + removeObjectStep(() => getObject(0)); - addObjects(() => new OsuHitObject[] - { - new HitCircle { Position = new Vector2(100, 100) }, - new HitCircle { Position = new Vector2(200, 200) }, - new HitCircle { Position = new Vector2(300, 300) }, - new HitCircle { Position = new Vector2(400, 400) }, - new HitCircle { Position = new Vector2(500, 500) }, - }, o => objects.Add(o)); - - removeObject(() => objects.First()); + assertGroups(); } [Test] public void TestRemoveMiddleHitCircle() { - var objects = new List(); + addMultipleObjectsStep(); - AddStep("reset", () => objects.Clear()); + removeObjectStep(() => getObject(2)); - addObjects(() => new OsuHitObject[] - { - new HitCircle { Position = new Vector2(100, 100) }, - new HitCircle { Position = new Vector2(200, 200) }, - new HitCircle { Position = new Vector2(300, 300) }, - new HitCircle { Position = new Vector2(400, 400) }, - new HitCircle { Position = new Vector2(500, 500) }, - }, o => objects.Add(o)); - - removeObject(() => objects[2]); + assertGroups(); } [Test] public void TestMoveHitCircle() { - var objects = new List(); + addMultipleObjectsStep(); - AddStep("reset", () => objects.Clear()); + AddStep("move hitobject", () => getObject(2).HitObject.Position = new Vector2(300, 100)); - addObjects(() => new OsuHitObject[] - { - new HitCircle { Position = new Vector2(100, 100) }, - new HitCircle { Position = new Vector2(200, 200) }, - new HitCircle { Position = new Vector2(300, 300) }, - new HitCircle { Position = new Vector2(400, 400) }, - new HitCircle { Position = new Vector2(500, 500) }, - }, o => objects.Add(o)); - - AddStep("move hitobject", () => objects[2].HitObject.Position = new Vector2(300, 100)); + assertGroups(); } - private void addObjects(Func ctorFunc, Action storeFunc = null) + [TestCase(0, 0)] // Start -> Start + [TestCase(0, 2)] // Start -> Middle + [TestCase(0, 5)] // Start -> End + [TestCase(2, 0)] // Middle -> Start + [TestCase(1, 3)] // Middle -> Middle (forwards) + [TestCase(3, 1)] // Middle -> Middle (backwards) + [TestCase(4, 0)] // End -> Start + [TestCase(4, 2)] // End -> Middle + [TestCase(4, 4)] // End -> End + public void TestReorderHitObjects(int startIndex, int endIndex) + { + addMultipleObjectsStep(); + + reorderObjectStep(startIndex, endIndex); + + assertGroups(); + } + + private void addMultipleObjectsStep() => addObjectsStep(() => new OsuHitObject[] + { + new HitCircle { Position = new Vector2(100, 100) }, + new HitCircle { Position = new Vector2(200, 200) }, + new HitCircle { Position = new Vector2(300, 300) }, + new HitCircle { Position = new Vector2(400, 400) }, + new HitCircle { Position = new Vector2(500, 500) }, + }); + + private void addObjectsStep(Func ctorFunc) { AddStep("add hitobjects", () => { @@ -166,13 +153,11 @@ namespace osu.Game.Rulesets.Osu.Tests hitObjectContainer.Add(drawableObject); followPointRenderer.AddFollowPoints(drawableObject); - - storeFunc?.Invoke(drawableObject); } }); } - private void removeObject(Func getFunc) + private void removeObjectStep(Func getFunc) { AddStep("remove hitobject", () => { @@ -182,5 +167,64 @@ namespace osu.Game.Rulesets.Osu.Tests followPointRenderer.RemoveFollowPoints(drawableObject); }); } + + private void reorderObjectStep(int startIndex, int endIndex) + { + AddStep($"move object {startIndex} to {endIndex}", () => + { + DrawableOsuHitObject toReorder = getObject(startIndex); + + double targetTime; + if (endIndex < hitObjectContainer.Count) + targetTime = getObject(endIndex).HitObject.StartTime - 1; + else + targetTime = getObject(hitObjectContainer.Count - 1).HitObject.StartTime + 1; + + hitObjectContainer.Remove(toReorder); + toReorder.HitObject.StartTime = targetTime; + hitObjectContainer.Add(toReorder); + }); + } + + private void assertGroups() + { + AddAssert("has correct group count", () => followPointRenderer.Groups.Count == hitObjectContainer.Count); + AddAssert("group endpoints are correct", () => + { + for (int i = 0; i < hitObjectContainer.Count; i++) + { + DrawableOsuHitObject expectedStart = getObject(i); + DrawableOsuHitObject expectedEnd = i < hitObjectContainer.Count - 1 ? getObject(i + 1) : null; + + if (getGroup(i).Start != expectedStart) + throw new AssertionException($"Object {i} expected to be the start of group {i}."); + + if (getGroup(i).End != expectedEnd) + throw new AssertionException($"Object {(expectedEnd == null ? "null" : i.ToString())} expected to be the end of group {i}."); + } + + return true; + }); + } + + private DrawableOsuHitObject getObject(int index) => hitObjectContainer[index]; + + private FollowPointGroup getGroup(int index) => followPointRenderer.Groups[index]; + + private class TestHitObjectContainer : Container + { + protected override int Compare(Drawable x, Drawable y) + { + var osuX = (DrawableOsuHitObject)x; + var osuY = (DrawableOsuHitObject)y; + + int compare = osuX.HitObject.StartTime.CompareTo(osuY.HitObject.StartTime); + + if (compare == 0) + return base.Compare(x, y); + + return compare; + } + } } } diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index e29cf6e128..e5e8efae8e 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -11,6 +11,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { public class FollowPointRenderer : CompositeDrawable { + internal IReadOnlyList Groups => groups; + private readonly List groups = new List(); /// From d0286037696594660cc0641d94e68ca616ba9bdf Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 22:50:01 +0900 Subject: [PATCH 135/177] Rename tests --- .../TestSceneFollowPoints.cs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index aac0119dc7..79a1bb579f 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.Tests }); [Test] - public void TestAddSingleHitCircle() + public void TestAddObject() { addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); @@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestRemoveSingleHitCircle() + public void TestRemoveObject() { addObjectsStep(() => new OsuHitObject[] { new HitCircle { Position = new Vector2(100, 100) } }); @@ -49,7 +49,7 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestAddMultipleHitCircles() + public void TestAddMultipleObjects() { addMultipleObjectsStep(); @@ -57,7 +57,7 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestRemoveEndHitCircle() + public void TestRemoveEndObject() { addMultipleObjectsStep(); @@ -67,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestRemoveStartHitCircle() + public void TestRemoveStartObject() { addMultipleObjectsStep(); @@ -77,7 +77,7 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestRemoveMiddleHitCircle() + public void TestRemoveMiddleObject() { addMultipleObjectsStep(); @@ -87,7 +87,7 @@ namespace osu.Game.Rulesets.Osu.Tests } [Test] - public void TestMoveHitCircle() + public void TestMoveObject() { addMultipleObjectsStep(); @@ -105,7 +105,7 @@ namespace osu.Game.Rulesets.Osu.Tests [TestCase(4, 0)] // End -> Start [TestCase(4, 2)] // End -> Middle [TestCase(4, 4)] // End -> End - public void TestReorderHitObjects(int startIndex, int endIndex) + public void TestReorderObjects(int startIndex, int endIndex) { addMultipleObjectsStep(); From 68a81e0eb0f813fc3710ea09aa273d75ac2b8671 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 22:50:21 +0900 Subject: [PATCH 136/177] Fix follow point transforms not working after rewind --- .../Objects/Drawables/Connections/FollowPoint.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index 89ffddf4cb..7ba044b462 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -18,6 +18,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections public override bool RemoveWhenNotAlive => false; + public override bool RemoveCompletedTransforms => false; + public FollowPoint() { Origin = Anchor.Centre; From d762ec959c9240594a46ba2228095fea4b7e10ca Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 22:50:38 +0900 Subject: [PATCH 137/177] Schedule group refresh when loaded --- .../Objects/Drawables/Connections/FollowPointGroup.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs index 47196f4893..c77ecd355f 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs @@ -60,7 +60,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections if (end != null) bindEvents(end); - refresh(); + if (IsLoaded) + scheduleRefresh(); + else + refresh(); } } From 0a2af2b0fe067b3caece643b1acbdc29b427a823 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 23:02:39 +0900 Subject: [PATCH 138/177] Apply transform override at a higher level --- .../Objects/Drawables/Connections/FollowPoint.cs | 2 -- .../Objects/Drawables/Connections/FollowPointRenderer.cs | 2 ++ 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index 7ba044b462..89ffddf4cb 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -18,8 +18,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections public override bool RemoveWhenNotAlive => false; - public override bool RemoveCompletedTransforms => false; - public FollowPoint() { Origin = Anchor.Centre; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index e5e8efae8e..aa9ec8d96d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -15,6 +15,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections private readonly List groups = new List(); + public override bool RemoveCompletedTransforms => false; + /// /// Adds the s around a . /// This includes s leading into , and s exiting . From aff275ea21784999ca29e37a695da2df430332a7 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 23:03:05 +0900 Subject: [PATCH 139/177] Revert "Fix follow point lifetime not being updated correctly" This reverts commit 1ef2b81041dfd9225ffb7e792b8c55bfa76d6ff1. --- .../Drawables/Connections/FollowPointGroup.cs | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs index c77ecd355f..9d651373ff 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs @@ -121,15 +121,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections using (fp.BeginAbsoluteSequence(fadeInTime)) { - // See: Expire calls are separated due to https://github.com/ppy/osu-framework/issues/2941 - - fp.FadeIn(osuEnd.TimeFadeIn).Expire(true); - - fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out) - .MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out) - .Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn) - .Expire(); + fp.FadeIn(osuEnd.TimeFadeIn); + fp.ScaleTo(osuEnd.Scale, osuEnd.TimeFadeIn, Easing.Out); + fp.MoveTo(pointEndPosition, osuEnd.TimeFadeIn, Easing.Out); + fp.Delay(fadeOutTime - fadeInTime).FadeOut(osuEnd.TimeFadeIn); } + + fp.Expire(true); } } } From 68ca5cb26a6b1f0d56ba258c0edb29afb808c646 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Tue, 5 Nov 2019 23:20:46 +0900 Subject: [PATCH 140/177] Adjust comments --- .../Drawables/Connections/FollowPoint.cs | 3 +++ .../Drawables/Connections/FollowPointGroup.cs | 6 ++++++ .../Connections/FollowPointRenderer.cs | 19 ++++++++++++++----- 3 files changed, 23 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs index 89ffddf4cb..db34ae1d87 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPoint.cs @@ -12,6 +12,9 @@ using osu.Game.Skinning; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { + /// + /// A single follow point positioned between two adjacent s. + /// public class FollowPoint : Container { private const float width = 8; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs index 9d651373ff..168d2b8532 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs @@ -11,12 +11,18 @@ using osuTK; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { + /// + /// Visualises the s between two s. + /// public class FollowPointGroup : CompositeDrawable { // Todo: These shouldn't be constants private const int spacing = 32; private const double preempt = 800; + /// + /// The start time of . + /// public readonly Bindable StartTime = new Bindable(); /// diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index aa9ec8d96d..afd86e004d 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -9,8 +9,14 @@ using osu.Framework.Graphics.Containers; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { + /// + /// Visualises groups of s. + /// public class FollowPointRenderer : CompositeDrawable { + /// + /// All the s contained by this . + /// internal IReadOnlyList Groups => groups; private readonly List groups = new List(); @@ -46,21 +52,24 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections if (index < groups.Count - 1) { - // Update the group's end point to the next hitobject + // Update the group's end point to the next group's start point // h1 -> -> -> h2 - // hitObject nextGroup + // group nextGroup FollowPointGroup nextGroup = groups[index + 1]; group.End = nextGroup.Start; } else + { + // The end point may be non-null during re-ordering group.End = null; + } if (index > 0) { - // Previous group's end point to the current group's start point + // Update the previous group's end point to the current group's start point // h1 -> -> -> h2 - // prevGroup hitObject + // prevGroup group FollowPointGroup previousGroup = groups[index - 1]; previousGroup.End = group.Start; @@ -95,7 +104,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections private void onStartTimeChanged(FollowPointGroup group) { - // Naive but can be improved if performance becomes problematic + // Naive but can be improved if performance becomes an issue removeGroup(group); addGroup(group); } From 1f40641d05cc9a1da02d00eaf4ba14a30c367299 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 10:32:09 +0900 Subject: [PATCH 141/177] Add failing test --- osu.Game.Tests/Visual/Gameplay/TestScenePause.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs index 64022b2410..6e8975f11b 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestScenePause.cs @@ -237,6 +237,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("player not exited", () => Player.IsCurrentScreen()); AddStep("exit", () => Player.Exit()); confirmExited(); + confirmNoTrackAdjustments(); } private void confirmPaused() @@ -258,6 +259,11 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("player exited", () => !Player.IsCurrentScreen()); } + private void confirmNoTrackAdjustments() + { + AddAssert("track has no adjustments", () => Beatmap.Value.Track.AggregateFrequency.Value == 1); + } + private void restart() => AddStep("restart", () => Player.Restart()); private void pause() => AddStep("pause", () => Player.Pause()); private void resume() => AddStep("resume", () => Player.Resume()); From cd1dd0f8981d2ed7c5c03b67d401ebc1ec0b4a62 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 10:38:04 +0900 Subject: [PATCH 142/177] Fix adjustments not being removed correctly on retry from pause --- osu.Game/Screens/Play/GameplayClockContainer.cs | 16 ++++++++++------ osu.Game/Screens/Play/Player.cs | 2 -- 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Play/GameplayClockContainer.cs b/osu.Game/Screens/Play/GameplayClockContainer.cs index f2efbe6073..2f2028ff53 100644 --- a/osu.Game/Screens/Play/GameplayClockContainer.cs +++ b/osu.Game/Screens/Play/GameplayClockContainer.cs @@ -162,16 +162,12 @@ namespace osu.Game.Screens.Play if (sourceClock != beatmap.Track) return; + removeSourceClockAdjustments(); + sourceClock = new TrackVirtual(beatmap.Track.Length); adjustableClock.ChangeSource(sourceClock); } - public void ResetLocalAdjustments() - { - // In the case of replays, we may have changed the playback rate. - UserPlaybackRate.Value = 1; - } - protected override void Update() { if (!IsPaused.Value) @@ -198,6 +194,14 @@ namespace osu.Game.Screens.Play protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); + + removeSourceClockAdjustments(); + sourceClock = null; + } + + private void removeSourceClockAdjustments() + { + sourceClock.ResetSpeedAdjustments(); (sourceClock as IAdjustableAudioComponent)?.RemoveAdjustment(AdjustableProperty.Frequency, pauseFreqAdjust); } } diff --git a/osu.Game/Screens/Play/Player.cs b/osu.Game/Screens/Play/Player.cs index a3c39d9cc1..a9b0649fab 100644 --- a/osu.Game/Screens/Play/Player.cs +++ b/osu.Game/Screens/Play/Player.cs @@ -536,8 +536,6 @@ namespace osu.Game.Screens.Play return true; } - GameplayClockContainer.ResetLocalAdjustments(); - // GameplayClockContainer performs seeks / start / stop operations on the beatmap's track. // as we are no longer the current screen, we cannot guarantee the track is still usable. GameplayClockContainer.StopUsingBeatmapClock(); From 892ef017ef3b85ba184e35202a3eb14f59a51c09 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 6 Nov 2019 03:05:03 +0000 Subject: [PATCH 143/177] Bump ppy.osu.Framework.iOS from 2019.1029.0 to 2019.1106.0 Bumps [ppy.osu.Framework.iOS](https://github.com/ppy/osu-framework) from 2019.1029.0 to 2019.1106.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.1029.0...2019.1106.0) Signed-off-by: dependabot-preview[bot] --- osu.iOS.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.iOS.props b/osu.iOS.props index 719aced705..d257948831 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -119,7 +119,7 @@ - + From 1d83f51a804f9a0972455433666f42219ed057c9 Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Wed, 6 Nov 2019 03:05:05 +0000 Subject: [PATCH 144/177] Bump ppy.osu.Framework.Android from 2019.1029.0 to 2019.1106.0 Bumps [ppy.osu.Framework.Android](https://github.com/ppy/osu-framework) from 2019.1029.0 to 2019.1106.0. - [Release notes](https://github.com/ppy/osu-framework/releases) - [Commits](https://github.com/ppy/osu-framework/compare/2019.1029.0...2019.1106.0) Signed-off-by: dependabot-preview[bot] --- osu.Android.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Android.props b/osu.Android.props index 8b31be3f12..d1860acbf9 100644 --- a/osu.Android.props +++ b/osu.Android.props @@ -62,6 +62,6 @@ - + From 8cf349c1eefa1996a1d5f9446181150bcf574ae8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 12:10:53 +0900 Subject: [PATCH 145/177] Update once more --- osu.Game/osu.Game.csproj | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj index 42b930b1af..dbe6559c40 100644 --- a/osu.Game/osu.Game.csproj +++ b/osu.Game/osu.Game.csproj @@ -26,7 +26,7 @@ - + From f20bfe7a554849d6d1fc762741e0fd4f084a4e07 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 12:16:48 +0900 Subject: [PATCH 146/177] Fix extra semicolon --- osu.Game/Screens/Edit/Timing/DifficultySection.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs index bd363c3158..cd33425ee5 100644 --- a/osu.Game/Screens/Edit/Timing/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Timing/DifficultySection.cs @@ -24,7 +24,7 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier::0.##}x"; }); + ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier:0.##}x"; }); } protected override DifficultyControlPoint CreatePoint() From 9dd7f997d2c01836a43ad9630261e095e14a8c36 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 12:17:18 +0900 Subject: [PATCH 147/177] Reoder SampleSection to match others --- osu.Game/Screens/Edit/Timing/SampleSection.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs index 0477ad4e78..e665db0a4d 100644 --- a/osu.Game/Screens/Edit/Timing/SampleSection.cs +++ b/osu.Game/Screens/Edit/Timing/SampleSection.cs @@ -22,17 +22,6 @@ namespace osu.Game.Screens.Edit.Timing }); } - protected override SampleControlPoint CreatePoint() - { - var reference = Beatmap.Value.Beatmap.ControlPointInfo.SamplePointAt(SelectedGroup.Value.Time); - - return new SampleControlPoint - { - SampleBank = reference.SampleBank, - SampleVolume = reference.SampleVolume, - }; - } - protected override void LoadComplete() { base.LoadComplete(); @@ -43,5 +32,16 @@ namespace osu.Game.Screens.Edit.Timing volume.Text = $"Volume: {point.NewValue?.SampleVolume}%"; }); } + + protected override SampleControlPoint CreatePoint() + { + var reference = Beatmap.Value.Beatmap.ControlPointInfo.SamplePointAt(SelectedGroup.Value.Time); + + return new SampleControlPoint + { + SampleBank = reference.SampleBank, + SampleVolume = reference.SampleVolume, + }; + } } } From 7cd4cb8a935c797fd0d977198b99f7515bc225e8 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 12:32:12 +0900 Subject: [PATCH 148/177] Rename selectedPoints to selectedGroup --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 6ae01e056f..9317448262 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -20,7 +20,7 @@ namespace osu.Game.Screens.Edit.Timing public class TimingScreen : EditorScreenWithTimeline { [Cached] - private Bindable selectedPoints = new Bindable(); + private Bindable selectedGroup = new Bindable(); [Resolved] private IAdjustableClock clock { get; set; } @@ -47,7 +47,7 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - selectedPoints.BindValueChanged(selected => { clock.Seek(selected.NewValue.Time); }); + selectedGroup.BindValueChanged(selected => { clock.Seek(selected.NewValue.Time); }); } public class ControlPointList : CompositeDrawable From 322a1f0a860eff545c166426e026f14da48437f3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 12:45:35 +0900 Subject: [PATCH 149/177] Fix potential nullref --- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index 9317448262..d9da3ff92d 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -47,7 +47,11 @@ namespace osu.Game.Screens.Edit.Timing { base.LoadComplete(); - selectedGroup.BindValueChanged(selected => { clock.Seek(selected.NewValue.Time); }); + selectedGroup.BindValueChanged(selected => + { + if (selected.NewValue != null) + clock.Seek(selected.NewValue.Time); + }); } public class ControlPointList : CompositeDrawable From 4ce3450cfc6a003c3ff220d6f57cd566d49613f7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 14:08:52 +0900 Subject: [PATCH 150/177] Move button implementation to OsuButton --- .../Editor/TestSceneEditorComposeTimeline.cs | 4 +- osu.Game/Graphics/UserInterface/OsuButton.cs | 106 ++++++++++++++---- osu.Game/Overlays/Settings/SidebarButton.cs | 28 +---- osu.Game/Overlays/SettingsPanel.cs | 4 +- 4 files changed, 87 insertions(+), 55 deletions(-) diff --git a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs index b6b2b2f92d..6e5b3b93e9 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneEditorComposeTimeline.cs @@ -10,9 +10,9 @@ using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; -using osu.Framework.Graphics.UserInterface; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Graphics.UserInterface; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; using osuTK.Graphics; @@ -101,7 +101,7 @@ namespace osu.Game.Tests.Visual.Editor } } - private class StartStopButton : BasicButton + private class StartStopButton : OsuButton { private IAdjustableClock adjustableClock; private bool started; diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index 319c3dd0b4..2750e61f0d 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -1,10 +1,12 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System.Diagnostics; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.UserInterface; @@ -17,55 +19,106 @@ namespace osu.Game.Graphics.UserInterface /// /// A button with added default sound effects. /// - public class OsuButton : BasicButton + public class OsuButton : Button { - private Box hover; + public string Text + { + get => SpriteText?.Text; + set + { + if (SpriteText != null) + SpriteText.Text = value; + } + } + + private Color4? backgroundColour; + + public Color4 BackgroundColour + { + set + { + backgroundColour = value; + Background.FadeColour(value); + } + } + + protected override Container Content { get; } + + protected Box Hover; + protected Box Background; + protected SpriteText SpriteText; public OsuButton() { Height = 40; - Content.Masking = true; - Content.CornerRadius = 5; + AddInternal(Content = new Container + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Masking = true, + CornerRadius = 5, + RelativeSizeAxes = Axes.Both, + Children = new Drawable[] + { + Background = new Box + { + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + }, + Hover = new Box + { + Alpha = 0, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + RelativeSizeAxes = Axes.Both, + Colour = Color4.White.Opacity(.1f), + Blending = BlendingParameters.Additive, + Depth = float.MinValue + }, + SpriteText = CreateText(), + new HoverClickSounds(HoverSampleSet.Loud), + } + }); + + Enabled.BindValueChanged(enabledChanged, true); } [BackgroundDependencyLoader] private void load(OsuColour colours) { - BackgroundColour = colours.BlueDark; - - AddRange(new Drawable[] - { - hover = new Box - { - RelativeSizeAxes = Axes.Both, - Blending = BlendingParameters.Additive, - Colour = Color4.White.Opacity(0.1f), - Alpha = 0, - Depth = -1 - }, - new HoverClickSounds(HoverSampleSet.Loud), - }); + if (backgroundColour == null) + BackgroundColour = colours.BlueDark; Enabled.ValueChanged += enabledChanged; Enabled.TriggerChange(); } - private void enabledChanged(ValueChangedEvent e) + protected override bool OnClick(ClickEvent e) { - this.FadeColour(e.NewValue ? Color4.White : Color4.Gray, 200, Easing.OutQuint); + if (Enabled.Value) + { + Debug.Assert(backgroundColour != null); + Background.FlashColour(backgroundColour.Value, 200); + } + + return base.OnClick(e); } protected override bool OnHover(HoverEvent e) { - hover.FadeIn(200); - return true; + if (Enabled.Value) + Hover.FadeIn(200, Easing.OutQuint); + + return base.OnHover(e); } protected override void OnHoverLost(HoverLostEvent e) { - hover.FadeOut(200); base.OnHoverLost(e); + + Hover.FadeOut(300); } protected override bool OnMouseDown(MouseDownEvent e) @@ -80,12 +133,17 @@ namespace osu.Game.Graphics.UserInterface return base.OnMouseUp(e); } - protected override SpriteText CreateText() => new OsuSpriteText + protected virtual SpriteText CreateText() => new OsuSpriteText { Depth = -1, Origin = Anchor.Centre, Anchor = Anchor.Centre, Font = OsuFont.GetFont(weight: FontWeight.Bold) }; + + private void enabledChanged(ValueChangedEvent e) + { + this.FadeColour(e.NewValue ? Color4.White : Color4.Gray, 200, Easing.OutQuint); + } } } diff --git a/osu.Game/Overlays/Settings/SidebarButton.cs b/osu.Game/Overlays/Settings/SidebarButton.cs index cdb4dc463a..5930c89d29 100644 --- a/osu.Game/Overlays/Settings/SidebarButton.cs +++ b/osu.Game/Overlays/Settings/SidebarButton.cs @@ -1,7 +1,6 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. -using System; using osuTK; using osuTK.Graphics; using osu.Framework.Allocation; @@ -9,21 +8,18 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; -using osu.Framework.Graphics.UserInterface; -using osu.Framework.Input.Events; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; namespace osu.Game.Overlays.Settings { - public class SidebarButton : BasicButton + public class SidebarButton : OsuButton { private readonly SpriteIcon drawableIcon; private readonly SpriteText headerText; private readonly Box selectionIndicator; private readonly Container text; - public new Action Action; private SettingsSection section; @@ -62,9 +58,6 @@ namespace osu.Game.Overlays.Settings public SidebarButton() { - BackgroundColour = OsuColour.Gray(60); - Background.Alpha = 0; - Height = Sidebar.DEFAULT_WIDTH; RelativeSizeAxes = Axes.X; @@ -99,7 +92,6 @@ namespace osu.Game.Overlays.Settings Anchor = Anchor.CentreRight, Origin = Anchor.CentreRight, }, - new HoverClickSounds(HoverSampleSet.Loud), }); } @@ -108,23 +100,5 @@ namespace osu.Game.Overlays.Settings { selectionIndicator.Colour = colours.Yellow; } - - protected override bool OnClick(ClickEvent e) - { - Action?.Invoke(section); - return base.OnClick(e); - } - - protected override bool OnHover(HoverEvent e) - { - Background.FadeTo(0.4f, 200); - return base.OnHover(e); - } - - protected override void OnHoverLost(HoverLostEvent e) - { - Background.FadeTo(0, 200); - base.OnHoverLost(e); - } } } diff --git a/osu.Game/Overlays/SettingsPanel.cs b/osu.Game/Overlays/SettingsPanel.cs index d028664fe0..2948231c4b 100644 --- a/osu.Game/Overlays/SettingsPanel.cs +++ b/osu.Game/Overlays/SettingsPanel.cs @@ -123,9 +123,9 @@ namespace osu.Game.Overlays var button = new SidebarButton { Section = section, - Action = s => + Action = () => { - SectionsContainer.ScrollTo(s); + SectionsContainer.ScrollTo(section); Sidebar.State = ExpandedState.Contracted; }, }; From ebfb5d050d740e9df7ecd1d14d6ce716d57039c8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 14:36:43 +0900 Subject: [PATCH 151/177] Move section update code to abstract method to avoid incorrect BindValue usage --- osu.Game/Screens/Edit/Timing/DifficultySection.cs | 7 +++---- osu.Game/Screens/Edit/Timing/EffectSection.cs | 12 ++++-------- osu.Game/Screens/Edit/Timing/SampleSection.cs | 12 ++++-------- osu.Game/Screens/Edit/Timing/Section.cs | 4 ++++ osu.Game/Screens/Edit/Timing/TimingSection.cs | 12 ++++-------- 5 files changed, 19 insertions(+), 28 deletions(-) diff --git a/osu.Game/Screens/Edit/Timing/DifficultySection.cs b/osu.Game/Screens/Edit/Timing/DifficultySection.cs index cd33425ee5..05e45014eb 100644 --- a/osu.Game/Screens/Edit/Timing/DifficultySection.cs +++ b/osu.Game/Screens/Edit/Timing/DifficultySection.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; @@ -20,11 +21,9 @@ namespace osu.Game.Screens.Edit.Timing }); } - protected override void LoadComplete() + protected override void OnControlPointChanged(ValueChangedEvent point) { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => { multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier:0.##}x"; }); + multiplier.Text = $"Multiplier: {point.NewValue?.SpeedMultiplier:0.##}x"; } protected override DifficultyControlPoint CreatePoint() diff --git a/osu.Game/Screens/Edit/Timing/EffectSection.cs b/osu.Game/Screens/Edit/Timing/EffectSection.cs index bafdfd0b0d..2b37ffaaaf 100644 --- a/osu.Game/Screens/Edit/Timing/EffectSection.cs +++ b/osu.Game/Screens/Edit/Timing/EffectSection.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; @@ -22,15 +23,10 @@ namespace osu.Game.Screens.Edit.Timing }); } - protected override void LoadComplete() + protected override void OnControlPointChanged(ValueChangedEvent point) { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => - { - kiai.Text = $"Kiai: {(point.NewValue?.KiaiMode == true ? "on" : "off")}"; - omitBarLine.Text = $"Skip Bar Line: {(point.NewValue?.OmitFirstBarLine == true ? "on" : "off")}"; - }); + kiai.Text = $"Kiai: {(point.NewValue?.KiaiMode == true ? "on" : "off")}"; + omitBarLine.Text = $"Skip Bar Line: {(point.NewValue?.OmitFirstBarLine == true ? "on" : "off")}"; } protected override EffectControlPoint CreatePoint() diff --git a/osu.Game/Screens/Edit/Timing/SampleSection.cs b/osu.Game/Screens/Edit/Timing/SampleSection.cs index e665db0a4d..1bedb1ff42 100644 --- a/osu.Game/Screens/Edit/Timing/SampleSection.cs +++ b/osu.Game/Screens/Edit/Timing/SampleSection.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; @@ -22,15 +23,10 @@ namespace osu.Game.Screens.Edit.Timing }); } - protected override void LoadComplete() + protected override void OnControlPointChanged(ValueChangedEvent point) { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => - { - bank.Text = $"Bank: {point.NewValue?.SampleBank}"; - volume.Text = $"Volume: {point.NewValue?.SampleVolume}%"; - }); + bank.Text = $"Bank: {point.NewValue?.SampleBank}"; + volume.Text = $"Volume: {point.NewValue?.SampleVolume}%"; } protected override SampleControlPoint CreatePoint() diff --git a/osu.Game/Screens/Edit/Timing/Section.cs b/osu.Game/Screens/Edit/Timing/Section.cs index c6140ff497..ccf1582486 100644 --- a/osu.Game/Screens/Edit/Timing/Section.cs +++ b/osu.Game/Screens/Edit/Timing/Section.cs @@ -119,8 +119,12 @@ namespace osu.Game.Screens.Edit.Timing ControlPoint.Value = points.NewValue?.ControlPoints.OfType().FirstOrDefault(); checkbox.Current.Value = ControlPoint.Value != null; }, true); + + ControlPoint.BindValueChanged(OnControlPointChanged, true); } + protected abstract void OnControlPointChanged(ValueChangedEvent point); + protected abstract T CreatePoint(); } } diff --git a/osu.Game/Screens/Edit/Timing/TimingSection.cs b/osu.Game/Screens/Edit/Timing/TimingSection.cs index 8609da4c4d..15007ffdb3 100644 --- a/osu.Game/Screens/Edit/Timing/TimingSection.cs +++ b/osu.Game/Screens/Edit/Timing/TimingSection.cs @@ -2,6 +2,7 @@ // See the LICENCE file in the repository root for full licence text. using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Game.Beatmaps.ControlPoints; using osu.Game.Graphics.Sprites; @@ -22,15 +23,10 @@ namespace osu.Game.Screens.Edit.Timing }); } - protected override void LoadComplete() + protected override void OnControlPointChanged(ValueChangedEvent point) { - base.LoadComplete(); - - ControlPoint.BindValueChanged(point => - { - bpm.Text = $"BPM: {point.NewValue?.BPM:0.##}"; - timeSignature.Text = $"Signature: {point.NewValue?.TimeSignature}"; - }); + bpm.Text = $"BPM: {point.NewValue?.BPM:0.##}"; + timeSignature.Text = $"Signature: {point.NewValue?.TimeSignature}"; } protected override TimingControlPoint CreatePoint() From 5e416bd18d5dc77b458cc1d1d59db2372d1b6ec8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 14:47:31 +0900 Subject: [PATCH 152/177] Fix tournament client crashing if beatmap added with an ID of zero --- osu.Game.Tournament/TournamentGameBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index dbfa70704b..21552882ef 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -215,7 +215,7 @@ namespace osu.Game.Tournament foreach (var r in ladder.Rounds) foreach (var b in r.Beatmaps) - if (b.BeatmapInfo == null) + if (b.BeatmapInfo == null && b.ID > 0) { var req = new GetBeatmapRequest(new BeatmapInfo { OnlineBeatmapID = b.ID }); req.Perform(API); From 0b09fb293e0ad9bea8657a84e19dd135c1eb566d Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 15:09:08 +0900 Subject: [PATCH 153/177] Fix background being coloured --- osu.Game/Overlays/Settings/SidebarButton.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game/Overlays/Settings/SidebarButton.cs b/osu.Game/Overlays/Settings/SidebarButton.cs index 5930c89d29..68836bc6b3 100644 --- a/osu.Game/Overlays/Settings/SidebarButton.cs +++ b/osu.Game/Overlays/Settings/SidebarButton.cs @@ -61,6 +61,8 @@ namespace osu.Game.Overlays.Settings Height = Sidebar.DEFAULT_WIDTH; RelativeSizeAxes = Axes.X; + BackgroundColour = Color4.Black; + AddRange(new Drawable[] { text = new Container From 020b08b4500c811702380b2c3286427a36d2e3b3 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 14:55:05 +0900 Subject: [PATCH 154/177] Initial implementation of limited distance snap --- .../TestSceneOsuDistanceSnapGrid.cs | 2 +- .../Edit/OsuDistanceSnapGrid.cs | 4 +-- .../Edit/OsuHitObjectComposer.cs | 33 ++++++++++++------- .../Editor/TestSceneDistanceSnapGrid.cs | 2 +- .../Components/CircularDistanceSnapGrid.cs | 12 ++++--- .../Compose/Components/DistanceSnapGrid.cs | 21 +++++++++++- 6 files changed, 52 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs index a9a6097182..aba1d8fa1d 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs @@ -153,7 +153,7 @@ namespace osu.Game.Rulesets.Osu.Tests public new float DistanceSpacing => base.DistanceSpacing; public TestOsuDistanceSnapGrid(OsuHitObject hitObject) - : base(hitObject) + : base(hitObject, null) { } } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs index 79cd51a7f4..9b00204d51 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuDistanceSnapGrid.cs @@ -8,8 +8,8 @@ namespace osu.Game.Rulesets.Osu.Edit { public class OsuDistanceSnapGrid : CircularDistanceSnapGrid { - public OsuDistanceSnapGrid(OsuHitObject hitObject) - : base(hitObject, hitObject.StackedEndPosition) + public OsuDistanceSnapGrid(OsuHitObject hitObject, OsuHitObject nextHitObject) + : base(hitObject, nextHitObject, hitObject.StackedEndPosition) { Masking = true; } diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index fcf2772219..a021f70598 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using System; using System.Collections.Generic; using System.Linq; using osu.Game.Beatmaps; @@ -60,25 +61,33 @@ namespace osu.Game.Rulesets.Osu.Edit var objects = selectedHitObjects.ToList(); if (objects.Count == 0) + return createGrid(h => h.StartTime <= EditorClock.CurrentTime); + + double minTime = objects.Min(h => h.StartTime); + return createGrid(h => h.StartTime < minTime); + } + + private OsuDistanceSnapGrid createGrid(Func hitObjectSelector) + { + int lastIndex = -1; + + for (int i = 0; i < EditorBeatmap.HitObjects.Count; i++) { - var lastObject = EditorBeatmap.HitObjects.LastOrDefault(h => h.StartTime <= EditorClock.CurrentTime); + HitObject hitObject = EditorBeatmap.HitObjects[i]; - if (lastObject == null) - return null; + if (!hitObjectSelector(hitObject)) + break; - return new OsuDistanceSnapGrid(lastObject); + lastIndex = i; } - else - { - double minTime = objects.Min(h => h.StartTime); - var lastObject = EditorBeatmap.HitObjects.LastOrDefault(h => h.StartTime < minTime); + if (lastIndex == -1) + return null; - if (lastObject == null) - return null; + OsuHitObject lastObject = EditorBeatmap.HitObjects[lastIndex]; + OsuHitObject nextObject = lastIndex == EditorBeatmap.HitObjects.Count - 1 ? null : EditorBeatmap.HitObjects[lastIndex + 1]; - return new OsuDistanceSnapGrid(lastObject); - } + return new OsuDistanceSnapGrid(lastObject, nextObject); } } } diff --git a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs index b8c31d5dbb..0681dff528 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs @@ -62,7 +62,7 @@ namespace osu.Game.Tests.Visual.Editor public new float DistanceSpacing => base.DistanceSpacing; public TestDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) - : base(hitObject, centrePosition) + : base(hitObject, null, centrePosition) { } diff --git a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs index f45115e1e4..0f2bae6305 100644 --- a/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/CircularDistanceSnapGrid.cs @@ -12,8 +12,8 @@ namespace osu.Game.Screens.Edit.Compose.Components { public abstract class CircularDistanceSnapGrid : DistanceSnapGrid { - protected CircularDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) - : base(hitObject, centrePosition) + protected CircularDistanceSnapGrid(HitObject hitObject, HitObject nextHitObject, Vector2 centrePosition) + : base(hitObject, nextHitObject, centrePosition) { } @@ -45,7 +45,7 @@ namespace osu.Game.Screens.Edit.Compose.Components float dx = Math.Max(centrePosition.X, DrawWidth - centrePosition.X); float dy = Math.Max(centrePosition.Y, DrawHeight - centrePosition.Y); float maxDistance = new Vector2(dx, dy).Length; - int requiredCircles = (int)(maxDistance / DistanceSpacing); + int requiredCircles = Math.Min(MaxIntervals, (int)(maxDistance / DistanceSpacing)); for (int i = 0; i < requiredCircles; i++) { @@ -65,15 +65,17 @@ namespace osu.Game.Screens.Edit.Compose.Components public override (Vector2 position, double time) GetSnappedPosition(Vector2 position) { - Vector2 direction = position - CentrePosition; + if (MaxIntervals == 0) + return (CentrePosition, StartTime); + Vector2 direction = position - CentrePosition; if (direction == Vector2.Zero) direction = new Vector2(0.001f, 0.001f); float distance = direction.Length; float radius = DistanceSpacing; - int radialCount = Math.Max(1, (int)Math.Round(distance / radius)); + int radialCount = MathHelper.Clamp((int)Math.Round(distance / radius), 1, MaxIntervals); Vector2 normalisedDirection = direction * new Vector2(1f / distance); Vector2 snappedPosition = CentrePosition + normalisedDirection * radialCount * radius; diff --git a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs index 193474093f..475b6e7274 100644 --- a/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs +++ b/osu.Game/Screens/Edit/Compose/Components/DistanceSnapGrid.cs @@ -1,6 +1,7 @@ // Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. // See the LICENCE file in the repository root for full licence text. +using JetBrains.Annotations; using osu.Framework.Allocation; using osu.Framework.Caching; using osu.Framework.Graphics; @@ -29,6 +30,11 @@ namespace osu.Game.Screens.Edit.Compose.Components /// protected double StartTime { get; private set; } + /// + /// The maximum number of distance snapping intervals allowed. + /// + protected int MaxIntervals { get; private set; } + /// /// The position which the grid is centred on. /// The first beat snapping tick is located at + in the desired direction. @@ -49,12 +55,15 @@ namespace osu.Game.Screens.Edit.Compose.Components private readonly Cached gridCache = new Cached(); private readonly HitObject hitObject; + private readonly HitObject nextHitObject; - protected DistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) + protected DistanceSnapGrid(HitObject hitObject, [CanBeNull] HitObject nextHitObject, Vector2 centrePosition) { this.hitObject = hitObject; + this.nextHitObject = nextHitObject; CentrePosition = centrePosition; + RelativeSizeAxes = Axes.Both; } @@ -74,6 +83,16 @@ namespace osu.Game.Screens.Edit.Compose.Components private void updateSpacing() { DistanceSpacing = SnapProvider.GetBeatSnapDistanceAt(StartTime); + + if (nextHitObject == null) + MaxIntervals = int.MaxValue; + else + { + // +1 is added since a snapped hitobject may have its start time slightly less than the snapped time due to floating point errors + double maxDuration = nextHitObject.StartTime - StartTime + 1; + MaxIntervals = (int)(maxDuration / SnapProvider.DistanceToDuration(StartTime, DistanceSpacing)); + } + gridCache.Invalidate(); } From c1a6cb1defaa7fc0008f1de0db821d3faf253fdb Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 15:58:47 +0900 Subject: [PATCH 155/177] Fix audio preview muting game audio indefinitely when beatmap panel is off-screen --- osu.Game/Audio/PreviewTrack.cs | 5 ++++- osu.Game/Audio/PreviewTrackManager.cs | 8 ++++---- 2 files changed, 8 insertions(+), 5 deletions(-) diff --git a/osu.Game/Audio/PreviewTrack.cs b/osu.Game/Audio/PreviewTrack.cs index 22ce7d4711..937ad7e45a 100644 --- a/osu.Game/Audio/PreviewTrack.cs +++ b/osu.Game/Audio/PreviewTrack.cs @@ -13,11 +13,13 @@ namespace osu.Game.Audio { /// /// Invoked when this has stopped playing. + /// Not invoked in a thread-safe context. /// public event Action Stopped; /// /// Invoked when this has started playing. + /// Not invoked in a thread-safe context. /// public event Action Started; @@ -29,7 +31,7 @@ namespace osu.Game.Audio { track = GetTrack(); if (track != null) - track.Completed += () => Schedule(Stop); + track.Completed += Stop; } /// @@ -93,6 +95,7 @@ namespace osu.Game.Audio hasStarted = false; track.Stop(); + Stopped?.Invoke(); } diff --git a/osu.Game/Audio/PreviewTrackManager.cs b/osu.Game/Audio/PreviewTrackManager.cs index e12c46ef16..fad2b5a5e8 100644 --- a/osu.Game/Audio/PreviewTrackManager.cs +++ b/osu.Game/Audio/PreviewTrackManager.cs @@ -46,18 +46,18 @@ namespace osu.Game.Audio { var track = CreatePreviewTrack(beatmapSetInfo, trackStore); - track.Started += () => + track.Started += () => Schedule(() => { current?.Stop(); current = track; audio.Tracks.AddAdjustment(AdjustableProperty.Volume, muteBindable); - }; + }); - track.Stopped += () => + track.Stopped += () => Schedule(() => { current = null; audio.Tracks.RemoveAdjustment(AdjustableProperty.Volume, muteBindable); - }; + }); return track; } From 2588534eda26d2a00f6cf070f0d150a9ee556eb1 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 16:04:20 +0900 Subject: [PATCH 156/177] Skin entire selection, add xmldocs --- .../Edit/OsuHitObjectComposer.cs | 29 ++++++++++++------- 1 file changed, 18 insertions(+), 11 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs index a021f70598..812afaaa24 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuHitObjectComposer.cs @@ -64,30 +64,37 @@ namespace osu.Game.Rulesets.Osu.Edit return createGrid(h => h.StartTime <= EditorClock.CurrentTime); double minTime = objects.Min(h => h.StartTime); - return createGrid(h => h.StartTime < minTime); + return createGrid(h => h.StartTime < minTime, objects.Count + 1); } - private OsuDistanceSnapGrid createGrid(Func hitObjectSelector) + /// + /// Creates a grid from the last matching a predicate to a target . + /// + /// A predicate that matches s where the grid can start from. + /// Only the last matching the predicate is used. + /// An offset from the selected via at which the grid should stop. + /// The from a selected to a target . + private OsuDistanceSnapGrid createGrid(Func sourceSelector, int targetOffset = 1) { - int lastIndex = -1; + if (targetOffset < 1) throw new ArgumentOutOfRangeException(nameof(targetOffset)); + + int sourceIndex = -1; for (int i = 0; i < EditorBeatmap.HitObjects.Count; i++) { - HitObject hitObject = EditorBeatmap.HitObjects[i]; - - if (!hitObjectSelector(hitObject)) + if (!sourceSelector(EditorBeatmap.HitObjects[i])) break; - lastIndex = i; + sourceIndex = i; } - if (lastIndex == -1) + if (sourceIndex == -1) return null; - OsuHitObject lastObject = EditorBeatmap.HitObjects[lastIndex]; - OsuHitObject nextObject = lastIndex == EditorBeatmap.HitObjects.Count - 1 ? null : EditorBeatmap.HitObjects[lastIndex + 1]; + OsuHitObject sourceObject = EditorBeatmap.HitObjects[sourceIndex]; + OsuHitObject targetObject = sourceIndex + targetOffset < EditorBeatmap.HitObjects.Count ? EditorBeatmap.HitObjects[sourceIndex + targetOffset] : null; - return new OsuDistanceSnapGrid(lastObject, nextObject); + return new OsuDistanceSnapGrid(sourceObject, targetObject); } } } From d985d048574ef01800fd3973d65583e273d4c45a Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Wed, 6 Nov 2019 14:07:05 +0700 Subject: [PATCH 157/177] Add background colour to music player ProgressBar --- osu.Game/Overlays/NowPlayingOverlay.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 6b79f2af07..8c7e717a82 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -174,6 +174,7 @@ namespace osu.Game.Overlays Anchor = Anchor.BottomCentre, Height = progress_height, FillColour = colours.Yellow, + BackgroundColour = colours.YellowDarker.Opacity(0.5f), OnSeek = musicController.SeekTo } }, From 02c21a1379e24a7ec2bb4c2b47c2c6efbcd0a103 Mon Sep 17 00:00:00 2001 From: Ganendra Afrasya Date: Wed, 6 Nov 2019 14:11:47 +0700 Subject: [PATCH 158/177] Make progress bar hoverable --- osu.Game/Overlays/NowPlayingOverlay.cs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/osu.Game/Overlays/NowPlayingOverlay.cs b/osu.Game/Overlays/NowPlayingOverlay.cs index 8c7e717a82..4f73cbfacd 100644 --- a/osu.Game/Overlays/NowPlayingOverlay.cs +++ b/osu.Game/Overlays/NowPlayingOverlay.cs @@ -168,11 +168,11 @@ namespace osu.Game.Overlays }, } }, - progressBar = new ProgressBar + progressBar = new HoverableProgressBar { Origin = Anchor.BottomCentre, Anchor = Anchor.BottomCentre, - Height = progress_height, + Height = progress_height / 2, FillColour = colours.Yellow, BackgroundColour = colours.YellowDarker.Opacity(0.5f), OnSeek = musicController.SeekTo @@ -402,5 +402,20 @@ namespace osu.Game.Overlays return base.OnDragEnd(e); } } + + private class HoverableProgressBar : ProgressBar + { + protected override bool OnHover(HoverEvent e) + { + this.ResizeHeightTo(progress_height, 500, Easing.OutQuint); + return base.OnHover(e); + } + + protected override void OnHoverLost(HoverLostEvent e) + { + this.ResizeHeightTo(progress_height / 2, 500, Easing.OutQuint); + base.OnHoverLost(e); + } + } } } From b83ceab1c1b2ba594efa02d166f364a32142e4ac Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 16:20:13 +0900 Subject: [PATCH 159/177] Add tests --- .../TestSceneOsuDistanceSnapGrid.cs | 47 ++++++++++++++----- .../Editor/TestSceneDistanceSnapGrid.cs | 39 +++++++++++---- 2 files changed, 64 insertions(+), 22 deletions(-) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs index aba1d8fa1d..eff4d919b0 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneOsuDistanceSnapGrid.cs @@ -42,11 +42,19 @@ namespace osu.Game.Rulesets.Osu.Tests [Cached(typeof(IDistanceSnapProvider))] private readonly SnapProvider snapProvider = new SnapProvider(); - private readonly TestOsuDistanceSnapGrid grid; + private TestOsuDistanceSnapGrid grid; public TestSceneOsuDistanceSnapGrid() { editorBeatmap = new EditorBeatmap(new OsuBeatmap()); + } + + [SetUp] + public void Setup() => Schedule(() => + { + editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1; + editorBeatmap.ControlPointInfo.Clear(); + editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); Children = new Drawable[] { @@ -58,14 +66,6 @@ namespace osu.Game.Rulesets.Osu.Tests grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }), new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position } }; - } - - [SetUp] - public void Setup() => Schedule(() => - { - editorBeatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier = 1; - editorBeatmap.ControlPointInfo.Clear(); - editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); }); [TestCase(1)] @@ -102,6 +102,27 @@ namespace osu.Game.Rulesets.Osu.Tests assertSnappedDistance((float)beat_length * 2); } + [Test] + public void TestLimitedDistance() + { + AddStep("create limited grid", () => + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.SlateGray + }, + grid = new TestOsuDistanceSnapGrid(new HitCircle { Position = grid_position }, new HitCircle { StartTime = 200 }), + new SnappingCursorContainer { GetSnapPosition = v => grid.GetSnappedPosition(grid.ToLocalSpace(v)).position } + }; + }); + + AddStep("move mouse outside grid", () => InputManager.MoveMouseTo(grid.ToScreenSpace(grid_position + new Vector2((float)beat_length, 0) * 3f))); + assertSnappedDistance((float)beat_length * 2); + } + private void assertSnappedDistance(float expectedDistance) => AddAssert($"snap distance = {expectedDistance}", () => { Vector2 snappedPosition = grid.GetSnappedPosition(grid.ToLocalSpace(InputManager.CurrentState.Mouse.Position)).position; @@ -152,8 +173,8 @@ namespace osu.Game.Rulesets.Osu.Tests { public new float DistanceSpacing => base.DistanceSpacing; - public TestOsuDistanceSnapGrid(OsuHitObject hitObject) - : base(hitObject, null) + public TestOsuDistanceSnapGrid(OsuHitObject hitObject, OsuHitObject nextHitObject = null) + : base(hitObject, nextHitObject) { } } @@ -164,9 +185,9 @@ namespace osu.Game.Rulesets.Osu.Tests public float GetBeatSnapDistanceAt(double referenceTime) => (float)beat_length; - public float DurationToDistance(double referenceTime, double duration) => 0; + public float DurationToDistance(double referenceTime, double duration) => (float)duration; - public double DistanceToDuration(double referenceTime, float distance) => 0; + public double DistanceToDuration(double referenceTime, float distance) => distance; public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0; diff --git a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs index 0681dff528..e4c987923c 100644 --- a/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs +++ b/osu.Game.Tests/Visual/Editor/TestSceneDistanceSnapGrid.cs @@ -32,7 +32,11 @@ namespace osu.Game.Tests.Visual.Editor { editorBeatmap = new EditorBeatmap(new OsuBeatmap()); editorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = beat_length }); + } + [SetUp] + public void Setup() => Schedule(() => + { Children = new Drawable[] { new Box @@ -42,7 +46,7 @@ namespace osu.Game.Tests.Visual.Editor }, new TestDistanceSnapGrid(new HitObject(), grid_position) }; - } + }); [TestCase(1)] [TestCase(2)] @@ -57,12 +61,29 @@ namespace osu.Game.Tests.Visual.Editor AddStep($"set beat divisor = {divisor}", () => BeatDivisor.Value = divisor); } + [Test] + public void TestLimitedDistance() + { + AddStep("create limited grid", () => + { + Children = new Drawable[] + { + new Box + { + RelativeSizeAxes = Axes.Both, + Colour = Color4.SlateGray + }, + new TestDistanceSnapGrid(new HitObject(), grid_position, new HitObject { StartTime = 100 }) + }; + }); + } + private class TestDistanceSnapGrid : DistanceSnapGrid { public new float DistanceSpacing => base.DistanceSpacing; - public TestDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition) - : base(hitObject, null, centrePosition) + public TestDistanceSnapGrid(HitObject hitObject, Vector2 centrePosition, HitObject nextHitObject = null) + : base(hitObject, nextHitObject, centrePosition) { } @@ -77,7 +98,7 @@ namespace osu.Game.Tests.Visual.Editor int beatIndex = 0; - for (float s = centrePosition.X + DistanceSpacing; s <= DrawWidth; s += DistanceSpacing, beatIndex++) + for (float s = centrePosition.X + DistanceSpacing; s <= DrawWidth && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++) { AddInternal(new Circle { @@ -90,7 +111,7 @@ namespace osu.Game.Tests.Visual.Editor beatIndex = 0; - for (float s = centrePosition.X - DistanceSpacing; s >= 0; s -= DistanceSpacing, beatIndex++) + for (float s = centrePosition.X - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++) { AddInternal(new Circle { @@ -103,7 +124,7 @@ namespace osu.Game.Tests.Visual.Editor beatIndex = 0; - for (float s = centrePosition.Y + DistanceSpacing; s <= DrawHeight; s += DistanceSpacing, beatIndex++) + for (float s = centrePosition.Y + DistanceSpacing; s <= DrawHeight && beatIndex < MaxIntervals; s += DistanceSpacing, beatIndex++) { AddInternal(new Circle { @@ -116,7 +137,7 @@ namespace osu.Game.Tests.Visual.Editor beatIndex = 0; - for (float s = centrePosition.Y - DistanceSpacing; s >= 0; s -= DistanceSpacing, beatIndex++) + for (float s = centrePosition.Y - DistanceSpacing; s >= 0 && beatIndex < MaxIntervals; s -= DistanceSpacing, beatIndex++) { AddInternal(new Circle { @@ -138,9 +159,9 @@ namespace osu.Game.Tests.Visual.Editor public float GetBeatSnapDistanceAt(double referenceTime) => 10; - public float DurationToDistance(double referenceTime, double duration) => 0; + public float DurationToDistance(double referenceTime, double duration) => (float)duration; - public double DistanceToDuration(double referenceTime, float distance) => 0; + public double DistanceToDuration(double referenceTime, float distance) => distance; public double GetSnappedDurationFromDistance(double referenceTime, float distance) => 0; From ee544e174a3971716fcd9a6ea0f284c2cb213da3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 16:33:42 +0900 Subject: [PATCH 160/177] Group -> Connection --- .../TestSceneFollowPoints.cs | 4 +- ...PointGroup.cs => FollowPointConnection.cs} | 6 +- .../Connections/FollowPointRenderer.cs | 76 +++++++++---------- 3 files changed, 43 insertions(+), 43 deletions(-) rename osu.Game.Rulesets.Osu/Objects/Drawables/Connections/{FollowPointGroup.cs => FollowPointConnection.cs} (95%) diff --git a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs index 79a1bb579f..94ca2d4cd1 100644 --- a/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs +++ b/osu.Game.Rulesets.Osu.Tests/TestSceneFollowPoints.cs @@ -188,7 +188,7 @@ namespace osu.Game.Rulesets.Osu.Tests private void assertGroups() { - AddAssert("has correct group count", () => followPointRenderer.Groups.Count == hitObjectContainer.Count); + AddAssert("has correct group count", () => followPointRenderer.Connections.Count == hitObjectContainer.Count); AddAssert("group endpoints are correct", () => { for (int i = 0; i < hitObjectContainer.Count; i++) @@ -209,7 +209,7 @@ namespace osu.Game.Rulesets.Osu.Tests private DrawableOsuHitObject getObject(int index) => hitObjectContainer[index]; - private FollowPointGroup getGroup(int index) => followPointRenderer.Groups[index]; + private FollowPointConnection getGroup(int index) => followPointRenderer.Connections[index]; private class TestHitObjectContainer : Container { diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs similarity index 95% rename from osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs rename to osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs index 168d2b8532..1e032eb977 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointGroup.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointConnection.cs @@ -14,7 +14,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections /// /// Visualises the s between two s. /// - public class FollowPointGroup : CompositeDrawable + public class FollowPointConnection : CompositeDrawable { // Todo: These shouldn't be constants private const int spacing = 32; @@ -32,10 +32,10 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections public readonly DrawableOsuHitObject Start; /// - /// Creates a new . + /// Creates a new . /// /// The which s will exit from. - public FollowPointGroup([NotNull] DrawableOsuHitObject start) + public FollowPointConnection([NotNull] DrawableOsuHitObject start) { Start = start; diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index afd86e004d..00ef0ba0d0 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -10,16 +10,16 @@ using osu.Framework.Graphics.Containers; namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections { /// - /// Visualises groups of s. + /// Visualises connections between s. /// public class FollowPointRenderer : CompositeDrawable { /// - /// All the s contained by this . + /// All the s contained by this . /// - internal IReadOnlyList Groups => groups; + internal IReadOnlyList Connections => connections; - private readonly List groups = new List(); + private readonly List connections = new List(); public override bool RemoveCompletedTransforms => false; @@ -29,84 +29,84 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections /// /// The to add s for. public void AddFollowPoints(DrawableOsuHitObject hitObject) - => addGroup(new FollowPointGroup(hitObject).With(g => g.StartTime.BindValueChanged(_ => onStartTimeChanged(g)))); + => addConnection(new FollowPointConnection(hitObject).With(g => g.StartTime.BindValueChanged(_ => onStartTimeChanged(g)))); /// /// Removes the s around a . /// This includes s leading into , and s exiting . /// /// The to remove s for. - public void RemoveFollowPoints(DrawableOsuHitObject hitObject) => removeGroup(groups.Single(g => g.Start == hitObject)); + public void RemoveFollowPoints(DrawableOsuHitObject hitObject) => removeGroup(connections.Single(g => g.Start == hitObject)); /// - /// Adds a to this . + /// Adds a to this . /// - /// The to add. - /// The index of in . - private int addGroup(FollowPointGroup group) + /// The to add. + /// The index of in . + private int addConnection(FollowPointConnection connection) { - AddInternal(group); + AddInternal(connection); - // Groups are sorted by their start time when added such that the index can be used to post-process other surrounding groups - int index = groups.AddInPlace(group, Comparer.Create((g1, g2) => g1.StartTime.Value.CompareTo(g2.StartTime.Value))); + // Groups are sorted by their start time when added such that the index can be used to post-process other surrounding connections + int index = connections.AddInPlace(connection, Comparer.Create((g1, g2) => g1.StartTime.Value.CompareTo(g2.StartTime.Value))); - if (index < groups.Count - 1) + if (index < connections.Count - 1) { - // Update the group's end point to the next group's start point + // Update the connection's end point to the next connection's start point // h1 -> -> -> h2 - // group nextGroup + // connection nextGroup - FollowPointGroup nextGroup = groups[index + 1]; - group.End = nextGroup.Start; + FollowPointConnection nextConnection = connections[index + 1]; + connection.End = nextConnection.Start; } else { // The end point may be non-null during re-ordering - group.End = null; + connection.End = null; } if (index > 0) { - // Update the previous group's end point to the current group's start point + // Update the previous connection's end point to the current connection's start point // h1 -> -> -> h2 - // prevGroup group + // prevGroup connection - FollowPointGroup previousGroup = groups[index - 1]; - previousGroup.End = group.Start; + FollowPointConnection previousConnection = connections[index - 1]; + previousConnection.End = connection.Start; } return index; } /// - /// Removes a from this . + /// Removes a from this . /// - /// The to remove. - /// Whether was removed. - private bool removeGroup(FollowPointGroup group) + /// The to remove. + /// Whether was removed. + private bool removeGroup(FollowPointConnection connection) { - RemoveInternal(group); + RemoveInternal(connection); - int index = groups.IndexOf(group); + int index = connections.IndexOf(connection); if (index > 0) { - // Update the previous group's end point to the next group's start point + // Update the previous connection's end point to the next connection's start point // h1 -> -> -> h2 -> -> -> h3 - // prevGroup group nextGroup - // The current group's end point is used since there may not be a next group - FollowPointGroup previousGroup = groups[index - 1]; - previousGroup.End = group.End; + // prevGroup connection nextGroup + // The current connection's end point is used since there may not be a next connection + FollowPointConnection previousConnection = connections[index - 1]; + previousConnection.End = connection.End; } - return groups.Remove(group); + return connections.Remove(connection); } - private void onStartTimeChanged(FollowPointGroup group) + private void onStartTimeChanged(FollowPointConnection connection) { // Naive but can be improved if performance becomes an issue - removeGroup(group); - addGroup(group); + removeGroup(connection); + addConnection(connection); } } } From 7b5b3ff15c21d378d607b2e4cccafe2a8f366bc3 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 16:36:12 +0900 Subject: [PATCH 161/177] Remove unused returns --- .../Objects/Drawables/Connections/FollowPointRenderer.cs | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs index 00ef0ba0d0..be192080f9 100644 --- a/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs +++ b/osu.Game.Rulesets.Osu/Objects/Drawables/Connections/FollowPointRenderer.cs @@ -43,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections /// /// The to add. /// The index of in . - private int addConnection(FollowPointConnection connection) + private void addConnection(FollowPointConnection connection) { AddInternal(connection); @@ -74,8 +74,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections FollowPointConnection previousConnection = connections[index - 1]; previousConnection.End = connection.Start; } - - return index; } /// @@ -83,7 +81,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections /// /// The to remove. /// Whether was removed. - private bool removeGroup(FollowPointConnection connection) + private void removeGroup(FollowPointConnection connection) { RemoveInternal(connection); @@ -99,7 +97,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Connections previousConnection.End = connection.End; } - return connections.Remove(connection); + connections.Remove(connection); } private void onStartTimeChanged(FollowPointConnection connection) From 2c1bfd62efaba5e37651de1ea97911674c1c5539 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 17:27:41 +0900 Subject: [PATCH 162/177] Disallow selections to be moved outside of the playfield --- .../Edit/ManiaSelectionHandler.cs | 4 +-- .../Edit/OsuSelectionHandler.cs | 25 +++++++++++++++++-- .../Compose/Components/BlueprintContainer.cs | 3 ++- .../Compose/Components/SelectionHandler.cs | 5 ++-- 4 files changed, 29 insertions(+), 8 deletions(-) diff --git a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs index 732231b0d9..9cdf045b5b 100644 --- a/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs +++ b/osu.Game.Rulesets.Mania/Edit/ManiaSelectionHandler.cs @@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Mania.Edit editorClock = clock; } - public override void HandleMovement(MoveSelectionEvent moveEvent) + public override bool HandleMovement(MoveSelectionEvent moveEvent) { var maniaBlueprint = (ManiaSelectionBlueprint)moveEvent.Blueprint; int lastColumn = maniaBlueprint.DrawableObject.HitObject.Column; @@ -38,7 +38,7 @@ namespace osu.Game.Rulesets.Mania.Edit performDragMovement(moveEvent); performColumnMovement(lastColumn, moveEvent); - base.HandleMovement(moveEvent); + return true; } /// diff --git a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs index 472267eb66..9418565907 100644 --- a/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs +++ b/osu.Game.Rulesets.Osu/Edit/OsuSelectionHandler.cs @@ -4,13 +4,34 @@ using System.Linq; using osu.Game.Rulesets.Osu.Objects; using osu.Game.Screens.Edit.Compose.Components; +using osuTK; namespace osu.Game.Rulesets.Osu.Edit { public class OsuSelectionHandler : SelectionHandler { - public override void HandleMovement(MoveSelectionEvent moveEvent) + public override bool HandleMovement(MoveSelectionEvent moveEvent) { + Vector2 minPosition = new Vector2(float.MaxValue, float.MaxValue); + Vector2 maxPosition = new Vector2(float.MinValue, float.MinValue); + + // Go through all hitobjects to make sure they would remain in the bounds of the editor after movement, before any movement is attempted + foreach (var h in SelectedHitObjects.OfType()) + { + if (h is Spinner) + { + // Spinners don't support position adjustments + continue; + } + + // Stacking is not considered + minPosition = Vector2.ComponentMin(minPosition, Vector2.ComponentMin(h.EndPosition + moveEvent.InstantDelta, h.Position + moveEvent.InstantDelta)); + maxPosition = Vector2.ComponentMax(maxPosition, Vector2.ComponentMax(h.EndPosition + moveEvent.InstantDelta, h.Position + moveEvent.InstantDelta)); + } + + if (minPosition.X < 0 || minPosition.Y < 0 || maxPosition.X > DrawWidth || maxPosition.Y > DrawHeight) + return false; + foreach (var h in SelectedHitObjects.OfType()) { if (h is Spinner) @@ -22,7 +43,7 @@ namespace osu.Game.Rulesets.Osu.Edit h.Position += moveEvent.InstantDelta; } - base.HandleMovement(moveEvent); + return true; } } } diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 288c712bde..c4d8176c7a 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -367,7 +367,8 @@ namespace osu.Game.Screens.Edit.Compose.Components (Vector2 snappedPosition, double snappedTime) = composer.GetSnappedPosition(ToLocalSpace(movePosition), draggedObject.StartTime); // Move the hitobjects - selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, startPosition, ToScreenSpace(snappedPosition))); + if (!selectionHandler.HandleMovement(new MoveSelectionEvent(movementBlueprint, startPosition, ToScreenSpace(snappedPosition)))) + return true; // Apply the start time at the newly snapped-to position double offset = snappedTime - draggedObject.StartTime; diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index 1722476e53..44bf22cfe1 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -68,9 +68,8 @@ namespace osu.Game.Screens.Edit.Compose.Components /// Handles the selected s being moved. /// /// The move event. - public virtual void HandleMovement(MoveSelectionEvent moveEvent) - { - } + /// Whether any s were moved. + public virtual bool HandleMovement(MoveSelectionEvent moveEvent) => false; public bool OnPressed(PlatformAction action) { From 3680e7c7047853f936075675855a2410b93b3721 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Wed, 6 Nov 2019 18:15:57 +0900 Subject: [PATCH 163/177] Seek editor when hit objects are double clicked --- .../Edit/Compose/Components/BlueprintContainer.cs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs index 288c712bde..1ce7863d60 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BlueprintContainer.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Input; using osu.Framework.Input.Events; +using osu.Framework.Timing; using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Edit.Tools; using osu.Game.Rulesets.Objects; @@ -30,6 +31,9 @@ namespace osu.Game.Screens.Edit.Compose.Components private SelectionHandler selectionHandler; private InputManager inputManager; + [Resolved] + private IAdjustableClock adjustableClock { get; set; } + [Resolved] private HitObjectComposer composer { get; set; } @@ -106,6 +110,17 @@ namespace osu.Game.Screens.Edit.Compose.Components return true; } + protected override bool OnDoubleClick(DoubleClickEvent e) + { + SelectionBlueprint clickedBlueprint = selectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered); + + if (clickedBlueprint == null) + return false; + + adjustableClock?.Seek(clickedBlueprint.DrawableObject.HitObject.StartTime); + return true; + } + protected override bool OnMouseUp(MouseUpEvent e) { // Special case for when a drag happened instead of a click From 31f2d6a842e380077b4866e948b8b50dfce864e9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 6 Nov 2019 19:06:24 +0900 Subject: [PATCH 164/177] Fix regressed test --- .../Components/TestScenePreviewTrackManager.cs | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs index df6740421b..f3f6444149 100644 --- a/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs +++ b/osu.Game.Tests/Visual/Components/TestScenePreviewTrackManager.cs @@ -34,6 +34,7 @@ namespace osu.Game.Tests.Visual.Components PreviewTrack track = null; AddStep("get track", () => track = getOwnedTrack()); + AddUntilStep("wait loaded", () => track.IsLoaded); AddStep("start", () => track.Start()); AddAssert("started", () => track.IsRunning); AddStep("stop", () => track.Stop()); @@ -52,6 +53,8 @@ namespace osu.Game.Tests.Visual.Components track2 = getOwnedTrack(); }); + AddUntilStep("wait loaded", () => track1.IsLoaded && track2.IsLoaded); + AddStep("start track 1", () => track1.Start()); AddStep("start track 2", () => track2.Start()); AddAssert("track 1 stopped", () => !track1.IsRunning); @@ -64,6 +67,7 @@ namespace osu.Game.Tests.Visual.Components PreviewTrack track = null; AddStep("get track", () => track = getOwnedTrack()); + AddUntilStep("wait loaded", () => track.IsLoaded); AddStep("start", () => track.Start()); AddStep("stop by owner", () => trackManager.StopAnyPlaying(this)); AddAssert("stopped", () => !track.IsRunning); @@ -76,6 +80,7 @@ namespace osu.Game.Tests.Visual.Components PreviewTrack track = null; AddStep("get track", () => Add(owner = new TestTrackOwner(track = getTrack()))); + AddUntilStep("wait loaded", () => track.IsLoaded); AddStep("start", () => track.Start()); AddStep("attempt stop", () => trackManager.StopAnyPlaying(this)); AddAssert("not stopped", () => track.IsRunning); @@ -89,16 +94,24 @@ namespace osu.Game.Tests.Visual.Components { var track = getTrack(); - Add(track); + LoadComponentAsync(track, Add); return track; } private class TestTrackOwner : CompositeDrawable, IPreviewTrackOwner { + private readonly PreviewTrack track; + public TestTrackOwner(PreviewTrack track) { - AddInternal(track); + this.track = track; + } + + [BackgroundDependencyLoader] + private void load() + { + LoadComponentAsync(track, AddInternal); } protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent) From 6ecea0e4c1e3ce8a42a5880baf7c6a7730d85844 Mon Sep 17 00:00:00 2001 From: iiSaLMaN Date: Wed, 6 Nov 2019 18:15:49 +0300 Subject: [PATCH 165/177] Fix DummyAPIAccess being potentially incorrect --- osu.Game/Online/API/DummyAPIAccess.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Online/API/DummyAPIAccess.cs b/osu.Game/Online/API/DummyAPIAccess.cs index 6c04c77dc0..28132765d3 100644 --- a/osu.Game/Online/API/DummyAPIAccess.cs +++ b/osu.Game/Online/API/DummyAPIAccess.cs @@ -19,7 +19,7 @@ namespace osu.Game.Online.API public Bindable Activity { get; } = new Bindable(); - public bool IsLoggedIn => true; + public bool IsLoggedIn => State == APIState.Online; public string ProvidedUsername => LocalUser.Value.Username; From b1da81571f77132b4267c92b217d5c7ca5c4916e Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Nov 2019 14:00:12 +0900 Subject: [PATCH 166/177] Implement slider head control point snapping --- .../Sliders/Components/PathControlPointPiece.cs | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs index 0353ba241c..155e814596 100644 --- a/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs +++ b/osu.Game.Rulesets.Osu/Edit/Blueprints/Sliders/Components/PathControlPointPiece.cs @@ -10,6 +10,7 @@ using osu.Framework.Graphics.Lines; using osu.Framework.Graphics.Shapes; using osu.Framework.Input.Events; using osu.Game.Graphics; +using osu.Game.Rulesets.Edit; using osu.Game.Rulesets.Osu.Objects; using osuTK; using osuTK.Graphics; @@ -29,6 +30,9 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components private readonly Container marker; private readonly Drawable markerRing; + [Resolved(CanBeNull = true)] + private IDistanceSnapProvider snapProvider { get; set; } + [Resolved] private OsuColour colours { get; set; } @@ -146,12 +150,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components if (Index == 0) { - // Special handling for the head - only the position of the slider changes - slider.Position += e.Delta; + // Special handling for the head control point - the position of the slider changes which means the snapped position and time have to be taken into account + (Vector2 snappedPosition, double snappedTime) = snapProvider?.GetSnappedPosition(e.MousePosition, slider.StartTime) ?? (e.MousePosition, slider.StartTime); + Vector2 movementDelta = snappedPosition - slider.Position; + + slider.Position += movementDelta; + slider.StartTime = snappedTime; // Since control points are relative to the position of the slider, they all need to be offset backwards by the delta for (int i = 1; i < newControlPoints.Length; i++) - newControlPoints[i] -= e.Delta; + newControlPoints[i] -= movementDelta; } else newControlPoints[Index] += e.Delta; From f3ddc4c00b60c3e08b68de08f6409ea967df58ca Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Thu, 7 Nov 2019 14:08:02 +0900 Subject: [PATCH 167/177] Advance editor clock after a snapped placement --- osu.Game/Rulesets/Edit/HitObjectComposer.cs | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/osu.Game/Rulesets/Edit/HitObjectComposer.cs b/osu.Game/Rulesets/Edit/HitObjectComposer.cs index 1c942e52ce..805fc2b46f 100644 --- a/osu.Game/Rulesets/Edit/HitObjectComposer.cs +++ b/osu.Game/Rulesets/Edit/HitObjectComposer.cs @@ -40,6 +40,9 @@ namespace osu.Game.Rulesets.Edit [Resolved] protected IFrameBasedClock EditorClock { get; private set; } + [Resolved] + private IAdjustableClock adjustableClock { get; set; } + [Resolved] private BindableBeatDivisor beatDivisor { get; set; } @@ -256,6 +259,9 @@ namespace osu.Game.Rulesets.Edit public void EndPlacement(HitObject hitObject) { EditorBeatmap.Add(hitObject); + + adjustableClock.Seek(hitObject.StartTime); + showGridFor(Enumerable.Empty()); } From f2084df0bb25c9336237f47de7c6d6e4434cb18d Mon Sep 17 00:00:00 2001 From: "dependabot-preview[bot]" <27856297+dependabot-preview[bot]@users.noreply.github.com> Date: Thu, 7 Nov 2019 06:19:49 +0000 Subject: [PATCH 168/177] Bump Microsoft.NET.Test.Sdk from 16.3.0 to 16.4.0 Bumps [Microsoft.NET.Test.Sdk](https://github.com/microsoft/vstest) from 16.3.0 to 16.4.0. - [Release notes](https://github.com/microsoft/vstest/releases) - [Commits](https://github.com/microsoft/vstest/compare/v16.3...v16.4.0) Signed-off-by: dependabot-preview[bot] --- .../osu.Game.Rulesets.Catch.Tests.csproj | 2 +- .../osu.Game.Rulesets.Mania.Tests.csproj | 2 +- osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj | 2 +- .../osu.Game.Rulesets.Taiko.Tests.csproj | 2 +- osu.Game.Tests/osu.Game.Tests.csproj | 2 +- osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj index 4b629902cb..1dbe9b39ee 100644 --- a/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj +++ b/osu.Game.Rulesets.Catch.Tests/osu.Game.Rulesets.Catch.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj index 30511d672d..8fc4dbfe72 100644 --- a/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj +++ b/osu.Game.Rulesets.Mania.Tests/osu.Game.Rulesets.Mania.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj index 3aea9e0387..fddf176fd0 100644 --- a/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj +++ b/osu.Game.Rulesets.Osu.Tests/osu.Game.Rulesets.Osu.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj index 717e795112..b5bd384e05 100644 --- a/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj +++ b/osu.Game.Rulesets.Taiko.Tests/osu.Game.Rulesets.Taiko.Tests.csproj @@ -2,7 +2,7 @@ - + diff --git a/osu.Game.Tests/osu.Game.Tests.csproj b/osu.Game.Tests/osu.Game.Tests.csproj index aa29fc802c..c5998c9cfc 100644 --- a/osu.Game.Tests/osu.Game.Tests.csproj +++ b/osu.Game.Tests/osu.Game.Tests.csproj @@ -3,7 +3,7 @@ - + diff --git a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj index 371ffcdf9e..d58a724c27 100644 --- a/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj +++ b/osu.Game.Tournament.Tests/osu.Game.Tournament.Tests.csproj @@ -5,7 +5,7 @@ - + From 5fe764b2db98b30113fdc9f2fc7690df8db97ed8 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Nov 2019 16:19:34 +0900 Subject: [PATCH 169/177] Fix tournament videos stuttering when changing scenes --- .../Components/TourneyVideo.cs | 21 +++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index 4f4660f645..f28a2773b6 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -7,6 +7,7 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Video; +using osu.Framework.Timing; using osu.Game.Graphics; namespace osu.Game.Tournament.Components @@ -15,6 +16,9 @@ namespace osu.Game.Tournament.Components { private readonly VideoSprite video; + private ManualClock manualClock; + private IFrameBasedClock sourceClock; + public TourneyVideo(Stream stream) { if (stream == null) @@ -41,5 +45,22 @@ namespace osu.Game.Tournament.Components video.Loop = value; } } + + protected override void LoadComplete() + { + base.LoadComplete(); + + sourceClock = Clock; + Clock = new FramedClock(manualClock = new ManualClock()); + } + + protected override void Update() + { + base.Update(); + + // we want to avoid seeking as much as possible, because we care about performance, not sync. + // to avoid seeking completely, we only increment out local clock when in an updating state. + manualClock.CurrentTime += sourceClock.ElapsedFrameTime; + } } } From 8bcbc93501626e11d230532647f3dfff374121aa Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Nov 2019 17:00:47 +0900 Subject: [PATCH 170/177] Fix tournament buttons playing sound effects --- .../Screens/Drawings/DrawingsScreen.cs | 9 +++---- .../Screens/Editors/TeamEditorScreen.cs | 3 +-- .../Screens/Editors/TournamentEditorScreen.cs | 3 +-- .../Screens/Gameplay/GameplayScreen.cs | 4 +-- .../Screens/MapPool/MapPoolScreen.cs | 10 ++++---- osu.Game.Tournament/TournamentGameBase.cs | 3 +-- osu.Game.Tournament/TournamentSceneManager.cs | 25 +++++++++---------- osu.Game.Tournament/TourneyButton.cs | 15 +++++++++++ osu.Game/Graphics/UserInterface/OsuButton.cs | 6 +++-- 9 files changed, 45 insertions(+), 33 deletions(-) create mode 100644 osu.Game.Tournament/TourneyButton.cs diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index 3a14b6d9c2..e8906466f3 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -15,7 +15,6 @@ using osu.Framework.Logging; using osu.Framework.Platform; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; -using osu.Game.Graphics.UserInterface; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; using osu.Game.Tournament.Screens.Drawings.Components; @@ -128,21 +127,21 @@ namespace osu.Game.Tournament.Screens.Drawings // Control panel container new ControlPanel { - new OsuButton + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Begin random", Action = teamsContainer.StartScrolling, }, - new OsuButton + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Stop random", Action = teamsContainer.StopScrolling, }, - new OsuButton + new TourneyButton { RelativeSizeAxes = Axes.X, @@ -150,7 +149,7 @@ namespace osu.Game.Tournament.Screens.Drawings Action = reloadTeams }, new ControlPanel.Spacer(), - new OsuButton + new TourneyButton { RelativeSizeAxes = Axes.X, diff --git a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs index a4479f3cfd..e1f6d16623 100644 --- a/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TeamEditorScreen.cs @@ -11,7 +11,6 @@ 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.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Overlays.Settings; @@ -32,7 +31,7 @@ namespace osu.Game.Tournament.Screens.Editors [BackgroundDependencyLoader] private void load() { - ControlPanel.Add(new OsuButton + ControlPanel.Add(new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Add all countries", diff --git a/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs b/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs index 50d3207345..32cf6bbcc8 100644 --- a/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs +++ b/osu.Game.Tournament/Screens/Editors/TournamentEditorScreen.cs @@ -9,7 +9,6 @@ using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Game.Graphics; using osu.Game.Graphics.Containers; -using osu.Game.Graphics.UserInterface; using osu.Game.Overlays.Settings; using osu.Game.Tournament.Components; using osuTK; @@ -56,7 +55,7 @@ namespace osu.Game.Tournament.Screens.Editors { Children = new Drawable[] { - new OsuButton + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Add new", diff --git a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs index b9a74bfe16..6a3095d42d 100644 --- a/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs +++ b/osu.Game.Tournament/Screens/Gameplay/GameplayScreen.cs @@ -103,13 +103,13 @@ namespace osu.Game.Tournament.Screens.Gameplay { Children = new Drawable[] { - warmupButton = new OsuButton + warmupButton = new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Toggle warmup", Action = () => warmup.Toggle() }, - new OsuButton + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Toggle chat", diff --git a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs index ec55bb5b54..7a5fc2cd06 100644 --- a/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs +++ b/osu.Game.Tournament/Screens/MapPool/MapPoolScreen.cs @@ -60,32 +60,32 @@ namespace osu.Game.Tournament.Screens.MapPool { Text = "Current Mode" }, - buttonRedBan = new OsuButton + buttonRedBan = new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Red Ban", Action = () => setMode(TeamColour.Red, ChoiceType.Ban) }, - buttonBlueBan = new OsuButton + buttonBlueBan = new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Blue Ban", Action = () => setMode(TeamColour.Blue, ChoiceType.Ban) }, - buttonRedPick = new OsuButton + buttonRedPick = new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Red Pick", Action = () => setMode(TeamColour.Red, ChoiceType.Pick) }, - buttonBluePick = new OsuButton + buttonBluePick = new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Blue Pick", Action = () => setMode(TeamColour.Blue, ChoiceType.Pick) }, new ControlPanel.Spacer(), - new OsuButton + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Reset", diff --git a/osu.Game.Tournament/TournamentGameBase.cs b/osu.Game.Tournament/TournamentGameBase.cs index 21552882ef..3b7718c61d 100644 --- a/osu.Game.Tournament/TournamentGameBase.cs +++ b/osu.Game.Tournament/TournamentGameBase.cs @@ -18,7 +18,6 @@ using osu.Framework.IO.Stores; using osu.Framework.Platform; using osu.Game.Beatmaps; using osu.Game.Graphics; -using osu.Game.Graphics.UserInterface; using osu.Game.Online.API.Requests; using osu.Game.Tournament.IPC; using osu.Game.Tournament.Models; @@ -76,7 +75,7 @@ namespace osu.Game.Tournament AddRange(new[] { - new OsuButton + new TourneyButton { Text = "Save Changes", Width = 140, diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index 02ee1c8603..7164141dea 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -8,7 +8,6 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Platform; -using osu.Game.Graphics.UserInterface; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; using osu.Game.Tournament.Screens; @@ -107,23 +106,23 @@ namespace osu.Game.Tournament Direction = FillDirection.Vertical, Children = new Drawable[] { - new OsuButton { RelativeSizeAxes = Axes.X, Text = "Setup", Action = () => SetScreen(typeof(SetupScreen)) }, + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Setup", Action = () => SetScreen(typeof(SetupScreen)) }, new Container { RelativeSizeAxes = Axes.X, Height = 50 }, - new OsuButton { RelativeSizeAxes = Axes.X, Text = "Team Editor", Action = () => SetScreen(typeof(TeamEditorScreen)) }, - new OsuButton { RelativeSizeAxes = Axes.X, Text = "Rounds Editor", Action = () => SetScreen(typeof(RoundEditorScreen)) }, - new OsuButton { RelativeSizeAxes = Axes.X, Text = "Bracket Editor", Action = () => SetScreen(typeof(LadderEditorScreen)) }, + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Team Editor", Action = () => SetScreen(typeof(TeamEditorScreen)) }, + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Rounds Editor", Action = () => SetScreen(typeof(RoundEditorScreen)) }, + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Bracket Editor", Action = () => SetScreen(typeof(LadderEditorScreen)) }, new Container { RelativeSizeAxes = Axes.X, Height = 50 }, - new OsuButton { RelativeSizeAxes = Axes.X, Text = "Drawings", Action = () => SetScreen(typeof(DrawingsScreen)) }, - new OsuButton { RelativeSizeAxes = Axes.X, Text = "Showcase", Action = () => SetScreen(typeof(ShowcaseScreen)) }, + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Drawings", Action = () => SetScreen(typeof(DrawingsScreen)) }, + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Showcase", Action = () => SetScreen(typeof(ShowcaseScreen)) }, new Container { RelativeSizeAxes = Axes.X, Height = 50 }, - new OsuButton { RelativeSizeAxes = Axes.X, Text = "Schedule", Action = () => SetScreen(typeof(ScheduleScreen)) }, - new OsuButton { RelativeSizeAxes = Axes.X, Text = "Bracket", Action = () => SetScreen(typeof(LadderScreen)) }, + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Schedule", Action = () => SetScreen(typeof(ScheduleScreen)) }, + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Bracket", Action = () => SetScreen(typeof(LadderScreen)) }, new Container { RelativeSizeAxes = Axes.X, Height = 50 }, - new OsuButton { RelativeSizeAxes = Axes.X, Text = "TeamIntro", Action = () => SetScreen(typeof(TeamIntroScreen)) }, - new OsuButton { RelativeSizeAxes = Axes.X, Text = "MapPool", Action = () => SetScreen(typeof(MapPoolScreen)) }, - new OsuButton { RelativeSizeAxes = Axes.X, Text = "Gameplay", Action = () => SetScreen(typeof(GameplayScreen)) }, + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "TeamIntro", Action = () => SetScreen(typeof(TeamIntroScreen)) }, + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "MapPool", Action = () => SetScreen(typeof(MapPoolScreen)) }, + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Gameplay", Action = () => SetScreen(typeof(GameplayScreen)) }, new Container { RelativeSizeAxes = Axes.X, Height = 50 }, - new OsuButton { RelativeSizeAxes = Axes.X, Text = "Win", Action = () => SetScreen(typeof(TeamWinScreen)) }, + new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Win", Action = () => SetScreen(typeof(TeamWinScreen)) }, } }, }, diff --git a/osu.Game.Tournament/TourneyButton.cs b/osu.Game.Tournament/TourneyButton.cs new file mode 100644 index 0000000000..12872d3197 --- /dev/null +++ b/osu.Game.Tournament/TourneyButton.cs @@ -0,0 +1,15 @@ +// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence. +// See the LICENCE file in the repository root for full licence text. + +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Tournament +{ + public class TourneyButton : OsuButton + { + public TourneyButton() + : base(null) + { + } + } +} diff --git a/osu.Game/Graphics/UserInterface/OsuButton.cs b/osu.Game/Graphics/UserInterface/OsuButton.cs index 2750e61f0d..c6a9aa1c97 100644 --- a/osu.Game/Graphics/UserInterface/OsuButton.cs +++ b/osu.Game/Graphics/UserInterface/OsuButton.cs @@ -48,7 +48,7 @@ namespace osu.Game.Graphics.UserInterface protected Box Background; protected SpriteText SpriteText; - public OsuButton() + public OsuButton(HoverSampleSet? hoverSounds = HoverSampleSet.Loud) { Height = 40; @@ -78,10 +78,12 @@ namespace osu.Game.Graphics.UserInterface Depth = float.MinValue }, SpriteText = CreateText(), - new HoverClickSounds(HoverSampleSet.Loud), } }); + if (hoverSounds.HasValue) + AddInternal(new HoverClickSounds(hoverSounds.Value)); + Enabled.BindValueChanged(enabledChanged, true); } From 380859e06e11137716ec7b2748214a58a213b0b4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Nov 2019 17:13:45 +0900 Subject: [PATCH 171/177] Set clock on video directly; ignore long frames --- .../Components/TourneyVideo.cs | 21 +++++++------------ 1 file changed, 8 insertions(+), 13 deletions(-) diff --git a/osu.Game.Tournament/Components/TourneyVideo.cs b/osu.Game.Tournament/Components/TourneyVideo.cs index f28a2773b6..8582b1634c 100644 --- a/osu.Game.Tournament/Components/TourneyVideo.cs +++ b/osu.Game.Tournament/Components/TourneyVideo.cs @@ -16,8 +16,7 @@ namespace osu.Game.Tournament.Components { private readonly VideoSprite video; - private ManualClock manualClock; - private IFrameBasedClock sourceClock; + private readonly ManualClock manualClock; public TourneyVideo(Stream stream) { @@ -34,6 +33,7 @@ namespace osu.Game.Tournament.Components { RelativeSizeAxes = Axes.Both, FillMode = FillMode.Fit, + Clock = new FramedClock(manualClock = new ManualClock()) }; } @@ -46,21 +46,16 @@ namespace osu.Game.Tournament.Components } } - protected override void LoadComplete() - { - base.LoadComplete(); - - sourceClock = Clock; - Clock = new FramedClock(manualClock = new ManualClock()); - } - protected override void Update() { base.Update(); - // we want to avoid seeking as much as possible, because we care about performance, not sync. - // to avoid seeking completely, we only increment out local clock when in an updating state. - manualClock.CurrentTime += sourceClock.ElapsedFrameTime; + if (manualClock != null && Clock.ElapsedFrameTime < 100) + { + // we want to avoid seeking as much as possible, because we care about performance, not sync. + // to avoid seeking completely, we only increment out local clock when in an updating state. + manualClock.CurrentTime += Clock.ElapsedFrameTime; + } } } } From 651a1e2ae10fb5001aa0a7e7fa466fd0e429ca3e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Nov 2019 17:20:20 +0900 Subject: [PATCH 172/177] Highlight selected tournament screen's button --- osu.Game.Tournament/TournamentSceneManager.cs | 85 +++++++++++++++---- 1 file changed, 67 insertions(+), 18 deletions(-) diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index 7164141dea..69248b6692 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Platform; +using osu.Game.Graphics; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; using osu.Game.Tournament.Screens; @@ -35,6 +36,7 @@ namespace osu.Game.Tournament private TournamentMatchChatDisplay chat = new TournamentMatchChatDisplay(); private Container chatContainer; + private FillFlowContainer buttons; public TournamentSceneManager() { @@ -100,29 +102,32 @@ namespace osu.Game.Tournament Colour = Color4.Black, RelativeSizeAxes = Axes.Both, }, - new FillFlowContainer + buttons = new FillFlowContainer { RelativeSizeAxes = Axes.Both, Direction = FillDirection.Vertical, + Spacing = new Vector2(2), + Padding = new MarginPadding(2), Children = new Drawable[] { - new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Setup", Action = () => SetScreen(typeof(SetupScreen)) }, - new Container { RelativeSizeAxes = Axes.X, Height = 50 }, - new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Team Editor", Action = () => SetScreen(typeof(TeamEditorScreen)) }, - new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Rounds Editor", Action = () => SetScreen(typeof(RoundEditorScreen)) }, - new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Bracket Editor", Action = () => SetScreen(typeof(LadderEditorScreen)) }, - new Container { RelativeSizeAxes = Axes.X, Height = 50 }, - new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Drawings", Action = () => SetScreen(typeof(DrawingsScreen)) }, - new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Showcase", Action = () => SetScreen(typeof(ShowcaseScreen)) }, - new Container { RelativeSizeAxes = Axes.X, Height = 50 }, - new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Schedule", Action = () => SetScreen(typeof(ScheduleScreen)) }, - new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Bracket", Action = () => SetScreen(typeof(LadderScreen)) }, - new Container { RelativeSizeAxes = Axes.X, Height = 50 }, - new TourneyButton { RelativeSizeAxes = Axes.X, Text = "TeamIntro", Action = () => SetScreen(typeof(TeamIntroScreen)) }, - new TourneyButton { RelativeSizeAxes = Axes.X, Text = "MapPool", Action = () => SetScreen(typeof(MapPoolScreen)) }, - new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Gameplay", Action = () => SetScreen(typeof(GameplayScreen)) }, - new Container { RelativeSizeAxes = Axes.X, Height = 50 }, - new TourneyButton { RelativeSizeAxes = Axes.X, Text = "Win", Action = () => SetScreen(typeof(TeamWinScreen)) }, + new ScreenButton(typeof(SetupScreen)) { Text = "Setup", RequestSelection = SetScreen }, + new Separator(), + new ScreenButton(typeof(TeamEditorScreen)) { Text = "Team Editor", RequestSelection = SetScreen }, + new ScreenButton(typeof(RoundEditorScreen)) { Text = "Rounds Editor", RequestSelection = SetScreen }, + new ScreenButton(typeof(LadderEditorScreen)) { Text = "Bracket Editor", RequestSelection = SetScreen }, + new Separator(), + new ScreenButton(typeof(ScheduleScreen)) { Text = "Schedule", RequestSelection = SetScreen }, + new ScreenButton(typeof(LadderScreen)) { Text = "Bracket", RequestSelection = SetScreen }, + new Separator(), + new ScreenButton(typeof(TeamIntroScreen)) { Text = "TeamIntro", RequestSelection = SetScreen }, + new Separator(), + new ScreenButton(typeof(MapPoolScreen)) { Text = "MapPool", RequestSelection = SetScreen }, + new ScreenButton(typeof(GameplayScreen)) { Text = "Gameplay", RequestSelection = SetScreen }, + new Separator(), + new ScreenButton(typeof(TeamWinScreen)) { Text = "Win", RequestSelection = SetScreen }, + new Separator(), + new ScreenButton(typeof(DrawingsScreen)) { Text = "Drawings", RequestSelection = SetScreen }, + new ScreenButton(typeof(ShowcaseScreen)) { Text = "Showcase", RequestSelection = SetScreen }, } }, }, @@ -162,6 +167,50 @@ namespace osu.Game.Tournament chatContainer.FadeOut(100); break; } + + foreach (var s in buttons.OfType()) + s.IsSelected = screenType == s.Type; + } + + private class Separator : CompositeDrawable + { + public Separator() + { + RelativeSizeAxes = Axes.X; + Height = 20; + } + } + + private class ScreenButton : TourneyButton + { + public readonly Type Type; + + public ScreenButton(Type type) + { + Type = type; + BackgroundColour = OsuColour.Gray(0.2f); + Action = () => RequestSelection(type); + + RelativeSizeAxes = Axes.X; + } + + private bool isSelected; + + public Action RequestSelection; + + public bool IsSelected + { + get => isSelected; + set + { + if (value == isSelected) + return; + + isSelected = value; + BackgroundColour = isSelected ? Color4.SkyBlue : OsuColour.Gray(0.2f); + SpriteText.Colour = isSelected ? Color4.Black : Color4.White; + } + } } } } From eb9fff96bacec5ca553e0565336d11b9e341bd33 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Nov 2019 17:12:47 +0900 Subject: [PATCH 173/177] Read default beat divisor from beatmap --- osu.Game/Screens/Edit/Editor.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index 35408e4003..97f01fd9f3 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -64,7 +64,10 @@ namespace osu.Game.Screens.Edit { this.host = host; - // TODO: should probably be done at a DrawableRuleset level to share logic with Player. + beatDivisor.Value = Beatmap.Value.BeatmapInfo.BeatDivisor; + beatDivisor.BindValueChanged(divisor => Beatmap.Value.BeatmapInfo.BeatDivisor = divisor.NewValue); + + // Todo: should probably be done at a DrawableRuleset level to share logic with Player. var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock(); clock = new EditorClock(Beatmap.Value, beatDivisor) { IsCoupled = false }; clock.ChangeSource(sourceClock); From 998e1dfe47aa0b1a2e05a63508194d0a831e208b Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Nov 2019 17:23:47 +0900 Subject: [PATCH 174/177] Fix non-1/1 initial beat divisor control display --- .../Edit/Compose/Components/BeatDivisorControl.cs | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs index 4d89e43ee5..9e4691e4dd 100644 --- a/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs +++ b/osu.Game/Screens/Edit/Compose/Components/BeatDivisorControl.cs @@ -218,12 +218,17 @@ namespace osu.Game.Screens.Edit.Compose.Components } AddInternal(marker = new Marker()); + } - CurrentNumber.ValueChanged += div => + protected override void LoadComplete() + { + base.LoadComplete(); + + CurrentNumber.BindValueChanged(div => { marker.MoveToX(getMappedPosition(div.NewValue), 100, Easing.OutQuint); marker.Flash(); - }; + }, true); } protected override void UpdateValue(float value) From 36cc79f04f94c1c077fe8575a97e9b012f973d84 Mon Sep 17 00:00:00 2001 From: smoogipoo Date: Fri, 8 Nov 2019 17:24:02 +0900 Subject: [PATCH 175/177] Softly handle invalid beat divisors instead of throwing --- osu.Game/Screens/Edit/BindableBeatDivisor.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/BindableBeatDivisor.cs b/osu.Game/Screens/Edit/BindableBeatDivisor.cs index a724f354e7..ce95d81f54 100644 --- a/osu.Game/Screens/Edit/BindableBeatDivisor.cs +++ b/osu.Game/Screens/Edit/BindableBeatDivisor.cs @@ -29,7 +29,10 @@ namespace osu.Game.Screens.Edit set { if (!VALID_DIVISORS.Contains(value)) - throw new ArgumentOutOfRangeException($"Provided divisor is not in {nameof(VALID_DIVISORS)}"); + { + // If it doesn't match, value will be 0, but will be clamped to the valid range via DefaultMinValue + value = Array.FindLast(VALID_DIVISORS, d => d < value); + } base.Value = value; } From 7b8ed1ac9ad0567189d327670e37b3b4dd7ad26c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Nov 2019 17:26:24 +0900 Subject: [PATCH 176/177] Improve tournament screen transitions --- .../Screens/TournamentScreen.cs | 12 ++--- osu.Game.Tournament/TournamentSceneManager.cs | 54 +++++++++++++------ 2 files changed, 42 insertions(+), 24 deletions(-) diff --git a/osu.Game.Tournament/Screens/TournamentScreen.cs b/osu.Game.Tournament/Screens/TournamentScreen.cs index 9d58ca2240..0b5b3e728b 100644 --- a/osu.Game.Tournament/Screens/TournamentScreen.cs +++ b/osu.Game.Tournament/Screens/TournamentScreen.cs @@ -10,6 +10,8 @@ namespace osu.Game.Tournament.Screens { public abstract class TournamentScreen : CompositeDrawable { + public const double FADE_DELAY = 200; + [Resolved] protected LadderInfo LadderInfo { get; private set; } @@ -18,14 +20,8 @@ namespace osu.Game.Tournament.Screens RelativeSizeAxes = Axes.Both; } - public override void Hide() - { - this.FadeOut(200); - } + public override void Hide() => this.FadeOut(FADE_DELAY); - public override void Show() - { - this.FadeIn(200); - } + public override void Show() => this.FadeIn(FADE_DELAY); } } diff --git a/osu.Game.Tournament/TournamentSceneManager.cs b/osu.Game.Tournament/TournamentSceneManager.cs index 02ee1c8603..d66f980202 100644 --- a/osu.Game.Tournament/TournamentSceneManager.cs +++ b/osu.Game.Tournament/TournamentSceneManager.cs @@ -8,6 +8,7 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Platform; +using osu.Framework.Threading; using osu.Game.Graphics.UserInterface; using osu.Game.Tournament.Components; using osu.Game.Tournament.Models; @@ -130,37 +131,58 @@ namespace osu.Game.Tournament }, }; + foreach (var drawable in screens) + drawable.Hide(); + SetScreen(typeof(SetupScreen)); } + private float depth; + + private Drawable currentScreen; + private ScheduledDelegate scheduledHide; + public void SetScreen(Type screenType) { - var screen = screens.FirstOrDefault(s => s.GetType() == screenType); - if (screen == null) return; + var target = screens.FirstOrDefault(s => s.GetType() == screenType); - foreach (var s in screens.Children) + if (target == null || currentScreen == target) return; + + if (scheduledHide?.Completed == false) { - if (s == screen) - { - s.Show(); - if (s is IProvideVideo) - video.FadeOut(200); - else - video.Show(); - } - else - s.Hide(); + scheduledHide.RunTask(); + scheduledHide.Cancel(); // see https://github.com/ppy/osu-framework/issues/2967 + scheduledHide = null; } - switch (screen) + var lastScreen = currentScreen; + currentScreen = target; + + if (currentScreen is IProvideVideo) + { + video.FadeOut(200); + + // delay the hide to avoid a double-fade transition. + scheduledHide = Scheduler.AddDelayed(() => lastScreen?.Hide(), TournamentScreen.FADE_DELAY); + } + else + { + lastScreen?.Hide(); + video.Show(); + } + + screens.ChangeChildDepth(currentScreen, depth--); + currentScreen.Show(); + + switch (currentScreen) { case GameplayScreen _: case MapPoolScreen _: - chatContainer.FadeIn(100); + chatContainer.FadeIn(TournamentScreen.FADE_DELAY); break; default: - chatContainer.FadeOut(100); + chatContainer.FadeOut(TournamentScreen.FADE_DELAY); break; } } From 12b855d68feffd9b2053284de58d0c20c7bcdb36 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Fri, 8 Nov 2019 17:56:11 +0900 Subject: [PATCH 177/177] Make drawing screen transition properly --- osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs index 3a14b6d9c2..f78124a66a 100644 --- a/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs +++ b/osu.Game.Tournament/Screens/Drawings/DrawingsScreen.cs @@ -24,7 +24,7 @@ using osuTK.Graphics; namespace osu.Game.Tournament.Screens.Drawings { - public class DrawingsScreen : CompositeDrawable + public class DrawingsScreen : TournamentScreen { private const string results_filename = "drawings_results.txt";