1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-28 04:42:58 +08:00

Merge pull request #18111 from bdach/mod-overlay/integration

Replace old mod overlay with new design
This commit is contained in:
Dean Herbert 2022-05-08 02:44:50 +09:00 committed by GitHub
commit 3bb22dece6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 353 additions and 113 deletions

View File

@ -1,7 +1,6 @@
// 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 System.Linq;
using System.Threading;
using NUnit.Framework;
using osu.Framework.Allocation;
@ -283,7 +282,7 @@ namespace osu.Game.Tests.Visual.Background
AddUntilStep("Song select has selection", () => songSelect.Carousel?.SelectedBeatmapInfo != null);
AddStep("Set default user settings", () =>
{
SelectedMods.Value = SelectedMods.Value.Concat(new[] { new OsuModNoFail() }).ToArray();
SelectedMods.Value = new[] { new OsuModNoFail() };
songSelect.DimLevel.Value = 0.7f;
songSelect.BlurLevel.Value = 0.4f;
});

View File

@ -627,7 +627,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("invoke on back button", () => multiplayerComponents.OnBackButton());
AddAssert("mod overlay is hidden", () => this.ChildrenOfType<UserModSelectOverlay>().Single().State.Value == Visibility.Hidden);
AddAssert("mod overlay is hidden", () => this.ChildrenOfType<UserModSelectScreen>().Single().State.Value == Visibility.Hidden);
AddAssert("dialog overlay is hidden", () => DialogOverlay.State.Value == Visibility.Hidden);

View File

@ -132,7 +132,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
private void assertHasFreeModButton(Type type, bool hasButton = true)
{
AddAssert($"{type.ReadableName()} {(hasButton ? "displayed" : "not displayed")} in freemod overlay",
() => songSelect.ChildrenOfType<FreeModSelectOverlay>().Single().ChildrenOfType<ModButton>().All(b => b.Mod.GetType() != type));
() => this.ChildrenOfType<FreeModSelectScreen>()
.Single()
.ChildrenOfType<ModPanel>()
.Where(panel => !panel.Filtered.Value)
.All(b => b.Mod.GetType() != type));
}
private class TestMultiplayerMatchSongSelect : MultiplayerMatchSongSelect

View File

@ -168,8 +168,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
ClickButtonWhenEnabled<RoomSubScreen.UserModSelectButton>();
AddUntilStep("mod select contents loaded",
() => this.ChildrenOfType<ModColumn>().Any() && this.ChildrenOfType<ModColumn>().All(col => col.IsLoaded && col.ItemsLoaded));
AddUntilStep("mod select contains only double time mod",
() => this.ChildrenOfType<UserModSelectOverlay>().SingleOrDefault()?.ChildrenOfType<ModButton>().SingleOrDefault()?.Mod is OsuModDoubleTime);
() => this.ChildrenOfType<UserModSelectScreen>()
.SingleOrDefault()?
.ChildrenOfType<ModPanel>()
.SingleOrDefault(panel => !panel.Filtered.Value)?.Mod is OsuModDoubleTime);
}
}
}

View File

@ -253,12 +253,12 @@ namespace osu.Game.Tests.Visual.Navigation
PushAndConfirm(() => songSelect = new TestPlaySongSelect());
AddStep("Show mods overlay", () => songSelect.ModSelectOverlay.Show());
AddAssert("Overlay was shown", () => songSelect.ModSelectOverlay.State.Value == Visibility.Visible);
AddStep("Move mouse to backButton", () => InputManager.MoveMouseTo(backButtonPosition));
// BackButton handles hover using its child button, so this checks whether or not any of BackButton's children are hovered.
AddUntilStep("Back button is hovered", () => Game.ChildrenOfType<BackButton>().First().Children.Any(c => c.IsHovered));
AddStep("Move mouse to dimmed area", () => InputManager.MoveMouseTo(new Vector2(
songSelect.ScreenSpaceDrawQuad.TopLeft.X + 1,
songSelect.ScreenSpaceDrawQuad.TopLeft.Y + songSelect.ScreenSpaceDrawQuad.Height / 2)));
AddStep("Click left mouse button", () => InputManager.Click(MouseButton.Left));
AddStep("Click back button", () => InputManager.Click(MouseButton.Left));
AddUntilStep("Overlay was hidden", () => songSelect.ModSelectOverlay.State.Value == Visibility.Hidden);
exitViaBackButtonAndConfirm();
}
@ -551,7 +551,7 @@ namespace osu.Game.Tests.Visual.Navigation
public class TestPlaySongSelect : PlaySongSelect
{
public ModSelectOverlay ModSelectOverlay => ModSelect;
public ModSelectScreen ModSelectOverlay => ModSelect;
public BeatmapOptionsOverlay BeatmapOptionsOverlay => BeatmapOptions;

View File

@ -8,6 +8,7 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Testing;
@ -18,6 +19,7 @@ using osu.Game.Extensions;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
@ -918,6 +920,19 @@ namespace osu.Game.Tests.Visual.SongSelect
AddAssert("check ruleset is correct for score", () => Ruleset.Value.OnlineID == 0);
}
[Test]
public void TestModOverlayToggling()
{
changeRuleset(0);
createSongSelect();
AddStep("toggle mod overlay on", () => InputManager.Key(Key.F1));
AddUntilStep("mod overlay shown", () => songSelect.ModSelect.State.Value == Visibility.Visible);
AddStep("toggle mod overlay off", () => InputManager.Key(Key.F1));
AddUntilStep("mod overlay hidden", () => songSelect.ModSelect.State.Value == Visibility.Hidden);
}
private void waitForInitialSelection()
{
AddUntilStep("wait for initial selection", () => !Beatmap.IsDefault);
@ -993,6 +1008,7 @@ namespace osu.Game.Tests.Visual.SongSelect
public WorkingBeatmap CurrentBeatmap => Beatmap.Value;
public IWorkingBeatmap CurrentBeatmapDetailsBeatmap => BeatmapDetails.Beatmap;
public new BeatmapCarousel Carousel => base.Carousel;
public new ModSelectScreen ModSelect => base.ModSelect;
public new void PresentScore(ScoreInfo score) => base.PresentScore(score);

View File

@ -66,7 +66,10 @@ namespace osu.Game.Tests.Visual.UserInterface
public class TestShearedOverlayContainer : ShearedOverlayContainer
{
protected override OverlayColourScheme ColourScheme => OverlayColourScheme.Green;
public TestShearedOverlayContainer()
: base(OverlayColourScheme.Green)
{
}
[BackgroundDependencyLoader]
private void load()

View File

@ -34,7 +34,7 @@ namespace osu.Game.Graphics.Containers
protected virtual bool DimMainContent => true;
[Resolved(CanBeNull = true)]
private OsuGame game { get; set; }
private IOverlayManager overlayManager { get; set; }
[Resolved]
private PreviewTrackManager previewTrackManager { get; set; }
@ -50,8 +50,8 @@ namespace osu.Game.Graphics.Containers
protected override void LoadComplete()
{
if (game != null)
OverlayActivationMode.BindTo(game.OverlayActivationMode);
if (overlayManager != null)
OverlayActivationMode.BindTo(overlayManager.OverlayActivationMode);
OverlayActivationMode.BindValueChanged(mode =>
{
@ -127,14 +127,14 @@ namespace osu.Game.Graphics.Containers
if (didChange)
samplePopIn?.Play();
if (BlockScreenWideMouse && DimMainContent) game?.AddBlockingOverlay(this);
if (BlockScreenWideMouse && DimMainContent) overlayManager?.ShowBlockingOverlay(this);
break;
case Visibility.Hidden:
if (didChange)
samplePopOut?.Play();
if (BlockScreenWideMouse) game?.RemoveBlockingOverlay(this);
if (BlockScreenWideMouse) overlayManager?.HideBlockingOverlay(this);
break;
}
@ -150,7 +150,7 @@ namespace osu.Game.Graphics.Containers
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
game?.RemoveBlockingOverlay(this);
overlayManager?.HideBlockingOverlay(this);
}
}
}

View File

@ -63,7 +63,7 @@ namespace osu.Game
/// The full osu! experience. Builds on top of <see cref="OsuGameBase"/> to add menus and binding logic
/// for initial components that are generally retrieved via DI.
/// </summary>
public class OsuGame : OsuGameBase, IKeyBindingHandler<GlobalAction>, ILocalUserPlayInfo, IPerformFromScreenRunner
public class OsuGame : OsuGameBase, IKeyBindingHandler<GlobalAction>, ILocalUserPlayInfo, IPerformFromScreenRunner, IOverlayManager
{
/// <summary>
/// The amount of global offset to apply when a left/right anchored overlay is displayed (ie. settings or notifications).
@ -171,6 +171,7 @@ namespace osu.Game
private readonly string[] args;
private readonly List<OsuFocusedOverlayContainer> focusedOverlays = new List<OsuFocusedOverlayContainer>();
private readonly List<OverlayContainer> externalOverlays = new List<OverlayContainer>();
private readonly List<OverlayContainer> visibleBlockingOverlays = new List<OverlayContainer>();
@ -183,22 +184,50 @@ namespace osu.Game
SentryLogger = new SentryLogger(this);
}
#region IOverlayManager
IBindable<OverlayActivation> IOverlayManager.OverlayActivationMode => OverlayActivationMode;
private void updateBlockingOverlayFade() =>
ScreenContainer.FadeColour(visibleBlockingOverlays.Any() ? OsuColour.Gray(0.5f) : Color4.White, 500, Easing.OutQuint);
public void AddBlockingOverlay(OverlayContainer overlay)
IDisposable IOverlayManager.RegisterBlockingOverlay(OverlayContainer overlayContainer)
{
if (overlayContainer.Parent != null)
throw new ArgumentException($@"Overlays registered via {nameof(IOverlayManager.RegisterBlockingOverlay)} should not be added to the scene graph.");
if (externalOverlays.Contains(overlayContainer))
throw new ArgumentException($@"{overlayContainer} has already been registered via {nameof(IOverlayManager.RegisterBlockingOverlay)} once.");
externalOverlays.Add(overlayContainer);
overlayContent.Add(overlayContainer);
return new InvokeOnDisposal(() => unregisterBlockingOverlay(overlayContainer));
}
void IOverlayManager.ShowBlockingOverlay(OverlayContainer overlay)
{
if (!visibleBlockingOverlays.Contains(overlay))
visibleBlockingOverlays.Add(overlay);
updateBlockingOverlayFade();
}
public void RemoveBlockingOverlay(OverlayContainer overlay) => Schedule(() =>
void IOverlayManager.HideBlockingOverlay(OverlayContainer overlay) => Schedule(() =>
{
visibleBlockingOverlays.Remove(overlay);
updateBlockingOverlayFade();
});
/// <summary>
/// Unregisters a blocking <see cref="OverlayContainer"/> that was not created by <see cref="OsuGame"/> itself.
/// </summary>
private void unregisterBlockingOverlay(OverlayContainer overlayContainer)
{
externalOverlays.Remove(overlayContainer);
overlayContainer.Expire();
}
#endregion
/// <summary>
/// Close all game-wide overlays.
/// </summary>

View File

@ -31,8 +31,6 @@ namespace osu.Game.Overlays
[Cached]
public class FirstRunSetupOverlay : ShearedOverlayContainer
{
protected override OverlayColourScheme ColourScheme => OverlayColourScheme.Purple;
[Resolved]
private IPerformFromScreenRunner performer { get; set; } = null!;
@ -70,6 +68,11 @@ namespace osu.Game.Overlays
private Container content = null!;
public FirstRunSetupOverlay()
: base(OverlayColourScheme.Purple)
{
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{

View File

@ -0,0 +1,44 @@
// 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 System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Game.Screens.Select;
namespace osu.Game.Overlays
{
[Cached]
internal interface IOverlayManager
{
/// <summary>
/// Whether overlays should be able to be opened game-wide. Value is sourced from the current active screen.
/// </summary>
IBindable<OverlayActivation> OverlayActivationMode { get; }
/// <summary>
/// Registers a blocking <see cref="OverlayContainer"/> that was not created by <see cref="OsuGame"/> itself for later use.
/// </summary>
/// <remarks>
/// The goal of this method is to allow child screens, like <see cref="SongSelect"/> to register their own full-screen blocking overlays
/// with background dim.
/// In those cases, for the dim to work correctly, the overlays need to be added at a game level directly, rather as children of the screens.
/// </remarks>
/// <returns>
/// An <see cref="IDisposable"/> that should be disposed of when the <paramref name="overlayContainer"/> should be unregistered.
/// Disposing of this <see cref="IDisposable"/> will automatically expire the <paramref name="overlayContainer"/>.
/// </returns>
IDisposable RegisterBlockingOverlay(OverlayContainer overlayContainer);
/// <summary>
/// Should be called when <paramref name="overlay"/> has been shown and should begin blocking background input.
/// </summary>
void ShowBlockingOverlay(OverlayContainer overlay);
/// <summary>
/// Should be called when a blocking <paramref name="overlay"/> has been hidden and should stop blocking background input.
/// </summary>
void HideBlockingOverlay(OverlayContainer overlay);
}
}

View File

@ -41,7 +41,7 @@ namespace osu.Game.Overlays.Mods
private Func<Mod, bool>? filter;
/// <summary>
/// Function determining whether each mod in the column should be displayed.
/// A function determining whether each mod in the column should be displayed.
/// A return value of <see langword="true"/> means that the mod is not filtered and therefore its corresponding panel should be displayed.
/// A return value of <see langword="false"/> means that the mod is filtered out and therefore its corresponding panel should be hidden.
/// </summary>
@ -250,9 +250,8 @@ namespace osu.Game.Overlays.Mods
private void load(OsuGameBase game, OverlayColourProvider colourProvider, OsuColour colours)
{
availableMods.BindTo(game.AvailableMods);
// this `BindValueChanged` callback is intentionally here, to ensure that local available mods are constructed as early as possible.
// this is needed to make sure no external changes to mods are dropped while mod panels are asynchronously loading.
availableMods.BindValueChanged(_ => updateLocalAvailableMods(), true);
updateLocalAvailableMods(asyncLoadContent: false);
availableMods.BindValueChanged(_ => updateLocalAvailableMods(asyncLoadContent: true));
headerBackground.Colour = accentColour = colours.ForModType(ModType);
@ -279,7 +278,7 @@ namespace osu.Game.Overlays.Mods
toggleAllCheckbox.LabelText = toggleAllCheckbox.Current.Value ? CommonStrings.DeselectAll : CommonStrings.SelectAll;
}
private void updateLocalAvailableMods()
private void updateLocalAvailableMods(bool asyncLoadContent)
{
var newMods = ModUtils.FlattenMods(availableMods.Value.GetValueOrDefault(ModType) ?? Array.Empty<Mod>())
.Select(m => m.DeepClone())
@ -290,11 +289,10 @@ namespace osu.Game.Overlays.Mods
localAvailableMods = newMods;
if (!IsLoaded)
// if we're coming from BDL, perform the first load synchronously to make sure everything is in place as early as possible.
onPanelsLoaded(createPanels());
else
if (asyncLoadContent)
asyncLoadPanels();
else
onPanelsLoaded(createPanels());
}
private CancellationTokenSource? cancellationTokenSource;

View File

@ -20,10 +20,10 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Input.Bindings;
using osu.Game.Localisation;
using osu.Game.Rulesets.Mods;
using osuTK;
using osuTK.Input;
using osu.Game.Localisation;
namespace osu.Game.Overlays.Mods
{
@ -31,13 +31,16 @@ namespace osu.Game.Overlays.Mods
{
protected const int BUTTON_WIDTH = 200;
protected override OverlayColourScheme ColourScheme => OverlayColourScheme.Green;
[Cached]
public Bindable<IReadOnlyList<Mod>> SelectedMods { get; private set; } = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
private Func<Mod, bool> isValidMod = m => true;
/// <summary>
/// A function determining whether each mod in the column should be displayed.
/// A return value of <see langword="true"/> means that the mod is not filtered and therefore its corresponding panel should be displayed.
/// A return value of <see langword="false"/> means that the mod is filtered out and therefore its corresponding panel should be hidden.
/// </summary>
public Func<Mod, bool> IsValidMod
{
get => isValidMod;
@ -57,31 +60,27 @@ namespace osu.Game.Overlays.Mods
protected virtual ModColumn CreateModColumn(ModType modType, Key[]? toggleKeys = null) => new ModColumn(modType, false, toggleKeys);
protected virtual IEnumerable<ShearedButton> CreateFooterButtons() => new[]
{
customisationButton = new ShearedToggleButton(BUTTON_WIDTH)
{
Text = ModSelectScreenStrings.ModCustomisation,
Active = { BindTarget = customisationVisible }
},
new ShearedButton(BUTTON_WIDTH)
{
Text = CommonStrings.DeselectAll,
Action = DeselectAll
}
};
protected virtual IReadOnlyList<Mod> ComputeNewModsFromSelection(IReadOnlyList<Mod> oldSelection, IReadOnlyList<Mod> newSelection) => newSelection;
protected virtual IEnumerable<ShearedButton> CreateFooterButtons() => createDefaultFooterButtons();
private readonly BindableBool customisationVisible = new BindableBool();
private DifficultyMultiplierDisplay? multiplierDisplay;
private ModSettingsArea modSettingsArea = null!;
private ColumnScrollContainer columnScroll = null!;
private ColumnFlowContainer columnFlow = null!;
private FillFlowContainer<ShearedButton> footerButtonFlow = null!;
private ShearedButton backButton = null!;
private DifficultyMultiplierDisplay? multiplierDisplay;
private ShearedToggleButton? customisationButton;
protected ModSelectScreen(OverlayColourScheme colourScheme = OverlayColourScheme.Green)
: base(colourScheme)
{
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
@ -185,14 +184,6 @@ namespace osu.Game.Overlays.Mods
};
}
private ColumnDimContainer createModColumnContent(ModType modType, Key[]? toggleKeys = null)
=> new ColumnDimContainer(CreateModColumn(modType, toggleKeys))
{
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
RequestScroll = column => columnScroll.ScrollIntoView(column, extraScroll: 140)
};
protected override void LoadComplete()
{
base.LoadComplete();
@ -216,6 +207,47 @@ namespace osu.Game.Overlays.Mods
updateAvailableMods();
}
/// <summary>
/// Select all visible mods in all columns.
/// </summary>
protected void SelectAll()
{
foreach (var column in columnFlow.Columns)
column.SelectAll();
}
/// <summary>
/// Deselect all visible mods in all columns.
/// </summary>
protected void DeselectAll()
{
foreach (var column in columnFlow.Columns)
column.DeselectAll();
}
private ColumnDimContainer createModColumnContent(ModType modType, Key[]? toggleKeys = null)
=> new ColumnDimContainer(CreateModColumn(modType, toggleKeys))
{
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
RequestScroll = column => columnScroll.ScrollIntoView(column, extraScroll: 140)
};
private ShearedButton[] createDefaultFooterButtons()
=> new[]
{
customisationButton = new ShearedToggleButton(BUTTON_WIDTH)
{
Text = ModSelectScreenStrings.ModCustomisation,
Active = { BindTarget = customisationVisible }
},
new ShearedButton(BUTTON_WIDTH)
{
Text = CommonStrings.DeselectAll,
Action = DeselectAll
}
};
private void updateMultiplier()
{
if (multiplierDisplay == null)
@ -306,7 +338,7 @@ namespace osu.Game.Overlays.Mods
SelectedMods.Value = ComputeNewModsFromSelection(SelectedMods.Value, candidateSelection);
}
protected virtual IReadOnlyList<Mod> ComputeNewModsFromSelection(IReadOnlyList<Mod> oldSelection, IReadOnlyList<Mod> newSelection) => newSelection;
#region Transition handling
protected override void PopIn()
{
@ -352,20 +384,17 @@ namespace osu.Game.Overlays.Mods
}
}
protected void SelectAll()
{
foreach (var column in columnFlow.Columns)
column.SelectAll();
}
#endregion
protected void DeselectAll()
{
foreach (var column in columnFlow.Columns)
column.DeselectAll();
}
#region Input handling
public override bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{
if (e.Repeat)
return false;
// This is handled locally here because this overlay is being registered at the game level
// and therefore takes away keyboard focus from the screen stack.
if (e.Action == GlobalAction.Back)
{
if (customisationVisible.Value)
@ -375,9 +404,27 @@ namespace osu.Game.Overlays.Mods
return true;
}
return base.OnPressed(e);
switch (e.Action)
{
case GlobalAction.ToggleModSelection:
case GlobalAction.Select:
{
if (customisationVisible.Value)
customisationVisible.Value = false;
Hide();
return true;
}
default:
return base.OnPressed(e);
}
}
#endregion
/// <summary>
/// Manages horizontal scrolling of mod columns, along with the "active" states of each column based on visibility.
/// </summary>
internal class ColumnScrollContainer : OsuScrollContainer<ColumnFlowContainer>
{
public ColumnScrollContainer()
@ -416,6 +463,9 @@ namespace osu.Game.Overlays.Mods
}
}
/// <summary>
/// Manages padding and layout of mod columns.
/// </summary>
internal class ColumnFlowContainer : FillFlowContainer<ColumnDimContainer>
{
public IEnumerable<ModColumn> Columns => Children.Select(dimWrapper => dimWrapper.Column);
@ -452,11 +502,21 @@ namespace osu.Game.Overlays.Mods
}
}
/// <summary>
/// Encapsulates a column and provides dim and input blocking based on an externally managed "active" state.
/// </summary>
internal class ColumnDimContainer : Container
{
public ModColumn Column { get; }
/// <summary>
/// Tracks whether this column is in an interactive state. Generally only the case when the column is on-screen.
/// </summary>
public readonly Bindable<bool> Active = new BindableBool();
/// <summary>
/// Invoked when the column is clicked while not active, requesting a scroll to be performed to bring it on-screen.
/// </summary>
public Action<ColumnDimContainer>? RequestScroll { get; set; }
[Resolved]
@ -511,6 +571,9 @@ namespace osu.Game.Overlays.Mods
}
}
/// <summary>
/// A container which blocks and handles input, managing the "return from customisation" state change.
/// </summary>
private class ClickToReturnContainer : Container
{
public BindableBool HandleMouse { get; } = new BindableBool();

View File

@ -51,17 +51,15 @@ namespace osu.Game.Overlays.Mods
/// </summary>
protected Container FooterContent { get; private set; }
protected abstract OverlayColourScheme ColourScheme { get; }
protected override bool StartHidden => true;
protected override bool BlockNonPositionalInput => true;
protected ShearedOverlayContainer()
protected ShearedOverlayContainer(OverlayColourScheme colourScheme)
{
RelativeSizeAxes = Axes.Both;
ColourProvider = new OverlayColourProvider(ColourScheme);
ColourProvider = new OverlayColourProvider(colourScheme);
}
[BackgroundDependencyLoader]

View File

@ -12,6 +12,11 @@ namespace osu.Game.Overlays.Mods
{
public class UserModSelectScreen : ModSelectScreen
{
public UserModSelectScreen(OverlayColourScheme colourScheme = OverlayColourScheme.Green)
: base(colourScheme)
{
}
protected override ModColumn CreateModColumn(ModType modType, Key[] toggleKeys = null) => new UserModColumn(modType, false, toggleKeys);
protected override IReadOnlyList<Mod> ComputeNewModsFromSelection(IReadOnlyList<Mod> oldSelection, IReadOnlyList<Mod> newSelection)

View File

@ -2,6 +2,7 @@
// See the LICENCE file in the repository root for full licence text.
using System;
using osu.Game.Overlays;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Game.Graphics.UserInterface;
@ -23,6 +24,7 @@ namespace osu.Game.Screens.OnlinePlay
}
public FreeModSelectScreen()
: base(OverlayColourScheme.Plum)
{
IsValidMod = _ => true;
}

View File

@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
@ -57,6 +58,9 @@ namespace osu.Game.Screens.OnlinePlay.Match
protected readonly IBindable<long?> RoomId = new Bindable<long?>();
[Resolved(CanBeNull = true)]
private IOverlayManager overlayManager { get; set; }
[Resolved]
private MusicController music { get; set; }
@ -77,7 +81,11 @@ namespace osu.Game.Screens.OnlinePlay.Match
public readonly Room Room;
private readonly bool allowEdit;
private ModSelectOverlay userModsSelectOverlay;
private ModSelectScreen userModsSelectOverlay;
[CanBeNull]
private IDisposable userModsSelectOverlayRegistration;
private RoomSettingsOverlay settingsOverlay;
private Drawable mainContent;
@ -180,11 +188,6 @@ namespace osu.Game.Screens.OnlinePlay.Match
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Child = userModsSelectOverlay = new UserModSelectOverlay
{
SelectedMods = { BindTarget = UserMods },
IsValidMod = _ => false
}
},
}
}
@ -227,6 +230,12 @@ namespace osu.Game.Screens.OnlinePlay.Match
}
}
};
LoadComponent(userModsSelectOverlay = new UserModSelectScreen(OverlayColourScheme.Plum)
{
SelectedMods = { BindTarget = UserMods },
IsValidMod = _ => false
});
}
protected override void LoadComplete()
@ -254,6 +263,8 @@ namespace osu.Game.Screens.OnlinePlay.Match
beatmapAvailabilityTracker.SelectedItem.BindTo(SelectedItem);
beatmapAvailabilityTracker.Availability.BindValueChanged(_ => updateWorkingBeatmap());
userModsSelectOverlayRegistration = overlayManager?.RegisterBlockingOverlay(userModsSelectOverlay);
}
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
@ -298,7 +309,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
public override void OnSuspending(ScreenTransitionEvent e)
{
endHandlingTrack();
onLeaving();
base.OnSuspending(e);
}
@ -316,7 +327,7 @@ namespace osu.Game.Screens.OnlinePlay.Match
RoomManager?.PartRoom();
Mods.Value = Array.Empty<Mod>();
endHandlingTrack();
onLeaving();
return base.OnExiting(e);
}
@ -412,6 +423,12 @@ namespace osu.Game.Screens.OnlinePlay.Match
Beatmap.BindValueChanged(applyLoopingToTrack, true);
}
private void onLeaving()
{
userModsSelectOverlay.Hide();
endHandlingTrack();
}
private void endHandlingTrack()
{
Beatmap.ValueChanged -= applyLoopingToTrack;
@ -459,5 +476,12 @@ namespace osu.Game.Screens.OnlinePlay.Match
public class UserModSelectButton : PurpleTriangleButton
{
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
userModsSelectOverlayRegistration?.Dispose();
}
}
}

View File

@ -14,6 +14,7 @@ using osu.Framework.Screens;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
@ -45,7 +46,6 @@ namespace osu.Game.Screens.OnlinePlay
protected readonly Bindable<IReadOnlyList<Mod>> FreeMods = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
private readonly FreeModSelectOverlay freeModSelectOverlay;
private readonly Room room;
private WorkingBeatmap initialBeatmap;
@ -53,13 +53,16 @@ namespace osu.Game.Screens.OnlinePlay
private IReadOnlyList<Mod> initialMods;
private bool itemSelected;
private readonly FreeModSelectScreen freeModSelectOverlay;
private IDisposable freeModSelectOverlayRegistration;
protected OnlinePlaySongSelect(Room room)
{
this.room = room;
Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING };
freeModSelectOverlay = new FreeModSelectOverlay
freeModSelectOverlay = new FreeModSelectScreen
{
SelectedMods = { BindTarget = FreeMods },
IsValidMod = IsValidFreeMod,
@ -75,7 +78,7 @@ namespace osu.Game.Screens.OnlinePlay
initialRuleset = Ruleset.Value;
initialMods = Mods.Value.ToList();
FooterPanels.Add(freeModSelectOverlay);
LoadComponent(freeModSelectOverlay);
}
protected override void LoadComplete()
@ -94,6 +97,8 @@ namespace osu.Game.Screens.OnlinePlay
Mods.BindValueChanged(onModsChanged);
Ruleset.BindValueChanged(onRulesetChanged);
freeModSelectOverlayRegistration = OverlayManager?.RegisterBlockingOverlay(freeModSelectOverlay);
}
private void onModsChanged(ValueChangedEvent<IReadOnlyList<Mod>> mods)
@ -150,10 +155,12 @@ namespace osu.Game.Screens.OnlinePlay
Mods.Value = initialMods;
}
freeModSelectOverlay.Hide();
return base.OnExiting(e);
}
protected override ModSelectOverlay CreateModSelectOverlay() => new UserModSelectOverlay
protected override ModSelectScreen CreateModSelectOverlay() => new UserModSelectScreen(OverlayColourScheme.Plum)
{
IsValidMod = IsValidMod
};
@ -182,5 +189,12 @@ namespace osu.Game.Screens.OnlinePlay
private bool checkCompatibleFreeMod(Mod mod)
=> Mods.Value.All(m => m.Acronym != mod.Acronym) // Mod must not be contained in the required mods.
&& ModUtils.CheckCompatibleSet(Mods.Value.Append(mod).ToArray()); // Mod must be compatible with all the required mods.
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
freeModSelectOverlayRegistration?.Dispose();
}
}
}

View File

@ -35,6 +35,7 @@ using osu.Framework.Input.Bindings;
using osu.Game.Collections;
using osu.Game.Graphics.UserInterface;
using System.Diagnostics;
using JetBrains.Annotations;
using osu.Game.Screens.Play;
using osu.Game.Database;
using osu.Game.Skinning;
@ -101,7 +102,7 @@ namespace osu.Game.Screens.Select
[Resolved(CanBeNull = true)]
private LegacyImportManager legacyImportManager { get; set; }
protected ModSelectOverlay ModSelect { get; private set; }
protected ModSelectScreen ModSelect { get; private set; }
protected Sample SampleConfirm { get; private set; }
@ -116,9 +117,15 @@ namespace osu.Game.Screens.Select
private double audioFeedbackLastPlaybackTime;
[CanBeNull]
private IDisposable modSelectOverlayRegistration;
[Resolved]
private MusicController music { get; set; }
[Resolved(CanBeNull = true)]
internal IOverlayManager OverlayManager { get; private set; }
[BackgroundDependencyLoader(true)]
private void load(AudioManager audio, IDialogOverlay dialog, OsuColour colours, ManageCollectionsDialog manageCollectionsDialog, DifficultyRecommender recommender)
{
@ -252,38 +259,25 @@ namespace osu.Game.Screens.Select
{
AddRangeInternal(new Drawable[]
{
new GridContainer // used for max height implementation
FooterPanels = new Container
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Both,
RowDimensions = new[]
Padding = new MarginPadding { Bottom = Footer.HEIGHT },
Children = new Drawable[]
{
new Dimension(),
new Dimension(GridSizeMode.Relative, 1f, maxSize: ModSelectOverlay.HEIGHT + Footer.HEIGHT),
},
Content = new[]
{
null,
new Drawable[]
{
FooterPanels = new Container
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Bottom = Footer.HEIGHT },
Children = new Drawable[]
{
BeatmapOptions = new BeatmapOptionsOverlay(),
ModSelect = CreateModSelectOverlay()
}
}
}
BeatmapOptions = new BeatmapOptionsOverlay(),
}
},
Footer = new Footer()
Footer = new Footer(),
});
}
// preload the mod select overlay for later use in `LoadComplete()`.
// therein it will be registered at the `OsuGame` level to properly function as a blocking overlay.
LoadComponent(ModSelect = CreateModSelectOverlay());
if (Footer != null)
{
foreach (var (button, overlay) in CreateFooterButtons())
@ -317,6 +311,13 @@ namespace osu.Game.Screens.Select
}
}
protected override void LoadComplete()
{
base.LoadComplete();
modSelectOverlayRegistration = OverlayManager?.RegisterBlockingOverlay(ModSelect);
}
/// <summary>
/// Creates the buttons to be displayed in the footer.
/// </summary>
@ -332,7 +333,7 @@ namespace osu.Game.Screens.Select
(new FooterButtonOptions(), BeatmapOptions)
};
protected virtual ModSelectOverlay CreateModSelectOverlay() => new UserModSelectOverlay();
protected virtual ModSelectScreen CreateModSelectOverlay() => new UserModSelectScreen();
protected virtual void ApplyFilterToCarousel(FilterCriteria criteria)
{
@ -658,6 +659,7 @@ namespace osu.Game.Screens.Select
return true;
beatmapInfoWedge.Hide();
ModSelect.Hide();
this.FadeOut(100);
@ -716,6 +718,8 @@ namespace osu.Game.Screens.Select
if (music != null)
music.TrackChanged -= ensureTrackLooping;
modSelectOverlayRegistration?.Dispose();
}
/// <summary>

View File

@ -1,12 +1,15 @@
// 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 System;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Development;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Logging;
using osu.Framework.Testing;
using osu.Game.Graphics;
using osu.Game.Overlays;
using osu.Game.Screens;
@ -15,11 +18,12 @@ namespace osu.Game.Tests.Visual
/// <summary>
/// A test case which can be used to test a screen (that relies on OnEntering being called to execute startup instructions).
/// </summary>
public abstract class ScreenTestScene : OsuManualInputManagerTestScene
public abstract class ScreenTestScene : OsuManualInputManagerTestScene, IOverlayManager
{
protected readonly OsuScreenStack Stack;
private readonly Container content;
private readonly Container overlayContent;
protected override Container<Drawable> Content => content;
@ -36,7 +40,11 @@ namespace osu.Game.Tests.Visual
RelativeSizeAxes = Axes.Both
},
content = new Container { RelativeSizeAxes = Axes.Both },
DialogOverlay = new DialogOverlay()
overlayContent = new Container
{
RelativeSizeAxes = Axes.Both,
Child = DialogOverlay = new DialogOverlay()
}
});
Stack.ScreenPushed += (lastScreen, newScreen) => Logger.Log($"{nameof(ScreenTestScene)} screen changed → {newScreen}");
@ -65,5 +73,26 @@ namespace osu.Game.Tests.Visual
return false;
});
}
#region IOverlayManager
IBindable<OverlayActivation> IOverlayManager.OverlayActivationMode { get; } = new Bindable<OverlayActivation>(OverlayActivation.All);
// in the blocking methods below it is important to be careful about threading (e.g. use `Expire()` rather than `Remove()`, and schedule transforms),
// because in the worst case the clean-up methods could be called from async disposal.
IDisposable IOverlayManager.RegisterBlockingOverlay(OverlayContainer overlayContainer)
{
overlayContent.Add(overlayContainer);
return new InvokeOnDisposal(() => overlayContainer.Expire());
}
void IOverlayManager.ShowBlockingOverlay(OverlayContainer overlay)
=> Schedule(() => Stack.FadeColour(OsuColour.Gray(0.5f), 500, Easing.OutQuint));
void IOverlayManager.HideBlockingOverlay(OverlayContainer overlay)
=> Schedule(() => Stack.FadeColour(Colour4.White, 500, Easing.OutQuint));
#endregion
}
}