From ce9dd0c9203a160b20bd4b90fe2c87f078e55e58 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 17:19:01 +0900 Subject: [PATCH 01/53] Fix enum descriptions not being displayed in OverlayHeaderTabControl --- osu.Game/Overlays/TabControlOverlayHeader.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/TabControlOverlayHeader.cs b/osu.Game/Overlays/TabControlOverlayHeader.cs index 61605d9e9e..7798dfa576 100644 --- a/osu.Game/Overlays/TabControlOverlayHeader.cs +++ b/osu.Game/Overlays/TabControlOverlayHeader.cs @@ -1,9 +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 JetBrains.Annotations; 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; @@ -104,7 +106,7 @@ namespace osu.Game.Overlays public OverlayHeaderTabItem(T value) : base(value) { - Text.Text = value.ToString().ToLower(); + Text.Text = ((Value as Enum)?.GetDescription() ?? Value.ToString()).ToLower(); Text.Font = OsuFont.GetFont(size: 14); Text.Margin = new MarginPadding { Vertical = 16.5f }; // 15px padding + 1.5px line-height difference compensation Bar.Margin = new MarginPadding { Bottom = bar_height }; From 32becb6882bf7a800565499cc70a38019bb563cd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 17:19:14 +0900 Subject: [PATCH 02/53] Add simple listing of currently playing users --- .../Dashboard/CurrentlyPlayingDisplay.cs | 95 +++++++++++++++++++ .../Dashboard/DashboardOverlayHeader.cs | 7 +- osu.Game/Overlays/DashboardOverlay.cs | 4 + 3 files changed, 105 insertions(+), 1 deletion(-) create mode 100644 osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs new file mode 100644 index 0000000000..8a98614fa0 --- /dev/null +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -0,0 +1,95 @@ +// 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.Specialized; +using System.Linq; +using osu.Framework.Allocation; +using osu.Framework.Bindables; +using osu.Framework.Graphics; +using osu.Framework.Graphics.Containers; +using osu.Framework.Screens; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.Spectator; +using osu.Game.Screens.Play; +using osu.Game.Users; +using osuTK; + +namespace osu.Game.Overlays.Dashboard +{ + internal class CurrentlyPlayingDisplay : CompositeDrawable + { + private IBindableList playingUsers; + + private FillFlowContainer userFlow; + + [Resolved] + private SpectatorStreamingClient spectatorStreaming { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + RelativeSizeAxes = Axes.X; + AutoSizeAxes = Axes.Y; + + InternalChild = userFlow = new FillFlowContainer + { + RelativeSizeAxes = Axes.X, + AutoSizeAxes = Axes.Y, + Spacing = new Vector2(10), + }; + } + + [Resolved] + private IAPIProvider api { get; set; } + + protected override void LoadComplete() + { + base.LoadComplete(); + + playingUsers = spectatorStreaming.PlayingUsers.GetBoundCopy(); + playingUsers.BindCollectionChanged((sender, e) => Schedule(() => + { + switch (e.Action) + { + case NotifyCollectionChangedAction.Add: + foreach (var u in e.NewItems.OfType()) + { + var request = new GetUserRequest(u); + request.Success += user => Schedule(() => + { + if (playingUsers.Contains((int)user.Id)) + userFlow.Add(createUserPanel(user)); + }); + api.Queue(request); + } + + break; + + case NotifyCollectionChangedAction.Remove: + foreach (var u in e.OldItems.OfType()) + userFlow.FirstOrDefault(card => card.User.Id == u)?.Expire(); + break; + + case NotifyCollectionChangedAction.Reset: + userFlow.Clear(); + break; + } + }), true); + } + + [Resolved] + private OsuGame game { get; set; } + + private UserPanel createUserPanel(User user) + { + return new UserGridPanel(user).With(panel => + { + panel.Anchor = Anchor.TopCentre; + panel.Origin = Anchor.TopCentre; + panel.Width = 290; + panel.Action = () => game.PerformFromScreen(s => s.Push(new Spectator(user))); + }); + } + } +} diff --git a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs index 36bf589877..3314ed957a 100644 --- a/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.cs +++ b/osu.Game/Overlays/Dashboard/DashboardOverlayHeader.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.ComponentModel; + namespace osu.Game.Overlays.Dashboard { public class DashboardOverlayHeader : TabControlOverlayHeader @@ -20,6 +22,9 @@ namespace osu.Game.Overlays.Dashboard public enum DashboardOverlayTabs { - Friends + Friends, + + [Description("Currently Playing")] + CurrentlyPlaying } } diff --git a/osu.Game/Overlays/DashboardOverlay.cs b/osu.Game/Overlays/DashboardOverlay.cs index a2490365e4..04defce636 100644 --- a/osu.Game/Overlays/DashboardOverlay.cs +++ b/osu.Game/Overlays/DashboardOverlay.cs @@ -130,6 +130,10 @@ namespace osu.Game.Overlays loadDisplay(new FriendDisplay()); break; + case DashboardOverlayTabs.CurrentlyPlaying: + loadDisplay(new CurrentlyPlayingDisplay()); + break; + default: throw new NotImplementedException($"Display for {tab.NewValue} tab is not implemented"); } From 84d854e23136434dcb5213abdf028f4c6ef63e6c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 17:23:38 +0900 Subject: [PATCH 03/53] Avoid having the user profile show when clicking a spectator panel --- osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs | 1 + osu.Game/Users/UserPanel.cs | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index 8a98614fa0..d71e582c05 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -88,6 +88,7 @@ namespace osu.Game.Overlays.Dashboard panel.Anchor = Anchor.TopCentre; panel.Origin = Anchor.TopCentre; panel.Width = 290; + panel.ShowProfileOnClick = false; panel.Action = () => game.PerformFromScreen(s => s.Push(new Spectator(user))); }); } diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index 57a87a713d..e97ff4287f 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -22,6 +22,8 @@ namespace osu.Game.Users public new Action Action; + public bool ShowProfileOnClick = true; + protected Action ViewProfile { get; private set; } protected Drawable Background { get; private set; } @@ -68,7 +70,8 @@ namespace osu.Game.Users base.Action = ViewProfile = () => { Action?.Invoke(); - profileOverlay?.ShowUser(User); + if (ShowProfileOnClick) + profileOverlay?.ShowUser(User); }; } From 16b0a7b33e2da40c759142fcfb29097c121e4439 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 18:01:55 +0900 Subject: [PATCH 04/53] Add button flow to allow resuming watching after exiting manually --- osu.Game/Screens/Play/Spectator.cs | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Spectator.cs b/osu.Game/Screens/Play/Spectator.cs index 51cd5b59aa..11cdb66087 100644 --- a/osu.Game/Screens/Play/Spectator.cs +++ b/osu.Game/Screens/Play/Spectator.cs @@ -13,6 +13,7 @@ using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; +using osu.Game.Graphics.UserInterface; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Spectator; @@ -63,6 +64,8 @@ namespace osu.Game.Screens.Play private IBindable> managerUpdated; + private TriangleButton watchButton; + public Spectator([NotNull] User targetUser) { this.targetUser = targetUser ?? throw new ArgumentNullException(nameof(targetUser)); @@ -108,6 +111,14 @@ namespace osu.Game.Screens.Play Anchor = Anchor.Centre, Origin = Anchor.Centre, }, + watchButton = new TriangleButton + { + Text = "Watch", + Width = 250, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Action = attemptStart + } } }, }; @@ -191,9 +202,11 @@ namespace osu.Game.Screens.Play var resolvedBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == state.BeatmapID); + showBeatmapPanel(state.BeatmapID.Value); + if (resolvedBeatmap == null) { - showBeatmapPanel(state.BeatmapID.Value); + watchButton.Enabled.Value = false; return; } @@ -209,6 +222,7 @@ namespace osu.Game.Screens.Play rulesetInstance = resolvedRuleset; beatmap.Value = beatmaps.GetWorkingBeatmap(resolvedBeatmap); + watchButton.Enabled.Value = true; this.Push(new SpectatorPlayerLoader(new Score { From c97feb09bf2872a564d452f89442ed9cd2eaafc4 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 18:05:15 +0900 Subject: [PATCH 05/53] Allow continuing to automatically spectate user from results screen --- osu.Game/Screens/Play/SpectatorPlayer.cs | 20 +++++++-- .../Screens/Play/SpectatorResultsScreen.cs | 42 +++++++++++++++++++ 2 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 osu.Game/Screens/Play/SpectatorResultsScreen.cs diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index fbd21b32ba..93e755f30a 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -7,19 +7,25 @@ using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Online.Spectator; using osu.Game.Scoring; +using osu.Game.Screens.Ranking; namespace osu.Game.Screens.Play { public class SpectatorPlayer : ReplayPlayer { - [Resolved] - private SpectatorStreamingClient spectatorStreaming { get; set; } - public SpectatorPlayer(Score score) : base(score) { } + protected override ResultsScreen CreateResults(ScoreInfo score) + { + return new SpectatorResultsScreen(score); + } + + [Resolved] + private SpectatorStreamingClient spectatorStreaming { get; set; } + [BackgroundDependencyLoader] private void load() { @@ -32,6 +38,14 @@ namespace osu.Game.Screens.Play Schedule(this.Exit); } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (spectatorStreaming != null) + spectatorStreaming.OnUserBeganPlaying -= userBeganPlaying; + } + protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) { // if we already have frames, start gameplay at the point in time they exist, should they be too far into the beatmap. diff --git a/osu.Game/Screens/Play/SpectatorResultsScreen.cs b/osu.Game/Screens/Play/SpectatorResultsScreen.cs new file mode 100644 index 0000000000..bb1bdee3a9 --- /dev/null +++ b/osu.Game/Screens/Play/SpectatorResultsScreen.cs @@ -0,0 +1,42 @@ +// 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.Screens; +using osu.Game.Online.Spectator; +using osu.Game.Scoring; +using osu.Game.Screens.Ranking; + +namespace osu.Game.Screens.Play +{ + public class SpectatorResultsScreen : SoloResultsScreen + { + public SpectatorResultsScreen(ScoreInfo score) + : base(score) + { + } + + [Resolved] + private SpectatorStreamingClient spectatorStreaming { get; set; } + + [BackgroundDependencyLoader] + private void load() + { + spectatorStreaming.OnUserBeganPlaying += userBeganPlaying; + } + + private void userBeganPlaying(int userId, SpectatorState state) + { + if (userId == Score.UserID) + Schedule(this.Exit); + } + + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + + if (spectatorStreaming != null) + spectatorStreaming.OnUserBeganPlaying -= userBeganPlaying; + } + } +} From 2d73dfbe39067052d37310bc7031ad0f3d8681e1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 18:16:19 +0900 Subject: [PATCH 06/53] Add more safety around beatmap panel and button display logic --- osu.Game/Screens/Play/Spectator.cs | 32 ++++++++++++++++++++---------- 1 file changed, 22 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Play/Spectator.cs b/osu.Game/Screens/Play/Spectator.cs index 11cdb66087..d97883069e 100644 --- a/osu.Game/Screens/Play/Spectator.cs +++ b/osu.Game/Screens/Play/Spectator.cs @@ -183,14 +183,22 @@ namespace osu.Game.Screens.Play if (userId != targetUser.Id) return; - if (replay == null) return; + if (replay != null) + { + replay.HasReceivedAllFrames = true; + replay = null; + } - replay.HasReceivedAllFrames = true; - replay = null; + // not really going to start anything, but will clear the beatmap card out. + attemptStart(); } private void attemptStart() { + watchButton.Enabled.Value = false; + + showBeatmapPanel(state); + var resolvedRuleset = rulesets.AvailableRulesets.FirstOrDefault(r => r.ID == state.RulesetID)?.CreateInstance(); // ruleset not available @@ -202,13 +210,8 @@ namespace osu.Game.Screens.Play var resolvedBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == state.BeatmapID); - showBeatmapPanel(state.BeatmapID.Value); - if (resolvedBeatmap == null) - { - watchButton.Enabled.Value = false; return; - } var scoreInfo = new ScoreInfo { @@ -231,11 +234,20 @@ namespace osu.Game.Screens.Play })); } - private void showBeatmapPanel(int beatmapId) + private void showBeatmapPanel(SpectatorState state) { - var req = new GetBeatmapSetRequest(beatmapId, BeatmapSetLookupType.BeatmapId); + if (state.BeatmapID == null) + { + beatmapPanelContainer.Clear(); + return; + } + + var req = new GetBeatmapSetRequest(state.BeatmapID.Value, BeatmapSetLookupType.BeatmapId); req.Success += res => Schedule(() => { + if (state != this.state) + return; + beatmapPanelContainer.Child = new GridBeatmapPanel(res.ToBeatmapSet(rulesets)); }); From 344ff8f4bc9bf76c98abe6c690917ce39d2ef55e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 18:35:20 +0900 Subject: [PATCH 07/53] "Improve" visuals of spectator screen --- osu.Game/Screens/Play/Spectator.cs | 134 ++++++++++++++++++----------- 1 file changed, 86 insertions(+), 48 deletions(-) diff --git a/osu.Game/Screens/Play/Spectator.cs b/osu.Game/Screens/Play/Spectator.cs index d97883069e..19409ade93 100644 --- a/osu.Game/Screens/Play/Spectator.cs +++ b/osu.Game/Screens/Play/Spectator.cs @@ -9,6 +9,8 @@ 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.Graphics.Sprites; using osu.Framework.Screens; using osu.Game.Beatmaps; using osu.Game.Graphics; @@ -24,6 +26,7 @@ using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays.Types; using osu.Game.Scoring; +using osu.Game.Screens.Multi.Match.Components; using osu.Game.Users; using osuTK; @@ -72,55 +75,83 @@ namespace osu.Game.Screens.Play } [BackgroundDependencyLoader] - private void load() + private void load(OsuColour colours) { - InternalChildren = new Drawable[] + InternalChild = new Container { - new FillFlowContainer + Masking = true, + CornerRadius = 20, + AutoSizeAxes = Axes.Both, + AutoSizeDuration = 500, + AutoSizeEasing = Easing.OutQuint, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Children = new Drawable[] { - AutoSizeAxes = Axes.Both, - Direction = FillDirection.Vertical, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Spacing = new Vector2(15), - Children = new Drawable[] + new Box { - new OsuSpriteText + Colour = colours.GreySeafoamDark, + RelativeSizeAxes = Axes.Both, + }, + new FillFlowContainer + { + Margin = new MarginPadding(20), + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Spacing = new Vector2(15), + Children = new Drawable[] { - Text = "Currently spectating", - Font = OsuFont.Default.With(size: 30), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new UserGridPanel(targetUser) - { - Width = 290, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - new OsuSpriteText - { - Text = "playing", - Font = OsuFont.Default.With(size: 30), - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - beatmapPanelContainer = new Container - { - AutoSizeAxes = Axes.Both, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - }, - watchButton = new TriangleButton - { - Text = "Watch", - Width = 250, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, - Action = attemptStart + new OsuSpriteText + { + Text = "Spectator Mode", + Font = OsuFont.Default.With(size: 30), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Horizontal, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Spacing = new Vector2(15), + Children = new Drawable[] + { + new UserGridPanel(targetUser) + { + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + Height = 145, + Width = 290, + }, + new SpriteIcon + { + Size = new Vector2(40), + Icon = FontAwesome.Solid.ArrowRight, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + beatmapPanelContainer = new Container + { + AutoSizeAxes = Axes.Both, + Anchor = Anchor.CentreLeft, + Origin = Anchor.CentreLeft, + }, + } + }, + watchButton = new PurpleTriangleButton + { + Text = "Start Watching", + Width = 250, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Action = attemptStart + } } } - }, + } }; } @@ -141,7 +172,7 @@ namespace osu.Game.Screens.Play private void beatmapUpdated(ValueChangedEvent> beatmap) { if (beatmap.NewValue.TryGetTarget(out var beatmapSet) && beatmapSet.Beatmaps.Any(b => b.OnlineBeatmapID == state.BeatmapID)) - attemptStart(); + Schedule(attemptStart); } private void userSentFrames(int userId, FrameDataBundle data) @@ -189,14 +220,18 @@ namespace osu.Game.Screens.Play replay = null; } - // not really going to start anything, but will clear the beatmap card out. - attemptStart(); + clearDisplay(); + } + + private void clearDisplay() + { + watchButton.Enabled.Value = false; + beatmapPanelContainer.Clear(); } private void attemptStart() { - watchButton.Enabled.Value = false; - + clearDisplay(); showBeatmapPanel(state); var resolvedRuleset = rulesets.AvailableRulesets.FirstOrDefault(r => r.ID == state.RulesetID)?.CreateInstance(); @@ -213,6 +248,9 @@ namespace osu.Game.Screens.Play if (resolvedBeatmap == null) return; + if (replay == null) + return; + var scoreInfo = new ScoreInfo { Beatmap = resolvedBeatmap, @@ -236,7 +274,7 @@ namespace osu.Game.Screens.Play private void showBeatmapPanel(SpectatorState state) { - if (state.BeatmapID == null) + if (state?.BeatmapID == null) { beatmapPanelContainer.Clear(); return; From 93e3e1a4dbcc053f6c6a1f7d319a0a99aca0fc46 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 18:40:06 +0900 Subject: [PATCH 08/53] Don't inherit ReplayPlayer to make results screen work correctly --- osu.Game/Screens/Play/SpectatorPlayer.cs | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index 93e755f30a..bd05665ae7 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -11,11 +11,16 @@ using osu.Game.Screens.Ranking; namespace osu.Game.Screens.Play { - public class SpectatorPlayer : ReplayPlayer + public class SpectatorPlayer : Player { + private readonly Score score; + + protected override bool CheckModsAllowFailure() => false; // todo: better support starting mid-way through beatmap + public SpectatorPlayer(Score score) - : base(score) + : base(true, true) { + this.score = score; } protected override ResultsScreen CreateResults(ScoreInfo score) @@ -32,9 +37,14 @@ namespace osu.Game.Screens.Play spectatorStreaming.OnUserBeganPlaying += userBeganPlaying; } + protected override void PrepareReplay() + { + DrawableRuleset?.SetReplayScore(score); + } + private void userBeganPlaying(int userId, SpectatorState state) { - if (userId == Score.ScoreInfo.UserID) + if (userId == score.ScoreInfo.UserID) Schedule(this.Exit); } @@ -49,7 +59,7 @@ namespace osu.Game.Screens.Play protected override GameplayClockContainer CreateGameplayClockContainer(WorkingBeatmap beatmap, double gameplayStart) { // if we already have frames, start gameplay at the point in time they exist, should they be too far into the beatmap. - double? firstFrameTime = Score.Replay.Frames.FirstOrDefault()?.Time; + double? firstFrameTime = score.Replay.Frames.FirstOrDefault()?.Time; if (firstFrameTime == null || firstFrameTime <= gameplayStart + 5000) return base.CreateGameplayClockContainer(beatmap, gameplayStart); From 4df811985214df8702d772ecb1590eab0947c7fd Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 18:45:09 +0900 Subject: [PATCH 09/53] Add missing schedule --- osu.Game/Screens/Play/Spectator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Spectator.cs b/osu.Game/Screens/Play/Spectator.cs index 19409ade93..9b26bd5ba3 100644 --- a/osu.Game/Screens/Play/Spectator.cs +++ b/osu.Game/Screens/Play/Spectator.cs @@ -220,7 +220,7 @@ namespace osu.Game.Screens.Play replay = null; } - clearDisplay(); + Schedule(clearDisplay); } private void clearDisplay() From 98070898348848f6e8e7d23bc7de5373fdc46a26 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 18:47:15 +0900 Subject: [PATCH 10/53] Fix screen exit potentially occuring during transition --- osu.Game/Screens/Play/SpectatorPlayer.cs | 7 ++++++- osu.Game/Screens/Play/SpectatorResultsScreen.cs | 7 ++++++- 2 files changed, 12 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index bd05665ae7..7c2f8742ba 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -45,7 +45,12 @@ namespace osu.Game.Screens.Play private void userBeganPlaying(int userId, SpectatorState state) { if (userId == score.ScoreInfo.UserID) - Schedule(this.Exit); + { + Schedule(() => + { + if (this.IsCurrentScreen()) this.Exit(); + }); + } } protected override void Dispose(bool isDisposing) diff --git a/osu.Game/Screens/Play/SpectatorResultsScreen.cs b/osu.Game/Screens/Play/SpectatorResultsScreen.cs index bb1bdee3a9..56ccfd2253 100644 --- a/osu.Game/Screens/Play/SpectatorResultsScreen.cs +++ b/osu.Game/Screens/Play/SpectatorResultsScreen.cs @@ -28,7 +28,12 @@ namespace osu.Game.Screens.Play private void userBeganPlaying(int userId, SpectatorState state) { if (userId == Score.UserID) - Schedule(this.Exit); + { + Schedule(() => + { + if (this.IsCurrentScreen()) this.Exit(); + }); + } } protected override void Dispose(bool isDisposing) From 93fd9138762dfcb0a41b07d8f6b04dfe2e2b2129 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 19:03:01 +0900 Subject: [PATCH 11/53] Add setting to allow automatically downloading during a spectating session --- osu.Game/Configuration/OsuConfigManager.cs | 3 ++ .../Settings/Sections/Online/WebSettings.cs | 6 +++ osu.Game/Screens/Play/Spectator.cs | 39 ++++++++++++++++++- 3 files changed, 46 insertions(+), 2 deletions(-) diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index 7d601c0cb9..d22daf697c 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -42,6 +42,8 @@ namespace osu.Game.Configuration Set(OsuSetting.Username, string.Empty); Set(OsuSetting.Token, string.Empty); + Set(OsuSetting.AutomaticallyDownloadWhenSpectating, false); + Set(OsuSetting.SavePassword, false).ValueChanged += enabled => { if (enabled.NewValue) Set(OsuSetting.SaveUsername, true); @@ -239,5 +241,6 @@ namespace osu.Game.Configuration HitLighting, MenuBackgroundSource, GameplayDisableWinKey, + AutomaticallyDownloadWhenSpectating, } } diff --git a/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs b/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs index 6461bd7b93..8134c350a6 100644 --- a/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs +++ b/osu.Game/Overlays/Settings/Sections/Online/WebSettings.cs @@ -27,6 +27,12 @@ namespace osu.Game.Overlays.Settings.Sections.Online Keywords = new[] { "no-video" }, Current = config.GetBindable(OsuSetting.PreferNoVideo) }, + new SettingsCheckbox + { + LabelText = "Automatically download beatmaps when spectating", + Keywords = new[] { "spectator" }, + Current = config.GetBindable(OsuSetting.AutomaticallyDownloadWhenSpectating), + }, }; } } diff --git a/osu.Game/Screens/Play/Spectator.cs b/osu.Game/Screens/Play/Spectator.cs index 9b26bd5ba3..33e97bdff3 100644 --- a/osu.Game/Screens/Play/Spectator.cs +++ b/osu.Game/Screens/Play/Spectator.cs @@ -7,12 +7,14 @@ using System.Linq; using JetBrains.Annotations; 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.Graphics.Sprites; using osu.Framework.Screens; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Sprites; using osu.Game.Graphics.UserInterface; @@ -20,6 +22,7 @@ using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Spectator; using osu.Game.Overlays.BeatmapListing.Panels; +using osu.Game.Overlays.Settings; using osu.Game.Replays; using osu.Game.Rulesets; using osu.Game.Rulesets.Mods; @@ -69,13 +72,17 @@ namespace osu.Game.Screens.Play private TriangleButton watchButton; + private SettingsCheckbox automaticDownload; + + private BeatmapSetInfo onlineBeatmap; + public Spectator([NotNull] User targetUser) { this.targetUser = targetUser ?? throw new ArgumentNullException(nameof(targetUser)); } [BackgroundDependencyLoader] - private void load(OsuColour colours) + private void load(OsuColour colours, OsuConfigManager config) { InternalChild = new Container { @@ -141,6 +148,13 @@ namespace osu.Game.Screens.Play }, } }, + automaticDownload = new SettingsCheckbox + { + LabelText = "Automatically download beatmaps", + Current = config.GetBindable(OsuSetting.AutomaticallyDownloadWhenSpectating), + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, watchButton = new PurpleTriangleButton { Text = "Start Watching", @@ -167,6 +181,8 @@ namespace osu.Game.Screens.Play managerUpdated = beatmaps.ItemUpdated.GetBoundCopy(); managerUpdated.BindValueChanged(beatmapUpdated); + + automaticDownload.Current.BindValueChanged(_ => checkForAutomaticDownload()); } private void beatmapUpdated(ValueChangedEvent> beatmap) @@ -246,7 +262,9 @@ namespace osu.Game.Screens.Play var resolvedBeatmap = beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == state.BeatmapID); if (resolvedBeatmap == null) + { return; + } if (replay == null) return; @@ -277,6 +295,7 @@ namespace osu.Game.Screens.Play if (state?.BeatmapID == null) { beatmapPanelContainer.Clear(); + onlineBeatmap = null; return; } @@ -286,12 +305,28 @@ namespace osu.Game.Screens.Play if (state != this.state) return; - beatmapPanelContainer.Child = new GridBeatmapPanel(res.ToBeatmapSet(rulesets)); + onlineBeatmap = res.ToBeatmapSet(rulesets); + beatmapPanelContainer.Child = new GridBeatmapPanel(onlineBeatmap); + checkForAutomaticDownload(); }); api.Queue(req); } + private void checkForAutomaticDownload() + { + if (onlineBeatmap == null) + return; + + if (!automaticDownload.Current.Value) + return; + + if (beatmaps.IsAvailableLocally(onlineBeatmap)) + return; + + beatmaps.Download(onlineBeatmap); + } + protected override void Dispose(bool isDisposing) { base.Dispose(isDisposing); From dd2f44f393e6cf16ef4f1a40f6b74ff4174219d9 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 19:39:38 +0900 Subject: [PATCH 12/53] Add basic "currently watching" text to player to signify that spectator is active --- osu.Game/Screens/Play/SpectatorPlayer.cs | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index 7c2f8742ba..2f98647d3e 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -3,8 +3,11 @@ using System.Linq; using osu.Framework.Allocation; +using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Game.Beatmaps; +using osu.Game.Graphics; +using osu.Game.Graphics.Sprites; using osu.Game.Online.Spectator; using osu.Game.Scoring; using osu.Game.Screens.Ranking; @@ -35,6 +38,15 @@ namespace osu.Game.Screens.Play private void load() { spectatorStreaming.OnUserBeganPlaying += userBeganPlaying; + + AddInternal(new OsuSpriteText + { + Text = $"Watching {score.ScoreInfo.User.Username} playing live!", + Font = OsuFont.Default.With(size: 30), + Y = 100, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, + }); } protected override void PrepareReplay() From bca317b1518a2f67dc7592736b36552f1bda600d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 28 Oct 2020 23:43:16 +0900 Subject: [PATCH 13/53] Remove excess using statement --- osu.Game/Screens/Play/Spectator.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/Spectator.cs b/osu.Game/Screens/Play/Spectator.cs index 34e7bb1bda..2c8b5c4cad 100644 --- a/osu.Game/Screens/Play/Spectator.cs +++ b/osu.Game/Screens/Play/Spectator.cs @@ -7,7 +7,6 @@ using System.Linq; using JetBrains.Annotations; 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; From c9b96a18294b0143a38407951b15072a7cd8f9ff Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Nov 2020 15:52:57 +0900 Subject: [PATCH 14/53] Disable spectator streaming connection logic on iOS to avoid startup crash Due to an [issue](https://github.com/dotnet/runtime/issues/35988) at xamarin's end (which *should* have been fixed via [this pr](https://github.com/xamarin/xamarin-macios/pull/8615) but still fails on latest preview release) we can't support this just yet. --- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index cb170ad298..125831035a 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -10,6 +10,7 @@ using JetBrains.Annotations; using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; +using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -101,6 +102,10 @@ namespace osu.Game.Online.Spectator private async Task connect() { + if (RuntimeInfo.OS == RuntimeInfo.Platform.iOS) + // disabled for now (see https://github.com/dotnet/runtime/issues/35988) + return; + if (connection != null) return; From 4e17634ee27ab7dfc33753c1750d8f91e6e2b206 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Nov 2020 17:31:04 +0900 Subject: [PATCH 15/53] Add (temporary) local user cache to avoid re-querying API each display --- .../Dashboard/CurrentlyPlayingDisplay.cs | 25 ++++++++++++++----- 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index d71e582c05..b461da4476 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.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 System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; @@ -43,6 +44,9 @@ namespace osu.Game.Overlays.Dashboard [Resolved] private IAPIProvider api { get; set; } + // temporary, should be game-global but i don't want to add more manager classes for now. + private static readonly Dictionary user_cache = new Dictionary(); + protected override void LoadComplete() { base.LoadComplete(); @@ -53,15 +57,24 @@ namespace osu.Game.Overlays.Dashboard switch (e.Action) { case NotifyCollectionChangedAction.Add: - foreach (var u in e.NewItems.OfType()) + foreach (int userId in e.NewItems.OfType()) { - var request = new GetUserRequest(u); - request.Success += user => Schedule(() => + if (user_cache.TryGetValue(userId, out var user)) { - if (playingUsers.Contains((int)user.Id)) - userFlow.Add(createUserPanel(user)); - }); + addUser(user); + continue; + } + + var request = new GetUserRequest(userId); + request.Success += u => Schedule(() => addUser(u)); api.Queue(request); + + void addUser(User u) + { + user_cache[userId] = u; + if (playingUsers.Contains(userId)) + userFlow.Add(createUserPanel(u)); + } } break; From 5cbfaf3589413c5be4292f97dd91c78b13059534 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Nov 2020 20:19:38 +0900 Subject: [PATCH 16/53] Revert "Add (temporary) local user cache to avoid re-querying API each display" This reverts commit 4e17634ee27ab7dfc33753c1750d8f91e6e2b206. --- .../Dashboard/CurrentlyPlayingDisplay.cs | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index b461da4476..d71e582c05 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.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.Collections.Generic; using System.Collections.Specialized; using System.Linq; using osu.Framework.Allocation; @@ -44,9 +43,6 @@ namespace osu.Game.Overlays.Dashboard [Resolved] private IAPIProvider api { get; set; } - // temporary, should be game-global but i don't want to add more manager classes for now. - private static readonly Dictionary user_cache = new Dictionary(); - protected override void LoadComplete() { base.LoadComplete(); @@ -57,24 +53,15 @@ namespace osu.Game.Overlays.Dashboard switch (e.Action) { case NotifyCollectionChangedAction.Add: - foreach (int userId in e.NewItems.OfType()) + foreach (var u in e.NewItems.OfType()) { - if (user_cache.TryGetValue(userId, out var user)) + var request = new GetUserRequest(u); + request.Success += user => Schedule(() => { - addUser(user); - continue; - } - - var request = new GetUserRequest(userId); - request.Success += u => Schedule(() => addUser(u)); + if (playingUsers.Contains((int)user.Id)) + userFlow.Add(createUserPanel(user)); + }); api.Queue(request); - - void addUser(User u) - { - user_cache[userId] = u; - if (playingUsers.Contains(userId)) - userFlow.Add(createUserPanel(u)); - } } break; From c6de0544d2920440ae7aa2cc9b4f5cfed8f23c11 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Nov 2020 20:21:23 +0900 Subject: [PATCH 17/53] Disable display for not --- osu.Game/Overlays/DashboardOverlay.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Overlays/DashboardOverlay.cs b/osu.Game/Overlays/DashboardOverlay.cs index 04defce636..787a4985d7 100644 --- a/osu.Game/Overlays/DashboardOverlay.cs +++ b/osu.Game/Overlays/DashboardOverlay.cs @@ -131,7 +131,8 @@ namespace osu.Game.Overlays break; case DashboardOverlayTabs.CurrentlyPlaying: - loadDisplay(new CurrentlyPlayingDisplay()); + //todo: enable once caching logic is better + //loadDisplay(new CurrentlyPlayingDisplay()); break; default: From ed30756c199989f8547dd3d9350581e417ab66ab Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Nov 2020 20:41:14 +0900 Subject: [PATCH 18/53] Add test coverage for new display (and remove live version for now) --- .../Visual/Gameplay/TestSceneSpectator.cs | 9 +++ .../TestSceneCurrentlyPlayingDisplay.cs | 64 +++++++++++++++++++ .../Spectator/SpectatorStreamingClient.cs | 4 +- .../Dashboard/CurrentlyPlayingDisplay.cs | 4 +- 4 files changed, 77 insertions(+), 4 deletions(-) create mode 100644 osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index a4df450db9..558778c918 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -3,8 +3,10 @@ using System.Collections.Generic; using System.Linq; +using System.Threading.Tasks; using NUnit.Framework; using osu.Framework.Allocation; +using osu.Framework.Bindables; using osu.Framework.Graphics; using osu.Framework.Screens; using osu.Framework.Testing; @@ -231,8 +233,15 @@ namespace osu.Game.Tests.Visual.Gameplay { public readonly User StreamingUser = new User { Id = 1234, Username = "Test user" }; + public new BindableList PlayingUsers => (BindableList)base.PlayingUsers; + private int beatmapId; + protected override Task Connect() + { + return Task.CompletedTask; + } + public void StartPlay(int beatmapId) { this.beatmapId = beatmapId; diff --git a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs new file mode 100644 index 0000000000..137d0c20a3 --- /dev/null +++ b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs @@ -0,0 +1,64 @@ +// 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.Framework.Allocation; +using osu.Framework.Graphics; +using osu.Framework.Testing; +using osu.Game.Online.API; +using osu.Game.Online.API.Requests; +using osu.Game.Online.Spectator; +using osu.Game.Overlays.Dashboard; +using osu.Game.Tests.Visual.Gameplay; +using osu.Game.Users; + +namespace osu.Game.Tests.Visual.Online +{ + public class TestSceneCurrentlyPlayingDisplay : OsuTestScene + { + [Cached(typeof(SpectatorStreamingClient))] + private TestSceneSpectator.TestSpectatorStreamingClient testSpectatorStreamingClient = new TestSceneSpectator.TestSpectatorStreamingClient(); + + private CurrentlyPlayingDisplay currentlyPlaying; + + private DummyAPIAccess dummyAPI => (DummyAPIAccess)API; + + [SetUpSteps] + public void SetUpSteps() + { + AddStep("register request handling", () => ((DummyAPIAccess)API).HandleRequest = req => + { + switch (req) + { + case GetUserRequest cRequest: + cRequest.TriggerSuccess(new User { Username = "peppy", Id = 2 }); + break; + } + }); + + AddStep("add streaming client", () => + { + Remove(testSpectatorStreamingClient); + + Children = new Drawable[] + { + testSpectatorStreamingClient, + currentlyPlaying = new CurrentlyPlayingDisplay + { + RelativeSizeAxes = Axes.Both, + } + }; + }); + } + + [Test] + public void TestBasicDisplay() + { + AddStep("Add playing user", () => testSpectatorStreamingClient.PlayingUsers.Add(2)); + AddUntilStep("Panel loaded", () => currentlyPlaying.ChildrenOfType()?.FirstOrDefault()?.User.Id == 2); + AddStep("Remove playing user", () => testSpectatorStreamingClient.PlayingUsers.Remove(2)); + AddUntilStep("Panel no longer present", () => !currentlyPlaying.ChildrenOfType().Any()); + } + } +} diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index cb170ad298..c07c00f3ba 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -92,14 +92,14 @@ namespace osu.Game.Online.Spectator break; case APIState.Online: - Task.Run(connect); + Task.Run(Connect); break; } } private const string endpoint = "https://spectator.ppy.sh/spectator"; - private async Task connect() + protected virtual async Task Connect() { if (connection != null) return; diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index d71e582c05..cb88065ec6 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -78,7 +78,7 @@ namespace osu.Game.Overlays.Dashboard }), true); } - [Resolved] + [Resolved(canBeNull: true)] private OsuGame game { get; set; } private UserPanel createUserPanel(User user) @@ -89,7 +89,7 @@ namespace osu.Game.Overlays.Dashboard panel.Origin = Anchor.TopCentre; panel.Width = 290; panel.ShowProfileOnClick = false; - panel.Action = () => game.PerformFromScreen(s => s.Push(new Spectator(user))); + panel.Action = () => game?.PerformFromScreen(s => s.Push(new Spectator(user))); }); } } From c1d9a0c92c1c33a229a4df1a28865b2396a3412d Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Nov 2020 21:09:47 +0900 Subject: [PATCH 19/53] Move click action out of user panel --- .../Visual/Gameplay/TestSceneSpectator.cs | 2 +- .../Dashboard/CurrentlyPlayingDisplay.cs | 57 +++++++++++++++---- osu.Game/Users/UserPanel.cs | 9 +-- 3 files changed, 52 insertions(+), 16 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs index 558778c918..df4b85b37a 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectator.cs @@ -229,7 +229,7 @@ namespace osu.Game.Tests.Visual.Gameplay AddUntilStep("wait for screen load", () => spectatorScreen.LoadState == LoadState.Loaded); } - internal class TestSpectatorStreamingClient : SpectatorStreamingClient + public class TestSpectatorStreamingClient : SpectatorStreamingClient { public readonly User StreamingUser = new User { Id = 1234, Username = "Test user" }; diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index cb88065ec6..8832cb52ea 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -11,6 +11,7 @@ using osu.Framework.Screens; using osu.Game.Online.API; using osu.Game.Online.API.Requests; using osu.Game.Online.Spectator; +using osu.Game.Screens.Multi.Match.Components; using osu.Game.Screens.Play; using osu.Game.Users; using osuTK; @@ -21,7 +22,7 @@ namespace osu.Game.Overlays.Dashboard { private IBindableList playingUsers; - private FillFlowContainer userFlow; + private FillFlowContainer userFlow; [Resolved] private SpectatorStreamingClient spectatorStreaming { get; set; } @@ -32,7 +33,7 @@ namespace osu.Game.Overlays.Dashboard RelativeSizeAxes = Axes.X; AutoSizeAxes = Axes.Y; - InternalChild = userFlow = new FillFlowContainer + InternalChild = userFlow = new FillFlowContainer { RelativeSizeAxes = Axes.X, AutoSizeAxes = Axes.Y, @@ -78,19 +79,53 @@ namespace osu.Game.Overlays.Dashboard }), true); } - [Resolved(canBeNull: true)] - private OsuGame game { get; set; } - - private UserPanel createUserPanel(User user) - { - return new UserGridPanel(user).With(panel => + private PlayingUserPanel createUserPanel(User user) => + new PlayingUserPanel(user).With(panel => { panel.Anchor = Anchor.TopCentre; panel.Origin = Anchor.TopCentre; - panel.Width = 290; - panel.ShowProfileOnClick = false; - panel.Action = () => game?.PerformFromScreen(s => s.Push(new Spectator(user))); }); + + private class PlayingUserPanel : CompositeDrawable + { + public readonly User User; + + [Resolved(canBeNull: true)] + private OsuGame game { get; set; } + + public PlayingUserPanel(User user) + { + User = user; + + AutoSizeAxes = Axes.Both; + + InternalChildren = new Drawable[] + { + new FillFlowContainer + { + AutoSizeAxes = Axes.Both, + Direction = FillDirection.Vertical, + Spacing = new Vector2(2), + Children = new Drawable[] + { + new UserGridPanel(user) + { + Width = 290, + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + }, + new PurpleTriangleButton + { + Width = 290, + Text = "Watch", + Anchor = Anchor.Centre, + Origin = Anchor.Centre, + Action = () => game?.PerformFromScreen(s => s.Push(new Spectator(user))) + } + } + }, + }; + } } } } diff --git a/osu.Game/Users/UserPanel.cs b/osu.Game/Users/UserPanel.cs index e97ff4287f..0981136dba 100644 --- a/osu.Game/Users/UserPanel.cs +++ b/osu.Game/Users/UserPanel.cs @@ -20,10 +20,12 @@ namespace osu.Game.Users { public readonly User User; + /// + /// Perform an action in addition to showing the user's profile. + /// This should be used to perform auxiliary tasks and not as a primary action for clicking a user panel (to maintain a consistent UX). + /// public new Action Action; - public bool ShowProfileOnClick = true; - protected Action ViewProfile { get; private set; } protected Drawable Background { get; private set; } @@ -70,8 +72,7 @@ namespace osu.Game.Users base.Action = ViewProfile = () => { Action?.Invoke(); - if (ShowProfileOnClick) - profileOverlay?.ShowUser(User); + profileOverlay?.ShowUser(User); }; } From c3d3856a64004bb41cc19065e0d0bf57c9408a67 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Mon, 2 Nov 2020 23:51:01 +0900 Subject: [PATCH 20/53] Fix looping mode not being set on first track after entering song select Closes #10656. --- osu.Game/Screens/Select/SongSelect.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index def620462f..82a2cd790a 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -519,6 +519,7 @@ namespace osu.Game.Screens.Select ModSelect.SelectedMods.BindTo(selectedMods); + music.CurrentTrack.Looping = true; music.TrackChanged += ensureTrackLooping; } From d5c95a8b46a21d69d71dbeb4e3b530f023968e49 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Nov 2020 00:45:55 +0900 Subject: [PATCH 21/53] Centralise into methods and add assertions for safety --- osu.Game/Screens/Select/SongSelect.cs | 32 ++++++++++++++++++++------- 1 file changed, 24 insertions(+), 8 deletions(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 82a2cd790a..975431fb62 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -37,6 +37,7 @@ using osu.Framework.Input.Bindings; using osu.Game.Collections; using osu.Game.Graphics.UserInterface; using osu.Game.Scoring; +using System.Diagnostics; namespace osu.Game.Screens.Select { @@ -519,8 +520,7 @@ namespace osu.Game.Screens.Select ModSelect.SelectedMods.BindTo(selectedMods); - music.CurrentTrack.Looping = true; - music.TrackChanged += ensureTrackLooping; + beginLooping(); } private const double logo_transition = 250; @@ -571,8 +571,7 @@ namespace osu.Game.Screens.Select BeatmapDetails.Refresh(); - music.CurrentTrack.Looping = true; - music.TrackChanged += ensureTrackLooping; + beginLooping(); music.ResetTrackAdjustments(); if (Beatmap != null && !Beatmap.Value.BeatmapSetInfo.DeletePending) @@ -598,8 +597,7 @@ namespace osu.Game.Screens.Select BeatmapOptions.Hide(); - music.CurrentTrack.Looping = false; - music.TrackChanged -= ensureTrackLooping; + endLooping(); this.ScaleTo(1.1f, 250, Easing.InSine); @@ -620,12 +618,30 @@ namespace osu.Game.Screens.Select FilterControl.Deactivate(); - music.CurrentTrack.Looping = false; - music.TrackChanged -= ensureTrackLooping; + endLooping(); return false; } + private bool isHandlingLooping; + + private void beginLooping() + { + Debug.Assert(!isHandlingLooping); + + music.CurrentTrack.Looping = isHandlingLooping = true; + + music.TrackChanged += ensureTrackLooping; + } + + private void endLooping() + { + Debug.Assert(isHandlingLooping); + music.CurrentTrack.Looping = isHandlingLooping = false; + + music.TrackChanged -= ensureTrackLooping; + } + private void ensureTrackLooping(WorkingBeatmap beatmap, TrackChangeDirection changeDirection) => music.CurrentTrack.Looping = true; From 71c04472fa99c5760f19ba2dcf71dd2727433081 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Nov 2020 14:21:19 +0900 Subject: [PATCH 22/53] Fix osu!catch replay conversion applying left movements to wrong frame --- osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs index 7efd832f62..1a80adb584 100644 --- a/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs +++ b/osu.Game.Rulesets.Catch/Replays/CatchReplayFrame.cs @@ -52,7 +52,7 @@ namespace osu.Game.Rulesets.Catch.Replays if (Position > lastCatchFrame.Position) lastCatchFrame.Actions.Add(CatchAction.MoveRight); else if (Position < lastCatchFrame.Position) - Actions.Add(CatchAction.MoveLeft); + lastCatchFrame.Actions.Add(CatchAction.MoveLeft); } } From 34aa59f237a3509d0f9e29394e9497e2d5339801 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Nov 2020 14:58:55 +0900 Subject: [PATCH 23/53] Fix editor clock not always remaining stopped when dragging timeline Closes https://github.com/ppy/osu/issues/10461. --- .../Screens/Edit/Compose/Components/Timeline/Timeline.cs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 9aff4ddf8f..f6b2f99c55 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -165,6 +165,11 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline if (!track.IsLoaded || track.Length == 0) return; + // covers the case where the user starts playback after a drag is in progress. + // we want to ensure the clock is always stopped during drags to avoid weird audio playback. + if (handlingDragInput) + editorClock.Stop(); + ScrollTo((float)(editorClock.CurrentTime / track.Length) * Content.DrawWidth, false); } From 6bf35d5767fd880d841d4f387f164f2fc30a373a Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Nov 2020 16:00:07 +0900 Subject: [PATCH 24/53] Fix editor menu not supporting stateful item drawables --- .../Edit/Components/Menus/EditorMenuBar.cs | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs index afd9e3d760..c6787a1fb1 100644 --- a/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs +++ b/osu.Game/Screens/Edit/Components/Menus/EditorMenuBar.cs @@ -163,30 +163,27 @@ namespace osu.Game.Screens.Edit.Components.Menus protected override Framework.Graphics.UserInterface.Menu CreateSubMenu() => new SubMenu(); - protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableSubMenuItem(item); - - private class DrawableSubMenuItem : DrawableOsuMenuItem + protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) { - public DrawableSubMenuItem(MenuItem item) + switch (item) + { + case EditorMenuItemSpacer spacer: + return new DrawableSpacer(spacer); + } + + return base.CreateDrawableMenuItem(item); + } + + private class DrawableSpacer : DrawableOsuMenuItem + { + public DrawableSpacer(MenuItem item) : base(item) { } - protected override bool OnHover(HoverEvent e) - { - if (Item is EditorMenuItemSpacer) - return true; + protected override bool OnHover(HoverEvent e) => true; - return base.OnHover(e); - } - - protected override bool OnClick(ClickEvent e) - { - if (Item is EditorMenuItemSpacer) - return true; - - return base.OnClick(e); - } + protected override bool OnClick(ClickEvent e) => true; } } } From 7e4314684bd4be4494eb5204a9fb57afc5fac2c0 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Nov 2020 16:01:14 +0900 Subject: [PATCH 25/53] Add editor waveform opacity selection to menu --- osu.Game/Configuration/OsuConfigManager.cs | 5 ++- osu.Game/Screens/Edit/Editor.cs | 10 ++++- osu.Game/Screens/Edit/WaveformOpacityMenu.cs | 46 ++++++++++++++++++++ 3 files changed, 59 insertions(+), 2 deletions(-) create mode 100644 osu.Game/Screens/Edit/WaveformOpacityMenu.cs diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs index e0971d238a..26f72d3455 100644 --- a/osu.Game/Configuration/OsuConfigManager.cs +++ b/osu.Game/Configuration/OsuConfigManager.cs @@ -132,6 +132,8 @@ namespace osu.Game.Configuration Set(OsuSetting.MenuBackgroundSource, BackgroundSource.Skin); Set(OsuSetting.SeasonalBackgroundMode, SeasonalBackgroundMode.Sometimes); + + Set(OsuSetting.EditorWaveformOpacity, 1f); } public OsuConfigManager(Storage storage) @@ -241,6 +243,7 @@ namespace osu.Game.Configuration HitLighting, MenuBackgroundSource, GameplayDisableWinKey, - SeasonalBackgroundMode + SeasonalBackgroundMode, + EditorWaveformOpacity, } } diff --git a/osu.Game/Screens/Edit/Editor.cs b/osu.Game/Screens/Edit/Editor.cs index f95c7fe7a6..13d1f378a6 100644 --- a/osu.Game/Screens/Edit/Editor.cs +++ b/osu.Game/Screens/Edit/Editor.cs @@ -20,6 +20,7 @@ using osu.Framework.Platform; using osu.Framework.Screens; using osu.Framework.Timing; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Graphics.Cursor; using osu.Game.Graphics.UserInterface; @@ -103,7 +104,7 @@ namespace osu.Game.Screens.Edit private MusicController music { get; set; } [BackgroundDependencyLoader] - private void load(OsuColour colours, GameHost host) + private void load(OsuColour colours, GameHost host, OsuConfigManager config) { beatDivisor.Value = Beatmap.Value.BeatmapInfo.BeatDivisor; beatDivisor.BindValueChanged(divisor => Beatmap.Value.BeatmapInfo.BeatDivisor = divisor.NewValue); @@ -208,6 +209,13 @@ namespace osu.Game.Screens.Edit copyMenuItem = new EditorMenuItem("Copy", MenuItemType.Standard, Copy), pasteMenuItem = new EditorMenuItem("Paste", MenuItemType.Standard, Paste), } + }, + new MenuItem("View") + { + Items = new[] + { + new WaveformOpacityMenu(config) + } } } } diff --git a/osu.Game/Screens/Edit/WaveformOpacityMenu.cs b/osu.Game/Screens/Edit/WaveformOpacityMenu.cs new file mode 100644 index 0000000000..5d209ae141 --- /dev/null +++ b/osu.Game/Screens/Edit/WaveformOpacityMenu.cs @@ -0,0 +1,46 @@ +// 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.Bindables; +using osu.Framework.Graphics.UserInterface; +using osu.Game.Configuration; +using osu.Game.Graphics.UserInterface; + +namespace osu.Game.Screens.Edit +{ + internal class WaveformOpacityMenu : MenuItem + { + private readonly Bindable waveformOpacity; + + private readonly Dictionary menuItemLookup = new Dictionary(); + + public WaveformOpacityMenu(OsuConfigManager config) + : base("Waveform opacity") + { + Items = new[] + { + createMenuItem(0.25f), + createMenuItem(0.5f), + createMenuItem(0.75f), + createMenuItem(1f), + }; + + waveformOpacity = config.GetBindable(OsuSetting.EditorWaveformOpacity); + waveformOpacity.BindValueChanged(opacity => + { + foreach (var kvp in menuItemLookup) + kvp.Value.State.Value = kvp.Key == opacity.NewValue; + }, true); + } + + private ToggleMenuItem createMenuItem(float opacity) + { + var item = new ToggleMenuItem($"{opacity * 100}%", MenuItemType.Standard, _ => updateOpacity(opacity)); + menuItemLookup[opacity] = item; + return item; + } + + private void updateOpacity(float opacity) => waveformOpacity.Value = opacity; + } +} From 0dcb4ea4413066850eaea9089cc9cb16fcd2813e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Nov 2020 16:07:01 +0900 Subject: [PATCH 26/53] Add handling of opacity to timeline waveform display --- .../Edit/Compose/Components/Timeline/Timeline.cs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs index 9aff4ddf8f..1e374d3103 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/Timeline.cs @@ -11,6 +11,7 @@ using osu.Framework.Graphics.Audio; using osu.Framework.Graphics.Containers; using osu.Framework.Input.Events; using osu.Game.Beatmaps; +using osu.Game.Configuration; using osu.Game.Graphics; using osu.Game.Rulesets.Edit; using osuTK; @@ -67,8 +68,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline private TimelineControlPointDisplay controlPoints; + private Bindable waveformOpacity; + [BackgroundDependencyLoader] - private void load(IBindable beatmap, OsuColour colours) + private void load(IBindable beatmap, OsuColour colours, OsuConfigManager config) { AddRange(new Drawable[] { @@ -95,7 +98,10 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline // We don't want the centre marker to scroll AddInternal(new CentreMarker { Depth = float.MaxValue }); - WaveformVisible.ValueChanged += visible => waveform.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint); + waveformOpacity = config.GetBindable(OsuSetting.EditorWaveformOpacity); + waveformOpacity.BindValueChanged(_ => updateWaveformOpacity(), true); + + WaveformVisible.ValueChanged += _ => updateWaveformOpacity(); ControlPointsVisible.ValueChanged += visible => controlPoints.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint); TicksVisible.ValueChanged += visible => ticks.FadeTo(visible.NewValue ? 1 : 0, 200, Easing.OutQuint); @@ -115,6 +121,9 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline }, true); } + private void updateWaveformOpacity() => + waveform.FadeTo(WaveformVisible.Value ? waveformOpacity.Value : 0, 200, Easing.OutQuint); + private float getZoomLevelForVisibleMilliseconds(double milliseconds) => Math.Max(1, (float)(track.Length / milliseconds)); protected override void Update() From c2ffc1837905821157a738ca7a31f19b900a5c8f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Nov 2020 16:30:11 +0900 Subject: [PATCH 27/53] Change editor timeline mouse wheel handling to scroll by default (and zoom with alt held) --- .../Timeline/ZoomableScrollContainer.cs | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index f90658e99c..9094b6bc98 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -113,19 +113,18 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override bool OnScroll(ScrollEvent e) { - if (e.IsPrecise) + if (e.CurrentState.Keyboard.AltPressed) { - // can't handle scroll correctly while playing. - // the editor will handle this case for us. - if (editorClock?.IsRunning == true) - return false; - - // for now, we don't support zoom when using a precision scroll device. this needs gesture support. - return base.OnScroll(e); + // zoom when holding alt. + setZoomTarget(zoomTarget + e.ScrollDelta.Y, zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); } - setZoomTarget(zoomTarget + e.ScrollDelta.Y, zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); - return true; + // can't handle scroll correctly while playing. + // the editor will handle this case for us. + if (editorClock?.IsRunning == true) + return false; + + return base.OnScroll(e); } private void updateZoomedContentWidth() => zoomedContent.Width = DrawWidth * currentZoom; From df9ff76f23871bfaf991adc2fea9f0086ad9ee50 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Nov 2020 16:49:13 +0900 Subject: [PATCH 28/53] Reduce assert to guard in the outwards direction --- osu.Game/Screens/Select/SongSelect.cs | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Select/SongSelect.cs b/osu.Game/Screens/Select/SongSelect.cs index 975431fb62..b55c0694ef 100644 --- a/osu.Game/Screens/Select/SongSelect.cs +++ b/osu.Game/Screens/Select/SongSelect.cs @@ -636,7 +636,10 @@ namespace osu.Game.Screens.Select private void endLooping() { - Debug.Assert(isHandlingLooping); + // may be called multiple times during screen exit process. + if (!isHandlingLooping) + return; + music.CurrentTrack.Looping = isHandlingLooping = false; music.TrackChanged -= ensureTrackLooping; From ff4dcf065a2da691a6b19aaaecd1a2a211487188 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Nov 2020 17:06:33 +0900 Subject: [PATCH 29/53] Update tests --- .../Visual/Editing/TestSceneZoomableScrollContainer.cs | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs index 082268d824..c8187491b9 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs @@ -7,13 +7,14 @@ using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Shapes; -using osu.Framework.Utils; using osu.Framework.Testing; +using osu.Framework.Utils; using osu.Game.Graphics; using osu.Game.Graphics.Cursor; using osu.Game.Screens.Edit.Compose.Components.Timeline; using osuTK; using osuTK.Graphics; +using osuTK.Input; namespace osu.Game.Tests.Visual.Editing { @@ -88,6 +89,7 @@ namespace osu.Game.Tests.Visual.Editing // Scroll in at 0.25 AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y))); + AddStep("Press alt down", () => InputManager.PressKey(Key.AltLeft)); AddStep("Scroll by 3", () => InputManager.ScrollBy(new Vector2(0, 3))); AddAssert("Box not at 0", () => !Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft)); AddAssert("Box 1/4 at 1/4", () => Precision.AlmostEquals(boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X)); @@ -96,6 +98,7 @@ namespace osu.Game.Tests.Visual.Editing AddStep("Scroll by -3", () => InputManager.ScrollBy(new Vector2(0, -3))); AddAssert("Box at 0", () => Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft)); AddAssert("Box 1/4 at 1/4", () => Precision.AlmostEquals(boxQuad.TopLeft.X + 0.25f * boxQuad.Size.X, scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X)); + AddStep("Release alt", () => InputManager.ReleaseKey(Key.AltLeft)); } [Test] @@ -103,6 +106,8 @@ namespace osu.Game.Tests.Visual.Editing { reset(); + AddStep("Press alt down", () => InputManager.PressKey(Key.AltLeft)); + // Scroll in at 0.25 AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y))); AddStep("Scroll by 1", () => InputManager.ScrollBy(new Vector2(0, 1))); @@ -124,6 +129,8 @@ namespace osu.Game.Tests.Visual.Editing AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y))); AddStep("Scroll by -1", () => InputManager.ScrollBy(new Vector2(0, -1))); AddAssert("Box at 0", () => Precision.AlmostEquals(boxQuad.TopLeft, scrollQuad.TopLeft)); + + AddStep("Release alt", () => InputManager.ReleaseKey(Key.AltLeft)); } private void reset() From ff2b01fa6f6fa33035de9378d20a8f2187d54571 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Nov 2020 17:22:45 +0900 Subject: [PATCH 30/53] Add test coverage of zoom-then-scroll --- .../TestSceneZoomableScrollContainer.cs | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs index c8187491b9..95d11d6909 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneZoomableScrollContainer.cs @@ -101,6 +101,24 @@ namespace osu.Game.Tests.Visual.Editing AddStep("Release alt", () => InputManager.ReleaseKey(Key.AltLeft)); } + [Test] + public void TestMouseZoomInThenScroll() + { + reset(); + + // Scroll in at 0.25 + AddStep("Move mouse to 0.25x", () => InputManager.MoveMouseTo(new Vector2(scrollQuad.TopLeft.X + 0.25f * scrollQuad.Size.X, scrollQuad.Centre.Y))); + AddStep("Press alt down", () => InputManager.PressKey(Key.AltLeft)); + AddStep("Zoom by 3", () => InputManager.ScrollBy(new Vector2(0, 3))); + AddStep("Release alt", () => InputManager.ReleaseKey(Key.AltLeft)); + + AddStep("Scroll far left", () => InputManager.ScrollBy(new Vector2(0, 30))); + AddUntilStep("Scroll is at start", () => Precision.AlmostEquals(scrollQuad.TopLeft.X, boxQuad.TopLeft.X, 1)); + + AddStep("Scroll far right", () => InputManager.ScrollBy(new Vector2(0, -300))); + AddUntilStep("Scroll is at end", () => Precision.AlmostEquals(scrollQuad.TopRight.X, boxQuad.TopRight.X, 1)); + } + [Test] public void TestMouseZoomInTwiceOutTwice() { From 9f8ea93068dc0b53c1390084c95e878703a90036 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Nov 2020 18:45:18 +0900 Subject: [PATCH 31/53] Fix osu!catch banana animation not playing due to incorrect lifetimes Closes #10117. --- .../Objects/Drawables/DrawableCatchHitObject.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs index d03a764bda..ac1f11e09f 100644 --- a/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs +++ b/osu.Game.Rulesets.Catch/Objects/Drawables/DrawableCatchHitObject.cs @@ -62,6 +62,8 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawables public abstract class DrawableCatchHitObject : DrawableHitObject { + protected override double InitialLifetimeOffset => HitObject.TimePreempt; + public virtual bool StaysOnPlate => HitObject.CanBePlated; public float DisplayRadius => DrawSize.X / 2 * Scale.X * HitObject.Scale; From d788931661e6110393f011ca3512582ab168c46c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Nov 2020 19:53:35 +0900 Subject: [PATCH 32/53] Fix LoadComponentAsync calls potentially occuring after beatmap wedge disposal As seen in https://ci.appveyor.com/project/peppy/osu/builds/36109658/tests. Also adds cancellation logic for good measure. --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index bdfcc2fd96..ad3bce8968 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Threading; using JetBrains.Annotations; using osuTK; using osuTK.Graphics; @@ -85,6 +86,8 @@ namespace osu.Game.Screens.Select private WorkingBeatmap beatmap; + private CancellationTokenSource cancellationSource; + public WorkingBeatmap Beatmap { get => beatmap; @@ -93,10 +96,12 @@ namespace osu.Game.Screens.Select if (beatmap == value) return; beatmap = value; + cancellationSource?.Cancel(); + cancellationSource = new CancellationTokenSource(); beatmapDifficulty?.UnbindAll(); - beatmapDifficulty = difficultyManager.GetBindableDifficulty(beatmap.BeatmapInfo); - beatmapDifficulty.BindValueChanged(_ => updateDisplay()); + beatmapDifficulty = difficultyManager.GetBindableDifficulty(beatmap.BeatmapInfo, cancellationSource.Token); + beatmapDifficulty.BindValueChanged(_ => Schedule(updateDisplay)); updateDisplay(); } @@ -137,6 +142,12 @@ namespace osu.Game.Screens.Select }); } + protected override void Dispose(bool isDisposing) + { + base.Dispose(isDisposing); + cancellationSource?.Cancel(); + } + public class BufferedWedgeInfo : BufferedContainer { public OsuSpriteText VersionLabel { get; private set; } From d4f8c63f9ee1de0cfcfbaa8b6b6206e42e16ee64 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Nov 2020 19:59:06 +0900 Subject: [PATCH 33/53] Fix reference to dummyAPI not using helper property --- .../Visual/Online/TestSceneCurrentlyPlayingDisplay.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs index 137d0c20a3..f85abeb51a 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs @@ -27,7 +27,7 @@ namespace osu.Game.Tests.Visual.Online [SetUpSteps] public void SetUpSteps() { - AddStep("register request handling", () => ((DummyAPIAccess)API).HandleRequest = req => + AddStep("register request handling", () => dummyAPI.HandleRequest = req => { switch (req) { From aaffd59dfe4a43ebe1d75802355f0ca6453e07f1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Nov 2020 20:02:02 +0900 Subject: [PATCH 34/53] Add test step to reset players (to better allow multiple runs of tests) --- .../Visual/Online/TestSceneCurrentlyPlayingDisplay.cs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs index f85abeb51a..d6fd33bce7 100644 --- a/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs +++ b/osu.Game.Tests/Visual/Online/TestSceneCurrentlyPlayingDisplay.cs @@ -50,6 +50,8 @@ namespace osu.Game.Tests.Visual.Online } }; }); + + AddStep("Reset players", () => testSpectatorStreamingClient.PlayingUsers.Clear()); } [Test] From 3e29e468ea3907bad4af44c08e24ad74c17eeeb2 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Nov 2020 20:06:42 +0900 Subject: [PATCH 35/53] Ensure "start watching" button starts in a disabled state --- osu.Game/Screens/Play/Spectator.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/osu.Game/Screens/Play/Spectator.cs b/osu.Game/Screens/Play/Spectator.cs index 2c8b5c4cad..9ed911efd5 100644 --- a/osu.Game/Screens/Play/Spectator.cs +++ b/osu.Game/Screens/Play/Spectator.cs @@ -165,7 +165,8 @@ namespace osu.Game.Screens.Play Width = 250, Anchor = Anchor.Centre, Origin = Anchor.Centre, - Action = attemptStart + Action = attemptStart, + Enabled = { Value = false } } } } From 1b2bd6a8c9de4abd0539bd217f5db55f65939ce1 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Nov 2020 20:10:13 +0900 Subject: [PATCH 36/53] Remove redundant base call --- osu.Game/Screens/Play/SpectatorPlayer.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/osu.Game/Screens/Play/SpectatorPlayer.cs b/osu.Game/Screens/Play/SpectatorPlayer.cs index fbed84b820..fdf996150f 100644 --- a/osu.Game/Screens/Play/SpectatorPlayer.cs +++ b/osu.Game/Screens/Play/SpectatorPlayer.cs @@ -21,7 +21,6 @@ namespace osu.Game.Screens.Play protected override bool CheckModsAllowFailure() => false; // todo: better support starting mid-way through beatmap public SpectatorPlayer(Score score) - : base(true, true) { this.score = score; } From 83c004bbdefa4cc9bf4eb9dcfff7d91bcaf8187c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Nov 2020 21:10:31 +0900 Subject: [PATCH 37/53] Add key bindings for flip and reverse patterns --- .../Edit/Compose/Components/SelectionBox.cs | 36 +++++++++++++++---- .../Compose/Components/SelectionHandler.cs | 8 ++--- 2 files changed, 33 insertions(+), 11 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index b753c45cca..6367dd6b9b 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -7,17 +7,19 @@ using osu.Framework.Graphics; using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Sprites; +using osu.Framework.Input.Events; using osu.Game.Graphics; using osuTK; +using osuTK.Input; namespace osu.Game.Screens.Edit.Compose.Components { public class SelectionBox : CompositeDrawable { - public Action OnRotation; - public Action OnScale; - public Action OnFlip; - public Action OnReverse; + public Func OnRotation; + public Func OnScale; + public Func OnFlip; + public Func OnReverse; public Action OperationStarted; public Action OperationEnded; @@ -105,6 +107,26 @@ namespace osu.Game.Screens.Edit.Compose.Components recreate(); } + protected override bool OnKeyDown(KeyDownEvent e) + { + if (e.Repeat || !e.ControlPressed) + return false; + + switch (e.Key) + { + case Key.G: + return OnReverse?.Invoke() == true; + + case Key.H: + return OnFlip?.Invoke(Direction.Horizontal) == true; + + case Key.J: + return OnFlip?.Invoke(Direction.Vertical) == true; + } + + return base.OnKeyDown(e); + } + private void recreate() { if (LoadState < LoadState.Loading) @@ -143,7 +165,7 @@ namespace osu.Game.Screens.Edit.Compose.Components if (CanScaleX && CanScaleY) addFullScaleComponents(); if (CanScaleY) addYScaleComponents(); if (CanRotate) addRotationComponents(); - if (CanReverse) addButton(FontAwesome.Solid.Backward, "Reverse pattern", () => OnReverse?.Invoke()); + if (CanReverse) addButton(FontAwesome.Solid.Backward, "Reverse pattern (Ctrl-G)", () => OnReverse?.Invoke()); } private void addRotationComponents() @@ -178,7 +200,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void addYScaleComponents() { - addButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically", () => OnFlip?.Invoke(Direction.Vertical)); + addButton(FontAwesome.Solid.ArrowsAltV, "Flip vertically (Ctrl-J)", () => OnFlip?.Invoke(Direction.Vertical)); addDragHandle(Anchor.TopCentre); addDragHandle(Anchor.BottomCentre); @@ -194,7 +216,7 @@ namespace osu.Game.Screens.Edit.Compose.Components private void addXScaleComponents() { - addButton(FontAwesome.Solid.ArrowsAltH, "Flip horizontally", () => OnFlip?.Invoke(Direction.Horizontal)); + addButton(FontAwesome.Solid.ArrowsAltH, "Flip horizontally (Ctrl-H)", () => OnFlip?.Invoke(Direction.Horizontal)); addDragHandle(Anchor.CentreLeft); addDragHandle(Anchor.CentreRight); diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs index c2441b31a9..406ca07185 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionHandler.cs @@ -99,10 +99,10 @@ namespace osu.Game.Screens.Edit.Compose.Components OperationStarted = OnOperationBegan, OperationEnded = OnOperationEnded, - OnRotation = angle => HandleRotation(angle), - OnScale = (amount, anchor) => HandleScale(amount, anchor), - OnFlip = direction => HandleFlip(direction), - OnReverse = () => HandleReverse(), + OnRotation = HandleRotation, + OnScale = HandleScale, + OnFlip = HandleFlip, + OnReverse = HandleReverse, }; /// From d45520be5eb2a7008a815daefea9474563b9d247 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Tue, 3 Nov 2020 21:23:10 +0900 Subject: [PATCH 38/53] Fix incorrect return types on test methods --- osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs index da98a7a024..cf5f1b8818 100644 --- a/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs +++ b/osu.Game.Tests/Visual/Editing/TestSceneComposeSelectBox.cs @@ -41,7 +41,7 @@ namespace osu.Game.Tests.Visual.Editing AddToggleStep("toggle y", state => selectionBox.CanScaleY = state); } - private void handleScale(Vector2 amount, Anchor reference) + private bool handleScale(Vector2 amount, Anchor reference) { if ((reference & Anchor.y1) == 0) { @@ -58,12 +58,15 @@ namespace osu.Game.Tests.Visual.Editing selectionArea.X += amount.X; selectionArea.Width += directionX * amount.X; } + + return true; } - private void handleRotation(float angle) + private bool handleRotation(float angle) { // kinda silly and wrong, but just showing that the drag handles work. selectionArea.Rotation += angle; + return true; } } } From 18428dff8ee20a62ec8b720357d4a61e3559a8a2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 3 Nov 2020 18:01:12 +0100 Subject: [PATCH 39/53] Ensure hotkey actions are executable in handler --- osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs index 6367dd6b9b..742d433760 100644 --- a/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs +++ b/osu.Game/Screens/Edit/Compose/Components/SelectionBox.cs @@ -115,13 +115,13 @@ namespace osu.Game.Screens.Edit.Compose.Components switch (e.Key) { case Key.G: - return OnReverse?.Invoke() == true; + return CanReverse && OnReverse?.Invoke() == true; case Key.H: - return OnFlip?.Invoke(Direction.Horizontal) == true; + return CanScaleX && OnFlip?.Invoke(Direction.Horizontal) == true; case Key.J: - return OnFlip?.Invoke(Direction.Vertical) == true; + return CanScaleY && OnFlip?.Invoke(Direction.Vertical) == true; } return base.OnKeyDown(e); From 86d283ebf4439c940a02f5fd889ff1f27d406447 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 3 Nov 2020 19:03:48 +0100 Subject: [PATCH 40/53] Adjust layout slightly to avoid specifying width twice --- osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index 8832cb52ea..79af81cfac 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -103,20 +103,21 @@ namespace osu.Game.Overlays.Dashboard { new FillFlowContainer { - AutoSizeAxes = Axes.Both, + AutoSizeAxes = Axes.Y, Direction = FillDirection.Vertical, Spacing = new Vector2(2), + Width = 290, Children = new Drawable[] { new UserGridPanel(user) { - Width = 290, + RelativeSizeAxes = Axes.X, Anchor = Anchor.Centre, Origin = Anchor.Centre, }, new PurpleTriangleButton { - Width = 290, + RelativeSizeAxes = Axes.X, Text = "Watch", Anchor = Anchor.Centre, Origin = Anchor.Centre, From 211510fe9aa0eff1243249cb3aeb4b2cf8519f59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 3 Nov 2020 19:12:03 +0100 Subject: [PATCH 41/53] Fix undesirable vertical spacing in currently playing display --- osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index 79af81cfac..34444c2fa5 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -112,15 +112,15 @@ namespace osu.Game.Overlays.Dashboard new UserGridPanel(user) { RelativeSizeAxes = Axes.X, - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, }, new PurpleTriangleButton { RelativeSizeAxes = Axes.X, Text = "Watch", - Anchor = Anchor.Centre, - Origin = Anchor.Centre, + Anchor = Anchor.TopCentre, + Origin = Anchor.TopCentre, Action = () => game?.PerformFromScreen(s => s.Push(new Spectator(user))) } } From 4b528e80d012d96e5e81a693222fc60761af947d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 3 Nov 2020 21:49:04 +0100 Subject: [PATCH 42/53] Use AltPressed shorthand --- .../Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 9094b6bc98..548bb66198 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -113,7 +113,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline protected override bool OnScroll(ScrollEvent e) { - if (e.CurrentState.Keyboard.AltPressed) + if (e.AltPressed) { // zoom when holding alt. setZoomTarget(zoomTarget + e.ScrollDelta.Y, zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); From ddf0d75c38d44bab54db52d69309fe6ca3d7c957 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bart=C5=82omiej=20Dach?= Date: Tue, 3 Nov 2020 21:49:21 +0100 Subject: [PATCH 43/53] Don't fall through to seek if handling zoom --- .../Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs index 548bb66198..f10eb0d284 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/ZoomableScrollContainer.cs @@ -117,6 +117,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { // zoom when holding alt. setZoomTarget(zoomTarget + e.ScrollDelta.Y, zoomedContent.ToLocalSpace(e.ScreenSpaceMousePosition).X); + return true; } // can't handle scroll correctly while playing. From cfe32182397f79efd8ca30fbd02724c718f16b2c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Nov 2020 10:21:42 +0900 Subject: [PATCH 44/53] Move scheduler call to inside method itself for added safety --- osu.Game/Screens/Select/BeatmapInfoWedge.cs | 57 +++++++++++---------- 1 file changed, 31 insertions(+), 26 deletions(-) diff --git a/osu.Game/Screens/Select/BeatmapInfoWedge.cs b/osu.Game/Screens/Select/BeatmapInfoWedge.cs index ad3bce8968..2634f117de 100644 --- a/osu.Game/Screens/Select/BeatmapInfoWedge.cs +++ b/osu.Game/Screens/Select/BeatmapInfoWedge.cs @@ -101,7 +101,7 @@ namespace osu.Game.Screens.Select beatmapDifficulty?.UnbindAll(); beatmapDifficulty = difficultyManager.GetBindableDifficulty(beatmap.BeatmapInfo, cancellationSource.Token); - beatmapDifficulty.BindValueChanged(_ => Schedule(updateDisplay)); + beatmapDifficulty.BindValueChanged(_ => updateDisplay()); updateDisplay(); } @@ -113,33 +113,38 @@ namespace osu.Game.Screens.Select private void updateDisplay() { - void removeOldInfo() - { - State.Value = beatmap == null ? Visibility.Hidden : Visibility.Visible; + Scheduler.AddOnce(perform); - Info?.FadeOut(250); - Info?.Expire(); - Info = null; + void perform() + { + void removeOldInfo() + { + State.Value = beatmap == null ? Visibility.Hidden : Visibility.Visible; + + Info?.FadeOut(250); + Info?.Expire(); + Info = null; + } + + if (beatmap == null) + { + removeOldInfo(); + return; + } + + LoadComponentAsync(loadingInfo = new BufferedWedgeInfo(beatmap, ruleset.Value, beatmapDifficulty.Value) + { + Shear = -Shear, + Depth = Info?.Depth + 1 ?? 0 + }, loaded => + { + // ensure we are the most recent loaded wedge. + if (loaded != loadingInfo) return; + + removeOldInfo(); + Add(Info = loaded); + }); } - - if (beatmap == null) - { - removeOldInfo(); - return; - } - - LoadComponentAsync(loadingInfo = new BufferedWedgeInfo(beatmap, ruleset.Value, beatmapDifficulty.Value) - { - Shear = -Shear, - Depth = Info?.Depth + 1 ?? 0 - }, loaded => - { - // ensure we are the most recent loaded wedge. - if (loaded != loadingInfo) return; - - removeOldInfo(); - Add(Info = loaded); - }); } protected override void Dispose(bool isDisposing) From 1d90f5fc082b636eb4f2a6466deada44bdd16d43 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Nov 2020 14:16:51 +0900 Subject: [PATCH 45/53] Revert "Disable spectator streaming connection logic on iOS to avoid startup crash" This reverts commit c9b96a18294b0143a38407951b15072a7cd8f9ff. --- osu.Game/Online/Spectator/SpectatorStreamingClient.cs | 5 ----- 1 file changed, 5 deletions(-) diff --git a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs index 125831035a..cb170ad298 100644 --- a/osu.Game/Online/Spectator/SpectatorStreamingClient.cs +++ b/osu.Game/Online/Spectator/SpectatorStreamingClient.cs @@ -10,7 +10,6 @@ using JetBrains.Annotations; using Microsoft.AspNetCore.SignalR.Client; using Microsoft.Extensions.DependencyInjection; using Newtonsoft.Json; -using osu.Framework; using osu.Framework.Allocation; using osu.Framework.Bindables; using osu.Framework.Graphics; @@ -102,10 +101,6 @@ namespace osu.Game.Online.Spectator private async Task connect() { - if (RuntimeInfo.OS == RuntimeInfo.Platform.iOS) - // disabled for now (see https://github.com/dotnet/runtime/issues/35988) - return; - if (connection != null) return; From 5316d4c3696d7939f47b5364286e3e9c0c463dd7 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Nov 2020 14:16:34 +0900 Subject: [PATCH 46/53] Force using older signalr version for iOS --- osu.iOS/osu.iOS.csproj | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/osu.iOS/osu.iOS.csproj b/osu.iOS/osu.iOS.csproj index 1e9a21865d..2c6489a5d3 100644 --- a/osu.iOS/osu.iOS.csproj +++ b/osu.iOS/osu.iOS.csproj @@ -116,5 +116,9 @@ false + + + + From 6c4acb4d11c404602085f5e9a4132da44ff37faf Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Nov 2020 14:45:13 +0900 Subject: [PATCH 47/53] Move to props and ignore downgrade warning --- osu.iOS.props | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/osu.iOS.props b/osu.iOS.props index 76c496cd2d..61bb690225 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -73,6 +73,14 @@ + + + NU1605 + + + + + From d1829820e09559791f9436ac6c1c410faf47893c Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Nov 2020 14:53:29 +0900 Subject: [PATCH 48/53] Remove local changes from csproj --- osu.iOS/osu.iOS.csproj | 4 ---- 1 file changed, 4 deletions(-) diff --git a/osu.iOS/osu.iOS.csproj b/osu.iOS/osu.iOS.csproj index 2c6489a5d3..1e9a21865d 100644 --- a/osu.iOS/osu.iOS.csproj +++ b/osu.iOS/osu.iOS.csproj @@ -116,9 +116,5 @@ false - - - - From 7d0a7f170c388e90cf0f4f004fbd260e02499e92 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Nov 2020 14:57:47 +0900 Subject: [PATCH 49/53] Avoid overwriting inherited nowarns Co-authored-by: Dan Balasescu --- osu.iOS.props | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/osu.iOS.props b/osu.iOS.props index 61bb690225..40ecfffcca 100644 --- a/osu.iOS.props +++ b/osu.iOS.props @@ -75,7 +75,7 @@ - NU1605 + $(NoWarn);NU1605 From 8b1dd31bb4b6525ed33973a388423095f87c4b7e Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Nov 2020 15:20:41 +0900 Subject: [PATCH 50/53] Add gitignore ruile for new msbuild generated editorconfig file --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitignore b/.gitignore index 732b171f69..d122d25054 100644 --- a/.gitignore +++ b/.gitignore @@ -334,3 +334,5 @@ inspectcode # BenchmarkDotNet /BenchmarkDotNet.Artifacts + +*.GeneratedMSBuildEditorConfig.editorconfig From 9343f84a6881111986abf1835a1d241db0adda96 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Nov 2020 15:21:20 +0900 Subject: [PATCH 51/53] Add IBindableList.GetBoudCopy to banned symbols for now --- CodeAnalysis/BannedSymbols.txt | 1 + 1 file changed, 1 insertion(+) diff --git a/CodeAnalysis/BannedSymbols.txt b/CodeAnalysis/BannedSymbols.txt index e34626a59e..47839608c9 100644 --- a/CodeAnalysis/BannedSymbols.txt +++ b/CodeAnalysis/BannedSymbols.txt @@ -4,5 +4,6 @@ M:System.ValueType.Equals(System.Object)~System.Boolean;Don't use object.Equals( M:System.Nullable`1.Equals(System.Object)~System.Boolean;Use == instead. T:System.IComparable;Don't use non-generic IComparable. Use generic version instead. M:osu.Framework.Graphics.Sprites.SpriteText.#ctor;Use OsuSpriteText. +M:osu.Framework.Bindables.IBindableList`1.GetBoundCopy();Fails on iOS. Use manual ctor + BindTo instead. (see https://github.com/mono/mono/issues/19900) T:Microsoft.EntityFrameworkCore.Internal.EnumerableExtensions;Don't use internal extension methods. T:Microsoft.EntityFrameworkCore.Internal.TypeExtensions;Don't use internal extension methods. From 487a5ecdba11f6f2f9aee7e89ccf752bd598bb1f Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Nov 2020 15:29:14 +0900 Subject: [PATCH 52/53] Replace all usage of IBindableList.GetBoundCopy --- osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs | 4 ++-- osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs | 4 ++-- .../Timelines/Summary/Parts/ControlPointPart.cs | 4 ++-- .../Timelines/Summary/Parts/GroupVisualisation.cs | 4 ++-- .../Components/Timeline/TimelineControlPointDisplay.cs | 4 ++-- .../Components/Timeline/TimelineControlPointGroup.cs | 4 ++-- osu.Game/Screens/Edit/Timing/ControlPointTable.cs | 4 ++-- osu.Game/Screens/Edit/Timing/TimingScreen.cs | 9 ++++----- 8 files changed, 18 insertions(+), 19 deletions(-) diff --git a/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs b/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs index 4a3dc58604..e4dc261363 100644 --- a/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs +++ b/osu.Game.Rulesets.Taiko/Audio/DrumSampleContainer.cs @@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Taiko.Audio private readonly ControlPointInfo controlPoints; private readonly Dictionary mappings = new Dictionary(); - private IBindableList samplePoints; + private readonly IBindableList samplePoints = new BindableList(); public DrumSampleContainer(ControlPointInfo controlPoints) { @@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Taiko.Audio [BackgroundDependencyLoader] private void load() { - samplePoints = controlPoints.SamplePoints.GetBoundCopy(); + samplePoints.BindTo(controlPoints.SamplePoints); samplePoints.BindCollectionChanged((_, __) => recreateMappings(), true); } diff --git a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs index 34444c2fa5..697ceacf0a 100644 --- a/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs +++ b/osu.Game/Overlays/Dashboard/CurrentlyPlayingDisplay.cs @@ -20,7 +20,7 @@ namespace osu.Game.Overlays.Dashboard { internal class CurrentlyPlayingDisplay : CompositeDrawable { - private IBindableList playingUsers; + private readonly IBindableList playingUsers = new BindableList(); private FillFlowContainer userFlow; @@ -48,7 +48,7 @@ namespace osu.Game.Overlays.Dashboard { base.LoadComplete(); - playingUsers = spectatorStreaming.PlayingUsers.GetBoundCopy(); + playingUsers.BindTo(spectatorStreaming.PlayingUsers); playingUsers.BindCollectionChanged((sender, e) => Schedule(() => { switch (e.Action) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs index 8c0e31c04c..ba3ac9113e 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/ControlPointPart.cs @@ -14,13 +14,13 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts /// public class ControlPointPart : TimelinePart { - private IBindableList controlPointGroups; + private readonly IBindableList controlPointGroups = new BindableList(); protected override void LoadBeatmap(WorkingBeatmap beatmap) { base.LoadBeatmap(beatmap); - controlPointGroups = beatmap.Beatmap.ControlPointInfo.Groups.GetBoundCopy(); + controlPointGroups.BindTo(beatmap.Beatmap.ControlPointInfo.Groups); controlPointGroups.BindCollectionChanged((sender, args) => { switch (args.Action) diff --git a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs index b9eb53b697..93fe6f9989 100644 --- a/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs +++ b/osu.Game/Screens/Edit/Components/Timelines/Summary/Parts/GroupVisualisation.cs @@ -15,7 +15,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { public readonly ControlPointGroup Group; - private BindableList controlPoints; + private readonly IBindableList controlPoints = new BindableList(); [Resolved] private OsuColour colours { get; set; } @@ -30,7 +30,7 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts { base.LoadComplete(); - controlPoints = (BindableList)Group.ControlPoints.GetBoundCopy(); + controlPoints.BindTo(Group.ControlPoints); controlPoints.BindCollectionChanged((_, __) => { if (controlPoints.Count == 0) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs index 3f13e8e5d4..0da1b43201 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointDisplay.cs @@ -16,7 +16,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline /// public class TimelineControlPointDisplay : TimelinePart { - private IBindableList controlPointGroups; + private readonly IBindableList controlPointGroups = new BindableList(); public TimelineControlPointDisplay() { @@ -27,7 +27,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { base.LoadBeatmap(beatmap); - controlPointGroups = beatmap.Beatmap.ControlPointInfo.Groups.GetBoundCopy(); + controlPointGroups.BindTo(beatmap.Beatmap.ControlPointInfo.Groups); controlPointGroups.BindCollectionChanged((sender, args) => { switch (args.Action) diff --git a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs index e32616a574..fb69f16792 100644 --- a/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs +++ b/osu.Game/Screens/Edit/Compose/Components/Timeline/TimelineControlPointGroup.cs @@ -14,7 +14,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { public readonly ControlPointGroup Group; - private BindableList controlPoints; + private readonly IBindableList controlPoints = new BindableList(); [Resolved] private OsuColour colours { get; set; } @@ -34,7 +34,7 @@ namespace osu.Game.Screens.Edit.Compose.Components.Timeline { base.LoadComplete(); - controlPoints = (BindableList)Group.ControlPoints.GetBoundCopy(); + controlPoints.BindTo(Group.ControlPoints); controlPoints.BindCollectionChanged((_, __) => { ClearInternal(); diff --git a/osu.Game/Screens/Edit/Timing/ControlPointTable.cs b/osu.Game/Screens/Edit/Timing/ControlPointTable.cs index 64f9526816..89d3c36250 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 private class ControlGroupAttributes : CompositeDrawable { - private readonly IBindableList controlPoints; + private readonly IBindableList controlPoints = new BindableList(); private readonly FillFlowContainer fill; @@ -112,7 +112,7 @@ namespace osu.Game.Screens.Edit.Timing Spacing = new Vector2(2) }; - controlPoints = group.ControlPoints.GetBoundCopy(); + controlPoints.BindTo(group.ControlPoints); } [Resolved] diff --git a/osu.Game/Screens/Edit/Timing/TimingScreen.cs b/osu.Game/Screens/Edit/Timing/TimingScreen.cs index f511382cde..09d861522a 100644 --- a/osu.Game/Screens/Edit/Timing/TimingScreen.cs +++ b/osu.Game/Screens/Edit/Timing/TimingScreen.cs @@ -56,7 +56,7 @@ namespace osu.Game.Screens.Edit.Timing private OsuButton deleteButton; private ControlPointTable table; - private IBindableList controlGroups; + private readonly IBindableList controlPointGroups = new BindableList(); [Resolved] private EditorClock clock { get; set; } @@ -124,11 +124,10 @@ namespace osu.Game.Screens.Edit.Timing selectedGroup.BindValueChanged(selected => { deleteButton.Enabled.Value = selected.NewValue != null; }, true); - controlGroups = Beatmap.Value.Beatmap.ControlPointInfo.Groups.GetBoundCopy(); - - controlGroups.BindCollectionChanged((sender, args) => + controlPointGroups.BindTo(Beatmap.Value.Beatmap.ControlPointInfo.Groups); + controlPointGroups.BindCollectionChanged((sender, args) => { - table.ControlGroups = controlGroups; + table.ControlGroups = controlPointGroups; changeHandler.SaveState(); }, true); } From ce1743ab283d80fb1868d00d3c7b4169fdf17569 Mon Sep 17 00:00:00 2001 From: Dean Herbert Date: Wed, 4 Nov 2020 15:35:42 +0900 Subject: [PATCH 53/53] Fix missed usage in test scene --- osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs index 1d8231cce7..35473ee76c 100644 --- a/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs +++ b/osu.Game.Tests/Visual/Gameplay/TestSceneSpectatorPlayback.cs @@ -43,7 +43,7 @@ namespace osu.Game.Tests.Visual.Gameplay private Replay replay; - private IBindableList users; + private readonly IBindableList users = new BindableList(); private TestReplayRecorder recorder; @@ -67,7 +67,7 @@ namespace osu.Game.Tests.Visual.Gameplay { replay = new Replay(); - users = streamingClient.PlayingUsers.GetBoundCopy(); + users.BindTo(streamingClient.PlayingUsers); users.BindCollectionChanged((obj, args) => { switch (args.Action)