1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-23 02:21:19 +08:00

Hook up footer beatmap options via new ISongSelectBeatmapActions class

This commit is contained in:
Dean Herbert
2025-05-15 19:32:28 +09:00
Unverified
parent 5a17d3c290
commit 9fea4a2ed0
6 changed files with 198 additions and 100 deletions
@@ -12,11 +12,10 @@ using osu.Game.Overlays.Dialog;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.Footer;
using osu.Game.Screens.Play;
using osu.Game.Screens.Select;
using osuTK.Input;
using FooterButtonMods = osu.Game.Screens.SelectV2.FooterButtonMods;
using FooterButtonOptions = osu.Game.Screens.SelectV2.FooterButtonOptions;
namespace osu.Game.Tests.Visual.SongSelectV2
{
@@ -382,17 +381,20 @@ namespace osu.Game.Tests.Visual.SongSelectV2
// }
[Test]
public void TestFooterShowOptions()
public void TestFooterOptions()
{
LoadSongSelect();
AddStep("enable options", () =>
{
var optionsButton = this.ChildrenOfType<ScreenFooterButton>().Last();
ImportBeatmapForRuleset(0);
optionsButton.Enabled.Value = true;
optionsButton.TriggerClick();
});
// song select should automatically select the beatmap for us but this is not implemented yet.
// todo: remove when that's the case.
AddAssert("no beatmap selected", () => Beatmap.IsDefault);
AddStep("select beatmap", () => Beatmap.Value = Beatmaps.GetWorkingBeatmap(Beatmaps.GetAllUsableBeatmapSets().Single().Beatmaps.First()));
AddAssert("options enabled", () => this.ChildrenOfType<FooterButtonOptions>().Single().Enabled.Value);
AddStep("click", () => this.ChildrenOfType<FooterButtonOptions>().Single().TriggerClick());
AddUntilStep("popover displayed", () => this.ChildrenOfType<FooterButtonOptions.Popover>().Any(p => p.IsPresent));
}
[Test]
@@ -400,7 +402,23 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{
LoadSongSelect();
AddToggleStep("set options enabled state", state => this.ChildrenOfType<ScreenFooterButton>().Last().Enabled.Value = state);
ImportBeatmapForRuleset(0);
// song select should automatically select the beatmap for us but this is not implemented yet.
// todo: remove when that's the case.
AddAssert("no beatmap selected", () => Beatmap.IsDefault);
AddStep("select beatmap", () => Beatmap.Value = Beatmaps.GetWorkingBeatmap(Beatmaps.GetAllUsableBeatmapSets().Single().Beatmaps.First()));
AddAssert("options enabled", () => this.ChildrenOfType<FooterButtonOptions>().Single().Enabled.Value);
AddStep("delete all beatmaps", () => Beatmaps.Delete());
// song select should automatically select the beatmap for us but this is not implemented yet.
// todo: remove when that's the case.
AddAssert("beatmap selected", () => !Beatmap.IsDefault);
AddStep("select no beatmap", () => Beatmap.SetDefault());
AddUntilStep("wait for no beatmap", () => Beatmap.IsDefault);
AddAssert("options disabled", () => !this.ChildrenOfType<FooterButtonOptions>().Single().Enabled.Value);
}
#endregion
@@ -2,9 +2,11 @@
// See the LICENCE file in the repository root for full licence text.
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Input.Bindings;
using osu.Game.Overlays;
@@ -17,6 +19,12 @@ namespace osu.Game.Screens.SelectV2
[Resolved]
private OverlayColourProvider colourProvider { get; set; } = null!;
[Resolved]
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
[Resolved]
private ISongSelectBeatmapActions? beatmapActions { get; set; }
[BackgroundDependencyLoader]
private void load(OsuColour colour)
{
@@ -28,6 +36,22 @@ namespace osu.Game.Screens.SelectV2
Action = this.ShowPopover;
}
public Framework.Graphics.UserInterface.Popover GetPopover() => new Popover(this, colourProvider);
protected override void LoadComplete()
{
base.LoadComplete();
beatmap.BindValueChanged(_ => beatmapChanged(), true);
}
private void beatmapChanged()
{
this.HidePopover();
Enabled.Value = !beatmap.IsDefault;
}
public Framework.Graphics.UserInterface.Popover GetPopover() => new Popover(this, beatmap.Value)
{
ColourProvider = colourProvider,
BeatmapActions = beatmapActions
};
}
}
@@ -12,7 +12,6 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
using osu.Game.Collections;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
@@ -34,22 +33,21 @@ namespace osu.Game.Screens.SelectV2
private FillFlowContainer buttonFlow = null!;
private readonly FooterButtonOptions footerButton;
[Cached]
private readonly OverlayColourProvider colourProvider;
private readonly WorkingBeatmap beatmap;
private WorkingBeatmap beatmapWhenOpening = null!;
// Can't use DI for these due to popover being initialised from a footer button which ends up being on the global
// PopoverContainer.
public ISongSelectBeatmapActions? BeatmapActions { get; init; }
public required OverlayColourProvider ColourProvider { get; init; }
[Resolved]
private IBindable<WorkingBeatmap> beatmap { get; set; } = null!;
public Popover(FooterButtonOptions footerButton, OverlayColourProvider colourProvider)
public Popover(FooterButtonOptions footerButton, WorkingBeatmap beatmap)
{
this.footerButton = footerButton;
this.colourProvider = colourProvider;
this.beatmap = beatmap;
}
[BackgroundDependencyLoader]
private void load(ManageCollectionsDialog? manageCollectionsDialog, OsuColour colours, BeatmapManager? beatmapManager)
private void load(OsuColour colours)
{
Content.Padding = new MarginPadding(5);
@@ -60,23 +58,21 @@ namespace osu.Game.Screens.SelectV2
Spacing = new Vector2(3),
};
beatmapWhenOpening = beatmap.Value;
addHeader(CommonStrings.General);
addButton(SongSelectStrings.ManageCollections, FontAwesome.Solid.Book, () => manageCollectionsDialog?.Show());
addButton(SongSelectStrings.ManageCollections, FontAwesome.Solid.Book, () => BeatmapActions?.ManageCollections());
addHeader(SongSelectStrings.ForAllDifficulties, beatmapWhenOpening.BeatmapSetInfo.ToString());
addButton(SongSelectStrings.DeleteBeatmap, FontAwesome.Solid.Trash, () => { }, colours.Red1); // songSelect?.DeleteBeatmap(beatmapWhenOpening.BeatmapSetInfo);
addHeader(SongSelectStrings.ForAllDifficulties, beatmap.BeatmapSetInfo.ToString());
addButton(SongSelectStrings.DeleteBeatmap, FontAwesome.Solid.Trash, () => BeatmapActions?.Delete(beatmap.BeatmapSetInfo), colours.Red1);
addHeader(SongSelectStrings.ForSelectedDifficulty, beatmapWhenOpening.BeatmapInfo.DifficultyName);
// TODO: make work, and make show "unplayed" or "played" based on status.
addButton(SongSelectStrings.MarkAsPlayed, FontAwesome.Regular.TimesCircle, null);
addButton(SongSelectStrings.ClearAllLocalScores, FontAwesome.Solid.Eraser, () => { }, colours.Red1); // songSelect?.ClearScores(beatmapWhenOpening.BeatmapInfo);
addHeader(SongSelectStrings.ForSelectedDifficulty, beatmap.BeatmapInfo.DifficultyName);
// TODO: replace with "remove from played" button when beatmap is already played.
addButton(SongSelectStrings.MarkAsPlayed, FontAwesome.Regular.TimesCircle, () => BeatmapActions?.MarkPlayed(beatmap.BeatmapInfo));
addButton(SongSelectStrings.ClearAllLocalScores, FontAwesome.Solid.Eraser, () => BeatmapActions?.ClearScores(beatmap.BeatmapInfo), colours.Red1);
// if (songSelect != null && songSelect.AllowEditing)
addButton(SongSelectStrings.EditBeatmap, FontAwesome.Solid.PencilAlt, () => { }); // songSelect.Edit(beatmapWhenOpening.BeatmapInfo);
if (BeatmapActions?.EditingAllowed == true)
addButton(SongSelectStrings.EditBeatmap, FontAwesome.Solid.PencilAlt, () => BeatmapActions.Edit(beatmap.BeatmapInfo));
addButton(WebCommonStrings.ButtonsHide.ToSentence(), FontAwesome.Solid.Magic, () => beatmapManager?.Hide(beatmapWhenOpening.BeatmapInfo));
addButton(WebCommonStrings.ButtonsHide.ToSentence(), FontAwesome.Solid.Magic, () => BeatmapActions?.Hide(beatmap.BeatmapInfo));
}
protected override void LoadComplete()
@@ -84,8 +80,12 @@ namespace osu.Game.Screens.SelectV2
base.LoadComplete();
ScheduleAfterChildren(() => GetContainingFocusManager()!.ChangeFocus(this));
}
beatmap.BindValueChanged(_ => Hide());
protected override void UpdateState(ValueChangedEvent<Visibility> state)
{
base.UpdateState(state);
footerButton.OverlayState.Value = state.NewValue;
}
private void addHeader(LocalisableString text, string? context = null)
@@ -104,7 +104,7 @@ namespace osu.Game.Screens.SelectV2
textFlow.NewLine();
textFlow.AddText(context, t =>
{
t.Colour = colourProvider.Content2;
t.Colour = ColourProvider.Content2;
t.Font = t.Font.With(size: 13);
});
}
@@ -118,6 +118,7 @@ namespace osu.Game.Screens.SelectV2
{
Text = text,
Icon = icon,
BackgroundColour = ColourProvider.Background3,
TextColour = colour,
Action = () =>
{
@@ -129,44 +130,6 @@ namespace osu.Game.Screens.SelectV2
buttonFlow.Add(button);
}
private partial class OptionButton : OsuButton
{
public IconUsage Icon { get; init; }
public Color4? TextColour { get; init; }
public OptionButton()
{
Size = new Vector2(265, 50);
}
[BackgroundDependencyLoader]
private void load(OverlayColourProvider colourProvider)
{
BackgroundColour = colourProvider.Background3;
SpriteText.Colour = TextColour ?? Color4.White;
Content.CornerRadius = 10;
Add(new SpriteIcon
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Size = new Vector2(17),
X = 15,
Icon = Icon,
Colour = TextColour ?? Color4.White,
});
}
protected override SpriteText CreateText() => new OsuSpriteText
{
Depth = -1,
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
X = 40
};
}
protected override bool OnKeyDown(KeyDownEvent e)
{
// don't absorb control as ToolbarRulesetSelector uses control + number to navigate
@@ -188,10 +151,40 @@ namespace osu.Game.Screens.SelectV2
return base.OnKeyDown(e);
}
protected override void UpdateState(ValueChangedEvent<Visibility> state)
private partial class OptionButton : OsuButton
{
base.UpdateState(state);
footerButton.OverlayState.Value = state.NewValue;
public IconUsage Icon { get; init; }
public Color4? TextColour { get; init; }
public OptionButton()
{
Size = new Vector2(265, 50);
}
[BackgroundDependencyLoader]
private void load()
{
SpriteText.Colour = TextColour ?? Color4.White;
Content.CornerRadius = 10;
Add(new SpriteIcon
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Size = new Vector2(17),
X = 15,
Icon = Icon,
Colour = TextColour ?? Color4.White,
});
}
protected override SpriteText CreateText() => new OsuSpriteText
{
Depth = -1,
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
X = 40
};
}
}
}
@@ -0,0 +1,48 @@
// 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.Game.Beatmaps;
namespace osu.Game.Screens.SelectV2
{
/// <summary>
/// Actions exposed by song select which are used by subcomponents to perform top-level operations.
/// </summary>
public interface ISongSelectBeatmapActions
{
/// <summary>
/// Requests the user for confirmation to delete the given beatmap set.
/// </summary>
void Delete(BeatmapSetInfo beatmapBeatmapSetInfo);
/// <summary>
/// Requests the user for confirmation to clear all local scores in the given beatmap.
/// </summary>
void ClearScores(BeatmapInfo beatmap);
/// <summary>
/// Opens beatmap editor with the given beatmap.
/// </summary>
void Edit(BeatmapInfo beatmap);
/// <summary>
/// Whether calls to <see cref="Edit"/> will succeed or not.
/// </summary>
bool EditingAllowed { get; }
/// <summary>
/// Opens the manage collections dialog.
/// </summary>
void ManageCollections();
/// <summary>
/// Marks a beatmap manually as being played.
/// </summary>
void MarkPlayed(BeatmapInfo beatmap);
/// <summary>
/// Hides a beatmap from user's vision.
/// </summary>
void Hide(BeatmapInfo beatmap);
}
}
+3 -1
View File
@@ -23,6 +23,8 @@ namespace osu.Game.Screens.SelectV2
[Resolved]
private INotificationOverlay? notifications { get; set; }
public override bool EditingAllowed => true;
protected override bool OnStart()
{
if (playerLoader != null) return false;
@@ -98,7 +100,7 @@ namespace osu.Game.Screens.SelectV2
playerLoader = null;
}
private partial class PlayerLoader : Screens.Play.PlayerLoader
private partial class PlayerLoader : Play.PlayerLoader
{
public override bool ShowFooter => true;
+36 -23
View File
@@ -15,11 +15,13 @@ using osu.Framework.Input.Events;
using osu.Framework.Screens;
using osu.Framework.Threading;
using osu.Game.Beatmaps;
using osu.Game.Collections;
using osu.Game.Graphics.Containers;
using osu.Game.Input.Bindings;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Overlays.Volume;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Footer;
using osu.Game.Screens.Menu;
using osu.Game.Screens.Select;
@@ -35,8 +37,8 @@ namespace osu.Game.Screens.SelectV2
/// This screen is intended to house all components introduced in the new song select design to add transitions and examine the overall look.
/// This will be gradually built upon and ultimately replace <see cref="Select.SongSelect"/> once everything is in place.
/// </summary>
[Cached(typeof(SongSelect))]
public abstract partial class SongSelect : OsuScreen, IKeyBindingHandler<GlobalAction>
[Cached(typeof(ISongSelectBeatmapActions))]
public abstract partial class SongSelect : OsuScreen, IKeyBindingHandler<GlobalAction>, ISongSelectBeatmapActions
{
private const float logo_scale = 0.4f;
private const double fade_duration = 300;
@@ -77,6 +79,12 @@ namespace osu.Game.Screens.SelectV2
[Resolved]
private IDialogOverlay? dialogOverlay { get; set; }
[Resolved]
private BeatmapManager beatmaps { get; set; } = null!;
[Resolved]
private ManageCollectionsDialog? collectionsDialog { get; set; }
[BackgroundDependencyLoader]
private void load()
{
@@ -343,26 +351,6 @@ namespace osu.Game.Screens.SelectV2
#endregion
#region Beatmap management
/// <summary>
/// Requests the user for confirmation to delete the given beatmap set.
/// </summary>
public void DeleteBeatmap(BeatmapSetInfo beatmapSet)
{
dialogOverlay?.Push(new BeatmapDeleteDialog(beatmapSet));
}
/// <summary>
/// Requests the user for confirmation to clear all local scores in the given beatmap.
/// </summary>
public void ClearScores(BeatmapInfo beatmap)
{
dialogOverlay?.Push(new BeatmapClearScoresDialog(beatmap));
}
#endregion
#region Hotkeys
public virtual bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
@@ -395,7 +383,7 @@ namespace osu.Game.Screens.SelectV2
if (e.ShiftPressed)
{
if (!Beatmap.IsDefault)
DeleteBeatmap(Beatmap.Value.BeatmapSetInfo);
Delete(Beatmap.Value.BeatmapSetInfo);
return true;
}
@@ -406,5 +394,30 @@ namespace osu.Game.Screens.SelectV2
}
#endregion
#region Beatmap management
public virtual bool EditingAllowed => false;
public void ManageCollections() => collectionsDialog?.Show();
public void MarkPlayed(BeatmapInfo beatmap) => beatmaps.MarkPlayed(beatmap);
public void Hide(BeatmapInfo beatmap) => beatmaps.Hide(beatmap);
public void Edit(BeatmapInfo beatmap)
{
if (!EditingAllowed) return;
// Forced refetch is important here to guarantee correct invalidation across all difficulties.
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmap, true);
this.Push(new EditorLoader());
}
public void Delete(BeatmapSetInfo beatmapSet) => dialogOverlay?.Push(new BeatmapDeleteDialog(beatmapSet));
public void ClearScores(BeatmapInfo beatmap) => dialogOverlay?.Push(new BeatmapClearScoresDialog(beatmap));
#endregion
}
}