mirror of
https://github.com/ppy/osu.git
synced 2025-03-15 15:27:20 +08:00
Merge branch 'master' into fix-replay-export-queued-op
This commit is contained in:
commit
1f7f336fd5
@ -65,6 +65,19 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddStep("clear playing users", () => playingUsers.Clear());
|
||||
}
|
||||
|
||||
[TestCase(1)]
|
||||
[TestCase(4)]
|
||||
public void TestGeneral(int count)
|
||||
{
|
||||
int[] userIds = getPlayerIds(count);
|
||||
|
||||
start(userIds);
|
||||
loadSpectateScreen();
|
||||
|
||||
sendFrames(userIds, 1000);
|
||||
AddWaitStep("wait a bit", 20);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDelayedStart()
|
||||
{
|
||||
@ -88,18 +101,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
AddUntilStep("two players added", () => spectatorScreen.ChildrenOfType<Player>().Count() == 2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGeneral()
|
||||
{
|
||||
int[] userIds = getPlayerIds(4);
|
||||
|
||||
start(userIds);
|
||||
loadSpectateScreen();
|
||||
|
||||
sendFrames(userIds, 1000);
|
||||
AddWaitStep("wait a bit", 20);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSpectatorPlayerInteractiveElementsHidden()
|
||||
{
|
||||
|
@ -170,6 +170,39 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
AddUntilStep("time is correct", () => getEditor().ChildrenOfType<EditorClock>().First().CurrentTime, () => Is.EqualTo(1234));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAttemptGlobalMusicOperationFromEditor()
|
||||
{
|
||||
BeatmapSetInfo beatmapSet = null!;
|
||||
|
||||
AddStep("import test beatmap", () => Game.BeatmapManager.Import(TestResources.GetTestBeatmapForImport()).WaitSafely());
|
||||
AddStep("retrieve beatmap", () => beatmapSet = Game.BeatmapManager.QueryBeatmapSet(set => !set.Protected).AsNonNull().Value.Detach());
|
||||
|
||||
AddStep("present beatmap", () => Game.PresentBeatmap(beatmapSet));
|
||||
AddUntilStep("wait for song select",
|
||||
() => Game.Beatmap.Value.BeatmapSetInfo.Equals(beatmapSet)
|
||||
&& Game.ScreenStack.CurrentScreen is PlaySongSelect songSelect
|
||||
&& songSelect.IsLoaded);
|
||||
|
||||
AddUntilStep("wait for music playing", () => Game.MusicController.IsPlaying);
|
||||
AddStep("user request stop", () => Game.MusicController.Stop(requestedByUser: true));
|
||||
AddUntilStep("wait for music stopped", () => !Game.MusicController.IsPlaying);
|
||||
|
||||
AddStep("open editor", () => ((PlaySongSelect)Game.ScreenStack.CurrentScreen).Edit(beatmapSet.Beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == 0)));
|
||||
AddUntilStep("wait for editor open", () => Game.ScreenStack.CurrentScreen is Editor editor && editor.ReadyForUse);
|
||||
|
||||
AddUntilStep("music still stopped", () => !Game.MusicController.IsPlaying);
|
||||
AddStep("user request play", () => Game.MusicController.Play(requestedByUser: true));
|
||||
AddUntilStep("music still stopped", () => !Game.MusicController.IsPlaying);
|
||||
|
||||
AddStep("exit to song select", () => Game.PerformFromScreen(_ => { }, typeof(PlaySongSelect).Yield()));
|
||||
AddUntilStep("wait for song select", () => Game.ScreenStack.CurrentScreen is PlaySongSelect);
|
||||
|
||||
AddUntilStep("wait for music playing", () => Game.MusicController.IsPlaying);
|
||||
AddStep("user request stop", () => Game.MusicController.Stop(requestedByUser: true));
|
||||
AddUntilStep("wait for music stopped", () => !Game.MusicController.IsPlaying);
|
||||
}
|
||||
|
||||
private EditorBeatmap getEditorBeatmap() => getEditor().ChildrenOfType<EditorBeatmap>().Single();
|
||||
|
||||
private Editor getEditor() => (Editor)Game.ScreenStack.CurrentScreen;
|
||||
|
@ -56,38 +56,38 @@ namespace osu.Game.Tests.Visual
|
||||
public void AllowTrackAdjustmentsTest()
|
||||
{
|
||||
AddStep("push allowing screen", () => stack.Push(loadNewScreen<AllowScreen>()));
|
||||
AddAssert("allows adjustments 1", () => musicController.AllowTrackAdjustments);
|
||||
AddAssert("allows adjustments 1", () => musicController.ApplyModTrackAdjustments);
|
||||
|
||||
AddStep("push inheriting screen", () => stack.Push(loadNewScreen<InheritScreen>()));
|
||||
AddAssert("allows adjustments 2", () => musicController.AllowTrackAdjustments);
|
||||
AddAssert("allows adjustments 2", () => musicController.ApplyModTrackAdjustments);
|
||||
|
||||
AddStep("push disallowing screen", () => stack.Push(loadNewScreen<DisallowScreen>()));
|
||||
AddAssert("disallows adjustments 3", () => !musicController.AllowTrackAdjustments);
|
||||
AddAssert("disallows adjustments 3", () => !musicController.ApplyModTrackAdjustments);
|
||||
|
||||
AddStep("push inheriting screen", () => stack.Push(loadNewScreen<InheritScreen>()));
|
||||
AddAssert("disallows adjustments 4", () => !musicController.AllowTrackAdjustments);
|
||||
AddAssert("disallows adjustments 4", () => !musicController.ApplyModTrackAdjustments);
|
||||
|
||||
AddStep("push inheriting screen", () => stack.Push(loadNewScreen<InheritScreen>()));
|
||||
AddAssert("disallows adjustments 5", () => !musicController.AllowTrackAdjustments);
|
||||
AddAssert("disallows adjustments 5", () => !musicController.ApplyModTrackAdjustments);
|
||||
|
||||
AddStep("push allowing screen", () => stack.Push(loadNewScreen<AllowScreen>()));
|
||||
AddAssert("allows adjustments 6", () => musicController.AllowTrackAdjustments);
|
||||
AddAssert("allows adjustments 6", () => musicController.ApplyModTrackAdjustments);
|
||||
|
||||
// Now start exiting from screens
|
||||
AddStep("exit screen", () => stack.Exit());
|
||||
AddAssert("disallows adjustments 7", () => !musicController.AllowTrackAdjustments);
|
||||
AddAssert("disallows adjustments 7", () => !musicController.ApplyModTrackAdjustments);
|
||||
|
||||
AddStep("exit screen", () => stack.Exit());
|
||||
AddAssert("disallows adjustments 8", () => !musicController.AllowTrackAdjustments);
|
||||
AddAssert("disallows adjustments 8", () => !musicController.ApplyModTrackAdjustments);
|
||||
|
||||
AddStep("exit screen", () => stack.Exit());
|
||||
AddAssert("disallows adjustments 9", () => !musicController.AllowTrackAdjustments);
|
||||
AddAssert("disallows adjustments 9", () => !musicController.ApplyModTrackAdjustments);
|
||||
|
||||
AddStep("exit screen", () => stack.Exit());
|
||||
AddAssert("allows adjustments 10", () => musicController.AllowTrackAdjustments);
|
||||
AddAssert("allows adjustments 10", () => musicController.ApplyModTrackAdjustments);
|
||||
|
||||
AddStep("exit screen", () => stack.Exit());
|
||||
AddAssert("allows adjustments 11", () => musicController.AllowTrackAdjustments);
|
||||
AddAssert("allows adjustments 11", () => musicController.ApplyModTrackAdjustments);
|
||||
}
|
||||
|
||||
public partial class TestScreen : ScreenWithBeatmapBackground
|
||||
@ -129,12 +129,12 @@ namespace osu.Game.Tests.Visual
|
||||
|
||||
private partial class AllowScreen : OsuScreen
|
||||
{
|
||||
public override bool? AllowTrackAdjustments => true;
|
||||
public override bool? ApplyModTrackAdjustments => true;
|
||||
}
|
||||
|
||||
public partial class DisallowScreen : OsuScreen
|
||||
{
|
||||
public override bool? AllowTrackAdjustments => false;
|
||||
public override bool? ApplyModTrackAdjustments => false;
|
||||
}
|
||||
|
||||
private partial class InheritScreen : OsuScreen
|
||||
|
@ -104,7 +104,7 @@ namespace osu.Game.Overlays.FirstRunSetup
|
||||
{
|
||||
protected override bool ControlGlobalMusic => false;
|
||||
|
||||
public override bool? AllowTrackAdjustments => false;
|
||||
public override bool? ApplyModTrackAdjustments => false;
|
||||
}
|
||||
|
||||
private partial class UIScaleSlider : RoundedSliderBar<float>
|
||||
|
@ -30,20 +30,14 @@ namespace osu.Game.Overlays.Music
|
||||
[Resolved]
|
||||
private OnScreenDisplay? onScreenDisplay { get; set; }
|
||||
|
||||
[Resolved]
|
||||
private OsuGame game { get; set; } = null!;
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
if (e.Repeat)
|
||||
if (e.Repeat || !musicController.AllowTrackControl.Value)
|
||||
return false;
|
||||
|
||||
switch (e.Action)
|
||||
{
|
||||
case GlobalAction.MusicPlay:
|
||||
if (game.LocalUserPlaying.Value)
|
||||
return false;
|
||||
|
||||
// use previous state as TogglePause may not update the track's state immediately (state update is run on the audio thread see https://github.com/ppy/osu/issues/9880#issuecomment-674668842)
|
||||
bool wasPlaying = musicController.IsPlaying;
|
||||
|
||||
|
@ -40,6 +40,11 @@ namespace osu.Game.Overlays
|
||||
/// </summary>
|
||||
public bool UserPauseRequested { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether user control of the global track should be allowed.
|
||||
/// </summary>
|
||||
public readonly BindableBool AllowTrackControl = new BindableBool(true);
|
||||
|
||||
/// <summary>
|
||||
/// Fired when the global <see cref="WorkingBeatmap"/> has changed.
|
||||
/// Includes direction information for display purposes.
|
||||
@ -92,8 +97,10 @@ namespace osu.Game.Overlays
|
||||
seekDelegate?.Cancel();
|
||||
seekDelegate = Schedule(() =>
|
||||
{
|
||||
if (!beatmap.Disabled)
|
||||
CurrentTrack.Seek(position);
|
||||
if (beatmap.Disabled || !AllowTrackControl.Value)
|
||||
return;
|
||||
|
||||
CurrentTrack.Seek(position);
|
||||
});
|
||||
}
|
||||
|
||||
@ -107,7 +114,7 @@ namespace osu.Game.Overlays
|
||||
|
||||
if (CurrentTrack.IsDummyDevice || beatmap.Value.BeatmapSetInfo.DeletePending)
|
||||
{
|
||||
if (beatmap.Disabled)
|
||||
if (beatmap.Disabled || !AllowTrackControl.Value)
|
||||
return;
|
||||
|
||||
Logger.Log($"{nameof(MusicController)} skipping next track to {nameof(EnsurePlayingSomething)}");
|
||||
@ -132,6 +139,9 @@ namespace osu.Game.Overlays
|
||||
/// <returns>Whether the operation was successful.</returns>
|
||||
public bool Play(bool restart = false, bool requestedByUser = false)
|
||||
{
|
||||
if (requestedByUser && !AllowTrackControl.Value)
|
||||
return false;
|
||||
|
||||
if (requestedByUser)
|
||||
UserPauseRequested = false;
|
||||
|
||||
@ -153,6 +163,9 @@ namespace osu.Game.Overlays
|
||||
/// </param>
|
||||
public void Stop(bool requestedByUser = false)
|
||||
{
|
||||
if (requestedByUser && !AllowTrackControl.Value)
|
||||
return;
|
||||
|
||||
UserPauseRequested |= requestedByUser;
|
||||
if (CurrentTrack.IsRunning)
|
||||
CurrentTrack.StopAsync();
|
||||
@ -164,6 +177,9 @@ namespace osu.Game.Overlays
|
||||
/// <returns>Whether the operation was successful.</returns>
|
||||
public bool TogglePause()
|
||||
{
|
||||
if (!AllowTrackControl.Value)
|
||||
return false;
|
||||
|
||||
if (CurrentTrack.IsRunning)
|
||||
Stop(true);
|
||||
else
|
||||
@ -189,7 +205,7 @@ namespace osu.Game.Overlays
|
||||
/// <returns>The <see cref="PreviousTrackResult"/> that indicate the decided action.</returns>
|
||||
private PreviousTrackResult prev()
|
||||
{
|
||||
if (beatmap.Disabled)
|
||||
if (beatmap.Disabled || !AllowTrackControl.Value)
|
||||
return PreviousTrackResult.None;
|
||||
|
||||
double currentTrackPosition = CurrentTrack.CurrentTime;
|
||||
@ -229,7 +245,7 @@ namespace osu.Game.Overlays
|
||||
|
||||
private bool next()
|
||||
{
|
||||
if (beatmap.Disabled)
|
||||
if (beatmap.Disabled || !AllowTrackControl.Value)
|
||||
return false;
|
||||
|
||||
queuedDirection = TrackChangeDirection.Next;
|
||||
@ -352,24 +368,24 @@ namespace osu.Game.Overlays
|
||||
|
||||
private void onTrackCompleted()
|
||||
{
|
||||
if (!CurrentTrack.Looping && !beatmap.Disabled)
|
||||
if (!CurrentTrack.Looping && !beatmap.Disabled && AllowTrackControl.Value)
|
||||
NextTrack();
|
||||
}
|
||||
|
||||
private bool allowTrackAdjustments;
|
||||
private bool applyModTrackAdjustments;
|
||||
|
||||
/// <summary>
|
||||
/// Whether mod track adjustments are allowed to be applied.
|
||||
/// </summary>
|
||||
public bool AllowTrackAdjustments
|
||||
public bool ApplyModTrackAdjustments
|
||||
{
|
||||
get => allowTrackAdjustments;
|
||||
get => applyModTrackAdjustments;
|
||||
set
|
||||
{
|
||||
if (allowTrackAdjustments == value)
|
||||
if (applyModTrackAdjustments == value)
|
||||
return;
|
||||
|
||||
allowTrackAdjustments = value;
|
||||
applyModTrackAdjustments = value;
|
||||
ResetTrackAdjustments();
|
||||
}
|
||||
}
|
||||
@ -377,7 +393,7 @@ namespace osu.Game.Overlays
|
||||
private AudioAdjustments modTrackAdjustments;
|
||||
|
||||
/// <summary>
|
||||
/// Resets the adjustments currently applied on <see cref="CurrentTrack"/> and applies the mod adjustments if <see cref="AllowTrackAdjustments"/> is <c>true</c>.
|
||||
/// Resets the adjustments currently applied on <see cref="CurrentTrack"/> and applies the mod adjustments if <see cref="ApplyModTrackAdjustments"/> is <c>true</c>.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// Does not reset any adjustments applied directly to the beatmap track.
|
||||
@ -390,7 +406,7 @@ namespace osu.Game.Overlays
|
||||
CurrentTrack.RemoveAllAdjustments(AdjustableProperty.Tempo);
|
||||
CurrentTrack.RemoveAllAdjustments(AdjustableProperty.Volume);
|
||||
|
||||
if (allowTrackAdjustments)
|
||||
if (applyModTrackAdjustments)
|
||||
{
|
||||
CurrentTrack.BindAdjustments(modTrackAdjustments = new AudioAdjustments());
|
||||
|
||||
|
@ -1,13 +1,12 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using System.Threading;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
@ -40,33 +39,35 @@ namespace osu.Game.Overlays
|
||||
private const float bottom_black_area_height = 55;
|
||||
private const float margin = 10;
|
||||
|
||||
private Drawable background;
|
||||
private ProgressBar progressBar;
|
||||
private Drawable background = null!;
|
||||
private ProgressBar progressBar = null!;
|
||||
|
||||
private IconButton prevButton;
|
||||
private IconButton playButton;
|
||||
private IconButton nextButton;
|
||||
private IconButton playlistButton;
|
||||
private IconButton prevButton = null!;
|
||||
private IconButton playButton = null!;
|
||||
private IconButton nextButton = null!;
|
||||
private IconButton playlistButton = null!;
|
||||
|
||||
private SpriteText title, artist;
|
||||
private SpriteText title = null!, artist = null!;
|
||||
|
||||
private PlaylistOverlay playlist;
|
||||
private PlaylistOverlay? playlist;
|
||||
|
||||
private Container dragContainer;
|
||||
private Container playerContainer;
|
||||
private Container playlistContainer;
|
||||
private Container dragContainer = null!;
|
||||
private Container playerContainer = null!;
|
||||
private Container playlistContainer = null!;
|
||||
|
||||
protected override string PopInSampleName => "UI/now-playing-pop-in";
|
||||
protected override string PopOutSampleName => "UI/now-playing-pop-out";
|
||||
|
||||
[Resolved]
|
||||
private MusicController musicController { get; set; }
|
||||
private MusicController musicController { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private Bindable<WorkingBeatmap> beatmap { get; set; }
|
||||
private Bindable<WorkingBeatmap> beatmap { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; }
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
private Bindable<bool> allowTrackControl = null!;
|
||||
|
||||
public NowPlayingOverlay()
|
||||
{
|
||||
@ -220,8 +221,10 @@ namespace osu.Game.Overlays
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
beatmap.BindDisabledChanged(_ => Scheduler.AddOnce(beatmapDisabledChanged));
|
||||
beatmapDisabledChanged();
|
||||
beatmap.BindDisabledChanged(_ => Scheduler.AddOnce(updateEnabledStates));
|
||||
|
||||
allowTrackControl = musicController.AllowTrackControl.GetBoundCopy();
|
||||
allowTrackControl.BindValueChanged(_ => Scheduler.AddOnce(updateEnabledStates), true);
|
||||
|
||||
musicController.TrackChanged += trackChanged;
|
||||
trackChanged(beatmap.Value);
|
||||
@ -286,31 +289,34 @@ namespace osu.Game.Overlays
|
||||
}
|
||||
}
|
||||
|
||||
private Action pendingBeatmapSwitch;
|
||||
private Action? pendingBeatmapSwitch;
|
||||
|
||||
private CancellationTokenSource? backgroundLoadCancellation;
|
||||
|
||||
private WorkingBeatmap? currentBeatmap;
|
||||
|
||||
private void trackChanged(WorkingBeatmap beatmap, TrackChangeDirection direction = TrackChangeDirection.None)
|
||||
{
|
||||
currentBeatmap = beatmap;
|
||||
|
||||
// avoid using scheduler as our scheduler may not be run for a long time, holding references to beatmaps.
|
||||
pendingBeatmapSwitch = delegate
|
||||
{
|
||||
// todo: this can likely be replaced with WorkingBeatmap.GetBeatmapAsync()
|
||||
Task.Run(() =>
|
||||
{
|
||||
if (beatmap?.Beatmap == null) // this is not needed if a placeholder exists
|
||||
{
|
||||
title.Text = @"Nothing to play";
|
||||
artist.Text = @"Nothing to play";
|
||||
}
|
||||
else
|
||||
{
|
||||
BeatmapMetadata metadata = beatmap.Metadata;
|
||||
title.Text = new RomanisableString(metadata.TitleUnicode, metadata.Title);
|
||||
artist.Text = new RomanisableString(metadata.ArtistUnicode, metadata.Artist);
|
||||
}
|
||||
});
|
||||
BeatmapMetadata metadata = beatmap.Metadata;
|
||||
|
||||
title.Text = new RomanisableString(metadata.TitleUnicode, metadata.Title);
|
||||
artist.Text = new RomanisableString(metadata.ArtistUnicode, metadata.Artist);
|
||||
|
||||
backgroundLoadCancellation?.Cancel();
|
||||
|
||||
LoadComponentAsync(new Background(beatmap) { Depth = float.MaxValue }, newBackground =>
|
||||
{
|
||||
if (beatmap != currentBeatmap)
|
||||
{
|
||||
newBackground.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
switch (direction)
|
||||
{
|
||||
case TrackChangeDirection.Next:
|
||||
@ -330,27 +336,29 @@ namespace osu.Game.Overlays
|
||||
background = newBackground;
|
||||
|
||||
playerContainer.Add(newBackground);
|
||||
});
|
||||
}, (backgroundLoadCancellation = new CancellationTokenSource()).Token);
|
||||
};
|
||||
}
|
||||
|
||||
private void beatmapDisabledChanged()
|
||||
private void updateEnabledStates()
|
||||
{
|
||||
bool disabled = beatmap.Disabled;
|
||||
bool beatmapDisabled = beatmap.Disabled;
|
||||
bool trackControlDisabled = !musicController.AllowTrackControl.Value;
|
||||
|
||||
if (disabled)
|
||||
if (beatmapDisabled || trackControlDisabled)
|
||||
playlist?.Hide();
|
||||
|
||||
prevButton.Enabled.Value = !disabled;
|
||||
nextButton.Enabled.Value = !disabled;
|
||||
playlistButton.Enabled.Value = !disabled;
|
||||
prevButton.Enabled.Value = !beatmapDisabled && !trackControlDisabled;
|
||||
nextButton.Enabled.Value = !beatmapDisabled && !trackControlDisabled;
|
||||
playlistButton.Enabled.Value = !beatmapDisabled && !trackControlDisabled;
|
||||
playButton.Enabled.Value = !trackControlDisabled;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
|
||||
if (musicController != null)
|
||||
if (musicController.IsNotNull())
|
||||
musicController.TrackChanged -= trackChanged;
|
||||
}
|
||||
|
||||
@ -383,7 +391,7 @@ namespace osu.Game.Overlays
|
||||
private readonly Sprite sprite;
|
||||
private readonly WorkingBeatmap beatmap;
|
||||
|
||||
public Background(WorkingBeatmap beatmap = null)
|
||||
public Background(WorkingBeatmap beatmap)
|
||||
: base(cachedFrameBuffer: true)
|
||||
{
|
||||
this.beatmap = beatmap;
|
||||
@ -413,7 +421,7 @@ namespace osu.Game.Overlays
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(LargeTextureStore textures)
|
||||
{
|
||||
sprite.Texture = beatmap?.GetBackground() ?? textures.Get(@"Backgrounds/bg4");
|
||||
sprite.Texture = beatmap.GetBackground() ?? textures.Get(@"Backgrounds/bg4");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -66,7 +66,7 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
||||
|
||||
public override bool? AllowTrackAdjustments => false;
|
||||
public override bool? ApplyModTrackAdjustments => false;
|
||||
|
||||
protected override bool PlayExitSound => !ExitConfirmed && !switchingDifficulty;
|
||||
|
||||
|
@ -42,6 +42,8 @@ namespace osu.Game.Screens.Edit
|
||||
|
||||
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
||||
|
||||
public override bool? AllowGlobalTrackControl => false;
|
||||
|
||||
[Resolved]
|
||||
private BeatmapManager beatmapManager { get; set; }
|
||||
|
||||
|
@ -67,7 +67,13 @@ namespace osu.Game.Screens
|
||||
/// Whether mod track adjustments should be applied on entering this screen.
|
||||
/// A <see langword="null"/> value means that the parent screen's value of this setting will be used.
|
||||
/// </summary>
|
||||
bool? AllowTrackAdjustments { get; }
|
||||
bool? ApplyModTrackAdjustments { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether control of the global track should be allowed via the music controller / now playing overlay.
|
||||
/// A <see langword="null"/> value means that the parent screen's value of this setting will be used.
|
||||
/// </summary>
|
||||
bool? AllowGlobalTrackControl { get; }
|
||||
|
||||
/// <summary>
|
||||
/// Invoked when the back button has been pressed to close any overlays before exiting this <see cref="IOsuScreen"/>.
|
||||
|
@ -41,7 +41,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
|
||||
[Cached(typeof(IBindable<PlaylistItem>))]
|
||||
public readonly Bindable<PlaylistItem> SelectedItem = new Bindable<PlaylistItem>();
|
||||
|
||||
public override bool? AllowTrackAdjustments => true;
|
||||
public override bool? ApplyModTrackAdjustments => true;
|
||||
|
||||
protected override BackgroundScreen CreateBackground() => new RoomBackgroundScreen(Room.Playlist.FirstOrDefault())
|
||||
{
|
||||
|
@ -29,7 +29,9 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
||||
|
||||
// We are managing our own adjustments. For now, this happens inside the Player instances themselves.
|
||||
public override bool? AllowTrackAdjustments => false;
|
||||
public override bool? ApplyModTrackAdjustments => false;
|
||||
|
||||
public override bool HideOverlaysOnEnter => true;
|
||||
|
||||
/// <summary>
|
||||
/// Whether all spectating players have finished loading.
|
||||
|
@ -67,7 +67,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
SpectatorPlayerClock = clock;
|
||||
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
Masking = true;
|
||||
|
||||
AudioContainer audioContainer;
|
||||
InternalChildren = new Drawable[]
|
||||
|
@ -7,6 +7,7 @@ using System.Linq;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
{
|
||||
@ -15,20 +16,21 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
/// </summary>
|
||||
public partial class PlayerGrid : CompositeDrawable
|
||||
{
|
||||
public const float ANIMATION_DELAY = 400;
|
||||
|
||||
/// <summary>
|
||||
/// A temporary limitation on the number of players, because only layouts up to 16 players are supported for a single screen.
|
||||
/// Todo: Can be removed in the future with scrolling support + performance improvements.
|
||||
/// </summary>
|
||||
public const int MAX_PLAYERS = 16;
|
||||
|
||||
private const float player_spacing = 5;
|
||||
private const float player_spacing = 6;
|
||||
|
||||
/// <summary>
|
||||
/// The currently-maximised facade.
|
||||
/// </summary>
|
||||
public Drawable MaximisedFacade => maximisedFacade;
|
||||
public Facade MaximisedFacade { get; }
|
||||
|
||||
private readonly Facade maximisedFacade;
|
||||
private readonly Container paddingContainer;
|
||||
private readonly FillFlowContainer<Facade> facadeContainer;
|
||||
private readonly Container<Cell> cellContainer;
|
||||
@ -48,12 +50,18 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = facadeContainer = new FillFlowContainer<Facade>
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(player_spacing),
|
||||
}
|
||||
},
|
||||
maximisedFacade = new Facade { RelativeSizeAxes = Axes.Both }
|
||||
MaximisedFacade = new Facade
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Size = new Vector2(0.8f),
|
||||
}
|
||||
}
|
||||
},
|
||||
cellContainer = new Container<Cell> { RelativeSizeAxes = Axes.Both }
|
||||
@ -75,8 +83,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
var facade = new Facade();
|
||||
facadeContainer.Add(facade);
|
||||
|
||||
var cell = new Cell(index, content) { ToggleMaximisationState = toggleMaximisationState };
|
||||
cell.SetFacade(facade);
|
||||
var cell = new Cell(index, content, facade) { ToggleMaximisationState = toggleMaximisationState };
|
||||
|
||||
cellContainer.Add(cell);
|
||||
}
|
||||
@ -91,26 +98,28 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
|
||||
private void toggleMaximisationState(Cell target)
|
||||
{
|
||||
// Iterate through all cells to ensure only one is maximised at any time.
|
||||
foreach (var i in cellContainer.ToList())
|
||||
{
|
||||
if (i == target)
|
||||
i.IsMaximised = !i.IsMaximised;
|
||||
else
|
||||
i.IsMaximised = false;
|
||||
// in the case the target is the already maximised cell (or there is only one cell), no cell should be maximised.
|
||||
bool hasMaximised = !target.IsMaximised && cellContainer.Count > 1;
|
||||
|
||||
if (i.IsMaximised)
|
||||
// Iterate through all cells to ensure only one is maximised at any time.
|
||||
foreach (var cell in cellContainer.ToList())
|
||||
{
|
||||
if (hasMaximised && cell == target)
|
||||
{
|
||||
// Transfer cell to the maximised facade.
|
||||
i.SetFacade(maximisedFacade);
|
||||
cellContainer.ChangeChildDepth(i, maximisedInstanceDepth -= 0.001f);
|
||||
cell.SetFacade(MaximisedFacade, true);
|
||||
cellContainer.ChangeChildDepth(cell, maximisedInstanceDepth -= 0.001f);
|
||||
}
|
||||
else
|
||||
{
|
||||
// Transfer cell back to its original facade.
|
||||
i.SetFacade(facadeContainer[i.FacadeIndex]);
|
||||
cell.SetFacade(facadeContainer[cell.FacadeIndex], false);
|
||||
}
|
||||
|
||||
cell.FadeColour(hasMaximised && cell != target ? Color4.Gray : Color4.White, ANIMATION_DELAY, Easing.OutQuint);
|
||||
}
|
||||
|
||||
facadeContainer.ScaleTo(hasMaximised ? 0.95f : 1, ANIMATION_DELAY, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
@ -169,5 +178,17 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
foreach (var cell in facadeContainer)
|
||||
cell.Size = cellSize;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A facade of the grid which is used as a dummy object to store the required position/size of cells.
|
||||
/// </summary>
|
||||
public partial class Facade : Drawable
|
||||
{
|
||||
public Facade()
|
||||
{
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,13 +1,12 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
@ -32,68 +31,79 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
/// <summary>
|
||||
/// An action that toggles the maximisation state of this cell.
|
||||
/// </summary>
|
||||
public Action<Cell> ToggleMaximisationState;
|
||||
public Action<Cell>? ToggleMaximisationState;
|
||||
|
||||
/// <summary>
|
||||
/// Whether this cell is currently maximised.
|
||||
/// </summary>
|
||||
public bool IsMaximised;
|
||||
public bool IsMaximised { get; private set; }
|
||||
|
||||
private Facade facade;
|
||||
private bool isTracking = true;
|
||||
|
||||
public Cell(int facadeIndex, Drawable content)
|
||||
private bool isAnimating;
|
||||
|
||||
public Cell(int facadeIndex, Drawable content, Facade facade)
|
||||
{
|
||||
FacadeIndex = facadeIndex;
|
||||
this.facade = facade;
|
||||
|
||||
Origin = Anchor.Centre;
|
||||
InternalChild = Content = content;
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
|
||||
if (isTracking)
|
||||
{
|
||||
Position = getFinalPosition();
|
||||
Size = getFinalSize();
|
||||
}
|
||||
var targetPos = getFinalPosition();
|
||||
var targetSize = getFinalSize();
|
||||
|
||||
double duration = isAnimating ? 60 : 0;
|
||||
|
||||
Position = new Vector2(
|
||||
(float)Interpolation.DampContinuously(Position.X, targetPos.X, duration, Time.Elapsed),
|
||||
(float)Interpolation.DampContinuously(Position.Y, targetPos.Y, duration, Time.Elapsed)
|
||||
);
|
||||
|
||||
Size = new Vector2(
|
||||
(float)Interpolation.DampContinuously(Size.X, targetSize.X, duration, Time.Elapsed),
|
||||
(float)Interpolation.DampContinuously(Size.Y, targetSize.Y, duration, Time.Elapsed)
|
||||
);
|
||||
|
||||
// If we don't track the animating state, the animation will also occur when resizing the window.
|
||||
isAnimating &= !Precision.AlmostEquals(Position, targetPos, 0.01f);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Makes this cell track a new facade.
|
||||
/// </summary>
|
||||
public void SetFacade([NotNull] Facade newFacade)
|
||||
public void SetFacade(Facade newFacade, bool isMaximised)
|
||||
{
|
||||
Facade lastFacade = facade;
|
||||
facade = newFacade;
|
||||
IsMaximised = isMaximised;
|
||||
isAnimating = true;
|
||||
|
||||
if (lastFacade == null || lastFacade == newFacade)
|
||||
return;
|
||||
|
||||
isTracking = false;
|
||||
|
||||
this.MoveTo(getFinalPosition(), 400, Easing.OutQuint).ResizeTo(getFinalSize(), 400, Easing.OutQuint)
|
||||
.Then()
|
||||
.OnComplete(_ =>
|
||||
{
|
||||
if (facade == newFacade)
|
||||
isTracking = true;
|
||||
});
|
||||
TweenEdgeEffectTo(new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Radius = isMaximised ? 30 : 10,
|
||||
Colour = Colour4.Black.Opacity(isMaximised ? 0.5f : 0.2f),
|
||||
}, ANIMATION_DELAY, Easing.OutQuint);
|
||||
}
|
||||
|
||||
private Vector2 getFinalPosition()
|
||||
{
|
||||
var topLeft = Parent.ToLocalSpace(facade.ToScreenSpace(Vector2.Zero));
|
||||
return topLeft + facade.DrawSize / 2;
|
||||
}
|
||||
private Vector2 getFinalPosition() =>
|
||||
Parent.ToLocalSpace(facade.ScreenSpaceDrawQuad.Centre);
|
||||
|
||||
private Vector2 getFinalSize() => facade.DrawSize;
|
||||
private Vector2 getFinalSize() =>
|
||||
Parent.ToLocalSpace(facade.ScreenSpaceDrawQuad.BottomRight)
|
||||
- Parent.ToLocalSpace(facade.ScreenSpaceDrawQuad.TopLeft);
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
ToggleMaximisationState(this);
|
||||
ToggleMaximisationState?.Invoke(this);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
@ -1,22 +0,0 @@
|
||||
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Graphics;
|
||||
|
||||
namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
|
||||
{
|
||||
public partial class PlayerGrid
|
||||
{
|
||||
/// <summary>
|
||||
/// A facade of the grid which is used as a dummy object to store the required position/size of cells.
|
||||
/// </summary>
|
||||
private partial class Facade : Drawable
|
||||
{
|
||||
public Facade()
|
||||
{
|
||||
Anchor = Anchor.Centre;
|
||||
Origin = Anchor.Centre;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -85,7 +85,9 @@ namespace osu.Game.Screens
|
||||
[Resolved]
|
||||
private MusicController musicController { get; set; }
|
||||
|
||||
public virtual bool? AllowTrackAdjustments => null;
|
||||
public virtual bool? ApplyModTrackAdjustments => null;
|
||||
|
||||
public virtual bool? AllowGlobalTrackControl => null;
|
||||
|
||||
public Bindable<WorkingBeatmap> Beatmap { get; private set; }
|
||||
|
||||
@ -95,7 +97,9 @@ namespace osu.Game.Screens
|
||||
|
||||
private OsuScreenDependencies screenDependencies;
|
||||
|
||||
private bool? trackAdjustmentStateAtSuspend;
|
||||
private bool? globalMusicControlStateAtSuspend;
|
||||
|
||||
private bool? modTrackAdjustmentStateAtSuspend;
|
||||
|
||||
internal void CreateLeasedDependencies(IReadOnlyDependencyContainer dependencies) => createDependencies(dependencies);
|
||||
|
||||
@ -178,8 +182,10 @@ namespace osu.Game.Screens
|
||||
|
||||
// it's feasible to resume to a screen if the target screen never loaded successfully.
|
||||
// in such a case there's no need to restore this value.
|
||||
if (trackAdjustmentStateAtSuspend != null)
|
||||
musicController.AllowTrackAdjustments = trackAdjustmentStateAtSuspend.Value;
|
||||
if (modTrackAdjustmentStateAtSuspend != null)
|
||||
musicController.ApplyModTrackAdjustments = modTrackAdjustmentStateAtSuspend.Value;
|
||||
if (globalMusicControlStateAtSuspend != null)
|
||||
musicController.AllowTrackControl.Value = globalMusicControlStateAtSuspend.Value;
|
||||
|
||||
base.OnResuming(e);
|
||||
}
|
||||
@ -188,7 +194,8 @@ namespace osu.Game.Screens
|
||||
{
|
||||
base.OnSuspending(e);
|
||||
|
||||
trackAdjustmentStateAtSuspend = musicController.AllowTrackAdjustments;
|
||||
modTrackAdjustmentStateAtSuspend = musicController.ApplyModTrackAdjustments;
|
||||
globalMusicControlStateAtSuspend = musicController.AllowTrackControl.Value;
|
||||
|
||||
onSuspendingLogo();
|
||||
}
|
||||
@ -197,8 +204,11 @@ namespace osu.Game.Screens
|
||||
{
|
||||
applyArrivingDefaults(false);
|
||||
|
||||
if (AllowTrackAdjustments != null)
|
||||
musicController.AllowTrackAdjustments = AllowTrackAdjustments.Value;
|
||||
if (ApplyModTrackAdjustments != null)
|
||||
musicController.ApplyModTrackAdjustments = ApplyModTrackAdjustments.Value;
|
||||
|
||||
if (AllowGlobalTrackControl != null)
|
||||
musicController.AllowTrackControl.Value = AllowGlobalTrackControl.Value;
|
||||
|
||||
if (backgroundStack?.Push(ownedBackground = CreateBackground()) != true)
|
||||
{
|
||||
|
@ -70,7 +70,7 @@ namespace osu.Game.Screens.Play
|
||||
protected override OverlayActivation InitialOverlayActivationMode => OverlayActivation.UserTriggered;
|
||||
|
||||
// We are managing our own adjustments (see OnEntering/OnExiting).
|
||||
public override bool? AllowTrackAdjustments => false;
|
||||
public override bool? ApplyModTrackAdjustments => false;
|
||||
|
||||
private readonly IBindable<bool> gameActive = new Bindable<bool>(true);
|
||||
|
||||
|
@ -46,6 +46,8 @@ namespace osu.Game.Screens.Play
|
||||
|
||||
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
||||
|
||||
public override bool? AllowGlobalTrackControl => false;
|
||||
|
||||
// Here because IsHovered will not update unless we do so.
|
||||
public override bool HandlePositionalInput => true;
|
||||
|
||||
|
@ -36,6 +36,8 @@ namespace osu.Game.Screens.Ranking
|
||||
|
||||
public override bool DisallowExternalBeatmapRulesetChanges => true;
|
||||
|
||||
public override bool? AllowGlobalTrackControl => true;
|
||||
|
||||
// Temporary for now to stop dual transitions. Should respect the current toolbar mode, but there's no way to do so currently.
|
||||
public override bool HideOverlaysOnEnter => true;
|
||||
|
||||
|
@ -60,7 +60,7 @@ namespace osu.Game.Screens.Select
|
||||
|
||||
protected virtual bool ShowFooter => true;
|
||||
|
||||
public override bool? AllowTrackAdjustments => true;
|
||||
public override bool? ApplyModTrackAdjustments => true;
|
||||
|
||||
/// <summary>
|
||||
/// Can be null if <see cref="ShowFooter"/> is false.
|
||||
|
Loading…
x
Reference in New Issue
Block a user