1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-22 18:12:56 +08:00

Initial implementation of freemod selection overlay

This commit is contained in:
smoogipoo 2021-01-27 22:15:53 +09:00
parent 4019cc38e5
commit 45e41aaeac
6 changed files with 216 additions and 28 deletions

View File

@ -0,0 +1,24 @@
// 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 NUnit.Framework;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays.Mods;
using osu.Game.Screens.OnlinePlay.Multiplayer;
namespace osu.Game.Tests.Visual.Multiplayer
{
public class TestSceneFreeModSelectOverlay : MultiplayerTestScene
{
private ModSelectOverlay overlay;
[SetUp]
public new void Setup() => Schedule(() =>
{
Child = overlay = new FreeModSelectOverlay
{
State = { Value = Visibility.Visible }
};
});
}
}

View File

@ -174,7 +174,7 @@ namespace osu.Game.Overlays.Mods
switch (e.Button) switch (e.Button)
{ {
case MouseButton.Right: case MouseButton.Right:
SelectNext(-1); OnRightClick(e);
break; break;
} }
} }
@ -183,10 +183,14 @@ namespace osu.Game.Overlays.Mods
protected override bool OnClick(ClickEvent e) protected override bool OnClick(ClickEvent e)
{ {
SelectNext(1); SelectNext(1);
return true; return true;
} }
protected virtual void OnRightClick(MouseUpEvent e)
{
SelectNext(-1);
}
/// <summary> /// <summary>
/// Select the next available mod in a specified direction. /// Select the next available mod in a specified direction.
/// </summary> /// </summary>

View File

@ -19,7 +19,7 @@ namespace osu.Game.Overlays.Mods
{ {
public class ModSection : Container public class ModSection : Container
{ {
private readonly OsuSpriteText headerLabel; private readonly Drawable header;
public FillFlowContainer<ModButtonEmpty> ButtonsContainer { get; } public FillFlowContainer<ModButtonEmpty> ButtonsContainer { get; }
@ -47,10 +47,7 @@ namespace osu.Game.Overlays.Mods
if (m == null) if (m == null)
return new ModButtonEmpty(); return new ModButtonEmpty();
return new ModButton(m) return CreateModButton(m).With(b => b.SelectionChanged = Action);
{
SelectionChanged = Action,
};
}).ToArray(); }).ToArray();
modsLoadCts?.Cancel(); modsLoadCts?.Cancel();
@ -58,7 +55,7 @@ namespace osu.Game.Overlays.Mods
if (modContainers.Length == 0) if (modContainers.Length == 0)
{ {
ModIconsLoaded = true; ModIconsLoaded = true;
headerLabel.Hide(); header.Hide();
Hide(); Hide();
return; return;
} }
@ -73,7 +70,7 @@ namespace osu.Game.Overlays.Mods
buttons = modContainers.OfType<ModButton>().ToArray(); buttons = modContainers.OfType<ModButton>().ToArray();
headerLabel.FadeIn(200); header.FadeIn(200);
this.FadeIn(200); this.FadeIn(200);
} }
} }
@ -160,16 +157,9 @@ namespace osu.Game.Overlays.Mods
Origin = Anchor.TopCentre; Origin = Anchor.TopCentre;
Anchor = Anchor.TopCentre; Anchor = Anchor.TopCentre;
Children = new Drawable[] Children = new[]
{ {
headerLabel = new OsuSpriteText header = CreateHeader(type.Humanize(LetterCasing.Title)),
{
Origin = Anchor.TopLeft,
Anchor = Anchor.TopLeft,
Position = new Vector2(0f, 0f),
Font = OsuFont.GetFont(weight: FontWeight.Bold),
Text = type.Humanize(LetterCasing.Title)
},
ButtonsContainer = new FillFlowContainer<ModButtonEmpty> ButtonsContainer = new FillFlowContainer<ModButtonEmpty>
{ {
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
@ -185,5 +175,13 @@ namespace osu.Game.Overlays.Mods
}, },
}; };
} }
protected virtual ModButton CreateModButton(Mod mod) => new ModButton(mod);
protected virtual Drawable CreateHeader(string text) => new OsuSpriteText
{
Font = OsuFont.GetFont(weight: FontWeight.Bold),
Text = text
};
} }
} }

View File

@ -0,0 +1,101 @@
// 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 System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Screens.OnlinePlay.Multiplayer
{
public class FreeModSelectOverlay : ModSelectOverlay
{
protected override ModSection CreateModSection(ModType type) => new FreeModSection(type);
private class FreeModSection : ModSection
{
private HeaderCheckbox checkbox;
public FreeModSection(ModType type)
: base(type)
{
}
protected override ModButton CreateModButton(Mod mod) => new FreeModButton(mod);
protected override Drawable CreateHeader(string text) => new Container
{
AutoSizeAxes = Axes.Y,
Width = 175,
Child = checkbox = new HeaderCheckbox
{
LabelText = text,
Changed = onCheckboxChanged
}
};
private void onCheckboxChanged(bool value)
{
foreach (var button in ButtonsContainer.OfType<ModButton>())
{
if (value)
// Note: Buttons where only part of the group has an implementation are not fully supported.
button.SelectAt(0);
else
button.Deselect();
}
}
protected override void Update()
{
base.Update();
// If any of the buttons aren't selected, deselect the checkbox.
foreach (var button in ButtonsContainer.OfType<ModButton>())
{
if (button.Mods.Any(m => m.HasImplementation) && !button.Selected)
checkbox.Current.Value = false;
}
}
}
private class HeaderCheckbox : OsuCheckbox
{
public Action<bool> Changed;
protected override void OnUserChange(bool value)
{
base.OnUserChange(value);
Changed?.Invoke(value);
}
}
private class FreeModButton : ModButton
{
public FreeModButton(Mod mod)
: base(mod)
{
}
protected override bool OnClick(ClickEvent e)
{
onClick();
return true;
}
protected override void OnRightClick(MouseUpEvent e) => onClick();
private void onClick()
{
if (Selected)
Deselect();
else
SelectNext(1);
}
}
}
}

View File

@ -6,17 +6,23 @@ using System.Linq;
using Humanizer; using Humanizer;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms; using osu.Game.Online.Rooms;
using osu.Game.Overlays.Mods; using osu.Game.Overlays.Mods;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Play.HUD;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;
using osuTK;
namespace osu.Game.Screens.OnlinePlay.Multiplayer namespace osu.Game.Screens.OnlinePlay.Multiplayer
{ {
@ -33,6 +39,7 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
private StatefulMultiplayerClient client { get; set; } private StatefulMultiplayerClient client { get; set; }
private LoadingLayer loadingLayer; private LoadingLayer loadingLayer;
private FreeModSelectOverlay freeModSelectOverlay;
private WorkingBeatmap initialBeatmap; private WorkingBeatmap initialBeatmap;
private RulesetInfo initialRuleset; private RulesetInfo initialRuleset;
@ -43,6 +50,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
public MultiplayerMatchSongSelect() public MultiplayerMatchSongSelect()
{ {
Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING }; Padding = new MarginPadding { Horizontal = HORIZONTAL_OVERFLOW_PADDING };
freeModSelectOverlay = new FreeModSelectOverlay();
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
@ -52,6 +61,8 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
initialBeatmap = Beatmap.Value; initialBeatmap = Beatmap.Value;
initialRuleset = Ruleset.Value; initialRuleset = Ruleset.Value;
initialMods = Mods.Value.ToList(); initialMods = Mods.Value.ToList();
FooterPanels.Add(freeModSelectOverlay);
} }
protected override bool OnStart() protected override bool OnStart()
@ -111,8 +122,61 @@ namespace osu.Game.Screens.OnlinePlay.Multiplayer
protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea(); protected override BeatmapDetailArea CreateBeatmapDetailArea() => new PlayBeatmapDetailArea();
protected override ModSelectOverlay CreateModSelectOverlay() => new ModSelectOverlay(isValidMod); protected override ModSelectOverlay CreateModSelectOverlay() => new SoloModSelectOverlay(isValidMod);
protected override IEnumerable<(FooterButton, OverlayContainer)> CreateFooterButtons()
{
var buttons = base.CreateFooterButtons().ToList();
buttons.Insert(buttons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (new FooterButtonFreeMods(), freeModSelectOverlay));
return buttons;
}
private bool isValidMod(Mod mod) => !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true; private bool isValidMod(Mod mod) => !(mod is ModAutoplay) && (mod as MultiMod)?.Mods.Any(mm => mm is ModAutoplay) != true;
} }
public class FooterButtonFreeMods : FooterButton, IHasCurrentValue<IReadOnlyList<Mod>>
{
public Bindable<IReadOnlyList<Mod>> Current
{
get => modDisplay.Current;
set => modDisplay.Current = value;
}
private readonly ModDisplay modDisplay;
public FooterButtonFreeMods()
{
ButtonContentContainer.Add(modDisplay = new ModDisplay
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
DisplayUnrankedText = false,
Scale = new Vector2(0.8f),
ExpansionMode = ExpansionMode.AlwaysContracted,
});
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
SelectedColour = colours.Yellow;
DeselectedColour = SelectedColour.Opacity(0.5f);
Text = @"freemods";
}
protected override void LoadComplete()
{
base.LoadComplete();
Current.BindValueChanged(_ => updateModDisplay(), true);
}
private void updateModDisplay()
{
if (Current.Value?.Count > 0)
modDisplay.FadeIn();
else
modDisplay.FadeOut();
}
}
} }

View File

@ -28,19 +28,16 @@ namespace osu.Game.Screens.Select
private readonly List<OverlayContainer> overlays = new List<OverlayContainer>(); private readonly List<OverlayContainer> overlays = new List<OverlayContainer>();
/// <param name="button">THe button to be added.</param> /// <param name="button">The button to be added.</param>
/// <param name="overlay">The <see cref="OverlayContainer"/> to be toggled by this button.</param> /// <param name="overlay">The <see cref="OverlayContainer"/> to be toggled by this button.</param>
public void AddButton(FooterButton button, OverlayContainer overlay) public void AddButton(FooterButton button, OverlayContainer overlay)
{
if (overlay != null)
{ {
overlays.Add(overlay); overlays.Add(overlay);
button.Action = () => showOverlay(overlay); button.Action = () => showOverlay(overlay);
AddButton(button);
} }
/// <param name="button">Button to be added.</param>
public void AddButton(FooterButton button)
{
button.Hovered = updateModeLight; button.Hovered = updateModeLight;
button.HoverLost = updateModeLight; button.HoverLost = updateModeLight;