1
0
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:
Bartłomiej Dach 2023-07-30 14:58:14 +02:00 committed by GitHub
commit 1f7f336fd5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 261 additions and 177 deletions

View File

@ -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()
{

View File

@ -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;

View File

@ -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

View File

@ -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>

View File

@ -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;

View File

@ -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());

View File

@ -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");
}
}

View File

@ -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;

View File

@ -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; }

View File

@ -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"/>.

View File

@ -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())
{

View File

@ -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.

View File

@ -67,7 +67,6 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer.Spectate
SpectatorPlayerClock = clock;
RelativeSizeAxes = Axes.Both;
Masking = true;
AudioContainer audioContainer;
InternalChildren = new Drawable[]

View File

@ -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;
}
}
}
}

View File

@ -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;
}
}

View File

@ -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;
}
}
}
}

View File

@ -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)
{

View File

@ -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);

View File

@ -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;

View File

@ -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;

View File

@ -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.