1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-21 03:02:54 +08:00

Merge branch 'master' into tournament-team-editor-improvements

This commit is contained in:
Dean Herbert 2023-07-21 15:30:02 +09:00
commit 9d71e8f20d
13 changed files with 270 additions and 74 deletions

View File

@ -1,8 +1,6 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
#nullable disable
using System; using System;
using System.Linq; using System.Linq;
using NUnit.Framework; using NUnit.Framework;
@ -82,7 +80,7 @@ namespace osu.Game.Tests.Visual.Editing
[Test] [Test]
public void TestNudgeSelection() public void TestNudgeSelection()
{ {
HitCircle[] addedObjects = null; HitCircle[] addedObjects = null!;
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[] AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[]
{ {
@ -104,7 +102,7 @@ namespace osu.Game.Tests.Visual.Editing
[Test] [Test]
public void TestRotateHotkeys() public void TestRotateHotkeys()
{ {
HitCircle[] addedObjects = null; HitCircle[] addedObjects = null!;
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[] AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[]
{ {
@ -136,7 +134,7 @@ namespace osu.Game.Tests.Visual.Editing
[Test] [Test]
public void TestGlobalFlipHotkeys() public void TestGlobalFlipHotkeys()
{ {
HitCircle addedObject = null; HitCircle addedObject = null!;
AddStep("add hitobjects", () => EditorBeatmap.Add(addedObject = new HitCircle { StartTime = 100 })); AddStep("add hitobjects", () => EditorBeatmap.Add(addedObject = new HitCircle { StartTime = 100 }));
@ -286,11 +284,104 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject)); AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject));
} }
[Test]
public void TestCyclicSelection()
{
var firstObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 0 };
var secondObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 300 };
var thirdObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 600 };
AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] { firstObject, secondObject, thirdObject }));
moveMouseToObject(() => firstObject);
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("second selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(secondObject));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("third selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(thirdObject));
// cycle around
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject));
}
[Test]
public void TestCyclicSelectionOutwards()
{
var firstObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 0 };
var secondObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 300 };
var thirdObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 600 };
AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] { firstObject, secondObject, thirdObject }));
moveMouseToObject(() => firstObject);
AddStep("seek near second", () => EditorClock.Seek(320));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("second selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(secondObject));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("third selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(thirdObject));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject));
// cycle around
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("second selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(secondObject));
}
[Test]
public void TestCyclicSelectionBackwards()
{
var firstObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 0 };
var secondObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 300 };
var thirdObject = new HitCircle { Position = new Vector2(256, 192), StartTime = 600 };
AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] { firstObject, secondObject, thirdObject }));
moveMouseToObject(() => firstObject);
AddStep("seek to third", () => EditorClock.Seek(600));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("third selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(thirdObject));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("second selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(secondObject));
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("first selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(firstObject));
// cycle around
AddStep("left click", () => InputManager.Click(MouseButton.Left));
AddAssert("third selected", () => EditorBeatmap.SelectedHitObjects.Single(), () => Is.EqualTo(thirdObject));
}
[Test]
public void TestDoubleClickToSeek()
{
var hitCircle = new HitCircle { Position = new Vector2(256, 192), StartTime = 600 };
AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[] { hitCircle }));
moveMouseToObject(() => hitCircle);
AddRepeatStep("double click", () => InputManager.Click(MouseButton.Left), 2);
AddUntilStep("seeked to circle", () => EditorClock.CurrentTime, () => Is.EqualTo(600));
}
[TestCase(false)] [TestCase(false)]
[TestCase(true)] [TestCase(true)]
public void TestMultiSelectFromDrag(bool alreadySelectedBeforeDrag) public void TestMultiSelectFromDrag(bool alreadySelectedBeforeDrag)
{ {
HitCircle[] addedObjects = null; HitCircle[] addedObjects = null!;
AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[] AddStep("add hitobjects", () => EditorBeatmap.AddRange(addedObjects = new[]
{ {
@ -389,7 +480,7 @@ namespace osu.Game.Tests.Visual.Editing
[Test] [Test]
public void TestQuickDeleteRemovesSliderControlPoint() public void TestQuickDeleteRemovesSliderControlPoint()
{ {
Slider slider = null; Slider slider = null!;
PathControlPoint[] points = PathControlPoint[] points =
{ {

View File

@ -67,6 +67,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded); AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded);
} }
[Test]
public void TestSelectFreeMods()
{
AddStep("set some freemods", () => songSelect.FreeMods.Value = new OsuRuleset().GetModsFor(ModType.Fun).ToArray());
AddStep("set all freemods", () => songSelect.FreeMods.Value = new OsuRuleset().CreateAllMods().ToArray());
AddStep("set no freemods", () => songSelect.FreeMods.Value = Array.Empty<Mod>());
}
[Test] [Test]
public void TestBeatmapConfirmed() public void TestBeatmapConfirmed()
{ {

View File

@ -14,6 +14,7 @@ using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Input.States; using osu.Framework.Input.States;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Tournament.Components;
using osu.Game.Tournament.Models; using osu.Game.Tournament.Models;
using osu.Game.Tournament.Screens.Ladder; using osu.Game.Tournament.Screens.Ladder;
using osu.Game.Tournament.Screens.Ladder.Components; using osu.Game.Tournament.Screens.Ladder.Components;
@ -35,11 +36,9 @@ namespace osu.Game.Tournament.Screens.Editors
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Content.Add(new LadderEditorSettings AddInternal(new ControlPanel
{ {
Anchor = Anchor.TopRight, Child = new LadderEditorSettings(),
Origin = Anchor.TopRight,
Margin = new MarginPadding(5)
}); });
AddInternal(rightClickMessage = new WarningBox("Right click to place and link matches")); AddInternal(rightClickMessage = new WarningBox("Right click to place and link matches"));

View File

@ -213,7 +213,8 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
int instantWinAmount = Match.Round.Value.BestOf.Value / 2; int instantWinAmount = Match.Round.Value.BestOf.Value / 2;
Match.Completed.Value = Match.Round.Value.BestOf.Value > 0 Match.Completed.Value = Match.Round.Value.BestOf.Value > 0
&& (Match.Team1Score.Value + Match.Team2Score.Value >= Match.Round.Value.BestOf.Value || Match.Team1Score.Value > instantWinAmount || Match.Team2Score.Value > instantWinAmount); && (Match.Team1Score.Value + Match.Team2Score.Value >= Match.Round.Value.BestOf.Value || Match.Team1Score.Value > instantWinAmount
|| Match.Team2Score.Value > instantWinAmount);
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -265,8 +266,6 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
protected override bool OnMouseDown(MouseDownEvent e) => e.Button == MouseButton.Left && editorInfo != null; protected override bool OnMouseDown(MouseDownEvent e) => e.Button == MouseButton.Left && editorInfo != null;
protected override bool OnDragStart(DragStartEvent e) => editorInfo != null;
protected override bool OnKeyDown(KeyDownEvent e) protected override bool OnKeyDown(KeyDownEvent e)
{ {
if (Selected && editorInfo != null && e.Key == Key.Delete) if (Selected && editorInfo != null && e.Key == Key.Delete)
@ -287,17 +286,36 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
return true; return true;
} }
private Vector2 positionAtStartOfDrag;
protected override bool OnDragStart(DragStartEvent e)
{
if (editorInfo != null)
{
positionAtStartOfDrag = Position;
return true;
}
return false;
}
protected override void OnDrag(DragEvent e) protected override void OnDrag(DragEvent e)
{ {
base.OnDrag(e); base.OnDrag(e);
Selected = true; Selected = true;
this.MoveToOffset(e.Delta);
var pos = Position; this.MoveTo(snapToGrid(positionAtStartOfDrag + (e.MousePosition - e.MouseDownPosition)));
Match.Position.Value = new Point((int)pos.X, (int)pos.Y);
Match.Position.Value = new Point((int)Position.X, (int)Position.Y);
} }
private Vector2 snapToGrid(Vector2 pos) =>
new Vector2(
(int)(pos.X / 10) * 10,
(int)(pos.Y / 10) * 10
);
public void Remove() public void Remove()
{ {
Selected = false; Selected = false;

View File

@ -11,15 +11,17 @@ using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.IEnumerableExtensions; using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Overlays.Settings; using osu.Game.Overlays.Settings;
using osu.Game.Screens.Play.PlayerSettings; using osu.Game.Screens.Play.PlayerSettings;
using osu.Game.Tournament.Components; using osu.Game.Tournament.Components;
using osu.Game.Tournament.Models; using osu.Game.Tournament.Models;
using osuTK;
namespace osu.Game.Tournament.Screens.Ladder.Components namespace osu.Game.Tournament.Screens.Ladder.Components
{ {
public partial class LadderEditorSettings : PlayerSettingsGroup public partial class LadderEditorSettings : CompositeDrawable
{ {
private SettingsDropdown<TournamentRound> roundDropdown; private SettingsDropdown<TournamentRound> roundDropdown;
private PlayerCheckbox losersCheckbox; private PlayerCheckbox losersCheckbox;
@ -33,21 +35,26 @@ namespace osu.Game.Tournament.Screens.Ladder.Components
[Resolved] [Resolved]
private LadderInfo ladderInfo { get; set; } private LadderInfo ladderInfo { get; set; }
public LadderEditorSettings()
: base("ladder")
{
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load() private void load()
{ {
Children = new Drawable[] RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChild = new FillFlowContainer
{ {
team1Dropdown = new SettingsTeamDropdown(ladderInfo.Teams) { LabelText = "Team 1" }, RelativeSizeAxes = Axes.X,
team2Dropdown = new SettingsTeamDropdown(ladderInfo.Teams) { LabelText = "Team 2" }, AutoSizeAxes = Axes.Y,
roundDropdown = new SettingsRoundDropdown(ladderInfo.Rounds) { LabelText = "Round" }, Direction = FillDirection.Vertical,
losersCheckbox = new PlayerCheckbox { LabelText = "Losers Bracket" }, Spacing = new Vector2(5),
dateTimeBox = new DateTextBox { LabelText = "Match Time" }, Children = new Drawable[]
{
team1Dropdown = new SettingsTeamDropdown(ladderInfo.Teams) { LabelText = "Team 1" },
team2Dropdown = new SettingsTeamDropdown(ladderInfo.Teams) { LabelText = "Team 2" },
roundDropdown = new SettingsRoundDropdown(ladderInfo.Rounds) { LabelText = "Round" },
losersCheckbox = new PlayerCheckbox { LabelText = "Losers Bracket" },
dateTimeBox = new DateTextBox { LabelText = "Match Time" },
},
}; };
editorInfo.Selected.ValueChanged += selection => editorInfo.Selected.ValueChanged += selection =>

View File

@ -36,8 +36,8 @@ namespace osu.Game.Tournament.Screens.Ladder
{ {
float newScale = Math.Clamp(scale + e.ScrollDelta.Y / 15 * scale, min_scale, max_scale); float newScale = Math.Clamp(scale + e.ScrollDelta.Y / 15 * scale, min_scale, max_scale);
this.MoveTo(target -= e.MousePosition * (newScale - scale), 2000, Easing.OutQuint); this.MoveTo(target -= e.MousePosition * (newScale - scale), 1000, Easing.OutQuint);
this.ScaleTo(scale = newScale, 2000, Easing.OutQuint); this.ScaleTo(scale = newScale, 1000, Easing.OutQuint);
return true; return true;
} }

View File

@ -41,6 +41,7 @@ namespace osu.Game.Tournament.Screens.Ladder
InternalChild = Content = new Container InternalChild = Content = new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Masking = true,
Children = new Drawable[] Children = new Drawable[]
{ {
new TourneyVideo("ladder") new TourneyVideo("ladder")

View File

@ -38,7 +38,7 @@ namespace osu.Game.Tournament
private Container screens; private Container screens;
private TourneyVideo video; private TourneyVideo video;
public const float CONTROL_AREA_WIDTH = 160; public const float CONTROL_AREA_WIDTH = 200;
public const float STREAM_AREA_WIDTH = 1366; public const float STREAM_AREA_WIDTH = 1366;

View File

@ -111,7 +111,7 @@ namespace osu.Game.Overlays.Mods
private readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> globalAvailableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>(); private readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> globalAvailableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>();
private IEnumerable<ModState> allAvailableMods => AvailableMods.Value.SelectMany(pair => pair.Value); public IEnumerable<ModState> AllAvailableMods => AvailableMods.Value.SelectMany(pair => pair.Value);
private readonly BindableBool customisationVisible = new BindableBool(); private readonly BindableBool customisationVisible = new BindableBool();
@ -382,7 +382,7 @@ namespace osu.Game.Overlays.Mods
private void filterMods() private void filterMods()
{ {
foreach (var modState in allAvailableMods) foreach (var modState in AllAvailableMods)
modState.ValidForSelection.Value = modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod); modState.ValidForSelection.Value = modState.Mod.HasImplementation && IsValidMod.Invoke(modState.Mod);
} }
@ -407,7 +407,7 @@ namespace osu.Game.Overlays.Mods
bool anyCustomisableModActive = false; bool anyCustomisableModActive = false;
bool anyModPendingConfiguration = false; bool anyModPendingConfiguration = false;
foreach (var modState in allAvailableMods) foreach (var modState in AllAvailableMods)
{ {
anyCustomisableModActive |= modState.Active.Value && modState.Mod.GetSettingsSourceProperties().Any(); anyCustomisableModActive |= modState.Active.Value && modState.Mod.GetSettingsSourceProperties().Any();
anyModPendingConfiguration |= modState.PendingConfiguration; anyModPendingConfiguration |= modState.PendingConfiguration;
@ -464,7 +464,7 @@ namespace osu.Game.Overlays.Mods
var newSelection = new List<Mod>(); var newSelection = new List<Mod>();
foreach (var modState in allAvailableMods) foreach (var modState in AllAvailableMods)
{ {
var matchingSelectedMod = SelectedMods.Value.SingleOrDefault(selected => selected.GetType() == modState.Mod.GetType()); var matchingSelectedMod = SelectedMods.Value.SingleOrDefault(selected => selected.GetType() == modState.Mod.GetType());
@ -491,7 +491,7 @@ namespace osu.Game.Overlays.Mods
if (externalSelectionUpdateInProgress) if (externalSelectionUpdateInProgress)
return; return;
var candidateSelection = allAvailableMods.Where(modState => modState.Active.Value) var candidateSelection = AllAvailableMods.Where(modState => modState.Active.Value)
.Select(modState => modState.Mod) .Select(modState => modState.Mod)
.ToArray(); .ToArray();

View File

@ -25,8 +25,6 @@ namespace osu.Game.Overlays.SkinEditor
[Resolved] [Resolved]
private SkinEditor editor { get; set; } = null!; private SkinEditor editor { get; set; } = null!;
protected override bool AllowCyclicSelection => true;
public SkinBlueprintContainer(ISerialisableDrawableContainer targetContainer) public SkinBlueprintContainer(ISerialisableDrawableContainer targetContainer)
{ {
this.targetContainer = targetContainer; this.targetContainer = targetContainer;

View File

@ -46,15 +46,6 @@ namespace osu.Game.Screens.Edit.Compose.Components
protected readonly BindableList<T> SelectedItems = new BindableList<T>(); protected readonly BindableList<T> SelectedItems = new BindableList<T>();
/// <summary>
/// Whether to allow cyclic selection on clicking multiple times.
/// </summary>
/// <remarks>
/// Disabled by default as it does not work well with editors that support double-clicking or other advanced interactions.
/// Can probably be made to work with more thought.
/// </remarks>
protected virtual bool AllowCyclicSelection => false;
protected BlueprintContainer() protected BlueprintContainer()
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
@ -167,6 +158,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
if (ClickedBlueprint == null || SelectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered) != ClickedBlueprint) if (ClickedBlueprint == null || SelectionHandler.SelectedBlueprints.FirstOrDefault(b => b.IsHovered) != ClickedBlueprint)
return false; return false;
doubleClickHandled = true;
return true; return true;
} }
@ -177,6 +169,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
{ {
endClickSelection(e); endClickSelection(e);
clickSelectionHandled = false; clickSelectionHandled = false;
doubleClickHandled = false;
isDraggingBlueprint = false; isDraggingBlueprint = false;
wasDragStarted = false; wasDragStarted = false;
}); });
@ -376,6 +369,11 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// </summary> /// </summary>
private bool clickSelectionHandled; private bool clickSelectionHandled;
/// <summary>
/// Whether a blueprint was double-clicked since last mouse down.
/// </summary>
private bool doubleClickHandled;
/// <summary> /// <summary>
/// Whether the selected blueprint(s) were already selected on mouse down. Generally used to perform selection cycling on mouse up in such a case. /// Whether the selected blueprint(s) were already selected on mouse down. Generally used to perform selection cycling on mouse up in such a case.
/// </summary> /// </summary>
@ -427,8 +425,8 @@ namespace osu.Game.Screens.Edit.Compose.Components
/// <returns>Whether a click selection was active.</returns> /// <returns>Whether a click selection was active.</returns>
private bool endClickSelection(MouseButtonEvent e) private bool endClickSelection(MouseButtonEvent e)
{ {
// If already handled a selection or drag, we don't want to perform a mouse up / click action. // If already handled a selection, double-click, or drag, we don't want to perform a mouse up / click action.
if (clickSelectionHandled || isDraggingBlueprint) return true; if (clickSelectionHandled || doubleClickHandled || isDraggingBlueprint) return true;
if (e.Button != MouseButton.Left) return false; if (e.Button != MouseButton.Left) return false;
@ -444,7 +442,7 @@ namespace osu.Game.Screens.Edit.Compose.Components
return false; return false;
} }
if (!wasDragStarted && selectedBlueprintAlreadySelectedOnMouseDown && SelectedItems.Count == 1 && AllowCyclicSelection) if (!wasDragStarted && selectedBlueprintAlreadySelectedOnMouseDown && SelectedItems.Count == 1)
{ {
// If a click occurred and was handled by the currently selected blueprint but didn't result in a drag, // If a click occurred and was handled by the currently selected blueprint but didn't result in a drag,
// cycle between other blueprints which are also under the cursor. // cycle between other blueprints which are also under the cursor.

View File

@ -1,15 +1,21 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence. // 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. // See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Bindables; using osu.Framework.Bindables;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
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; using osuTK;
@ -17,28 +23,60 @@ namespace osu.Game.Screens.OnlinePlay
{ {
public partial class FooterButtonFreeMods : FooterButton, IHasCurrentValue<IReadOnlyList<Mod>> public partial class FooterButtonFreeMods : FooterButton, IHasCurrentValue<IReadOnlyList<Mod>>
{ {
public Bindable<IReadOnlyList<Mod>> Current public Bindable<IReadOnlyList<Mod>> Current { get; set; } = new BindableWithCurrent<IReadOnlyList<Mod>>();
private OsuSpriteText count = null!;
private Circle circle = null!;
private readonly FreeModSelectOverlay freeModSelectOverlay;
public FooterButtonFreeMods(FreeModSelectOverlay freeModSelectOverlay)
{ {
get => modDisplay.Current; this.freeModSelectOverlay = freeModSelectOverlay;
set => modDisplay.Current = value;
} }
private readonly ModDisplay modDisplay; [Resolved]
private OsuColour colours { get; set; } = null!;
public FooterButtonFreeMods()
{
ButtonContentContainer.Add(modDisplay = new ModDisplay
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(0.8f),
ExpansionMode = ExpansionMode.AlwaysContracted,
});
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load()
{ {
ButtonContentContainer.AddRange(new[]
{
new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Children = new Drawable[]
{
circle = new Circle
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Colour = colours.YellowDark,
RelativeSizeAxes = Axes.Both,
},
count = new OsuSpriteText
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Padding = new MarginPadding(5),
UseFullGlyphHeight = false,
}
}
},
new IconButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(0.8f),
Icon = FontAwesome.Solid.Bars,
Action = () => freeModSelectOverlay.ToggleVisibility()
}
});
SelectedColour = colours.Yellow; SelectedColour = colours.Yellow;
DeselectedColour = SelectedColour.Opacity(0.5f); DeselectedColour = SelectedColour.Opacity(0.5f);
Text = @"freemods"; Text = @"freemods";
@ -49,14 +87,49 @@ namespace osu.Game.Screens.OnlinePlay
base.LoadComplete(); base.LoadComplete();
Current.BindValueChanged(_ => updateModDisplay(), true); Current.BindValueChanged(_ => updateModDisplay(), true);
// Overwrite any external behaviour as we delegate the main toggle action to a sub-button.
Action = toggleAllFreeMods;
}
/// <summary>
/// Immediately toggle all free mods on/off.
/// </summary>
private void toggleAllFreeMods()
{
var availableMods = allAvailableAndValidMods.ToArray();
Current.Value = Current.Value.Count == availableMods.Length
? Array.Empty<Mod>()
: availableMods;
} }
private void updateModDisplay() private void updateModDisplay()
{ {
if (Current.Value?.Count > 0) int current = Current.Value.Count;
modDisplay.FadeIn();
if (current == allAvailableAndValidMods.Count())
{
count.Text = "all";
count.FadeColour(colours.Gray2, 200, Easing.OutQuint);
circle.FadeColour(colours.Yellow, 200, Easing.OutQuint);
}
else if (current > 0)
{
count.Text = $"{current} mods";
count.FadeColour(colours.Gray2, 200, Easing.OutQuint);
circle.FadeColour(colours.YellowDark, 200, Easing.OutQuint);
}
else else
modDisplay.FadeOut(); {
count.Text = "off";
count.FadeColour(colours.GrayF, 200, Easing.OutQuint);
circle.FadeColour(colours.Gray4, 200, Easing.OutQuint);
}
} }
private IEnumerable<Mod> allAvailableAndValidMods => freeModSelectOverlay.AllAvailableMods
.Where(state => state.ValidForSelection.Value)
.Select(state => state.Mod);
} }
} }

View File

@ -175,9 +175,12 @@ namespace osu.Game.Screens.OnlinePlay
protected override IEnumerable<(FooterButton, OverlayContainer?)> CreateFooterButtons() protected override IEnumerable<(FooterButton, OverlayContainer?)> CreateFooterButtons()
{ {
var buttons = base.CreateFooterButtons().ToList(); var baseButtons = base.CreateFooterButtons().ToList();
buttons.Insert(buttons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (new FooterButtonFreeMods { Current = FreeMods }, freeModSelectOverlay)); var freeModsButton = new FooterButtonFreeMods(freeModSelectOverlay) { Current = FreeMods };
return buttons;
baseButtons.Insert(baseButtons.FindIndex(b => b.Item1 is FooterButtonMods) + 1, (freeModsButton, freeModSelectOverlay));
return baseButtons;
} }
/// <summary> /// <summary>