mirror of
https://github.com/ppy/osu
synced 2024-12-26 00:32:52 +00:00
Merge pull request #28553 from frenzibyte/mod-select-customisation-panel
Detach mod customisation area from the footer and replace with an overlay panel display
This commit is contained in:
commit
14a93bfc1a
@ -58,7 +58,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
|
||||
AddStep("select difficulty adjust", () => freeModSelectOverlay.SelectedMods.Value = new[] { new OsuModDifficultyAdjust() });
|
||||
AddWaitStep("wait some", 3);
|
||||
AddAssert("customisation area not expanded", () => this.ChildrenOfType<ModSettingsArea>().Single().Height == 0);
|
||||
AddAssert("customisation area not expanded", () => !this.ChildrenOfType<ModCustomisationPanel>().Single().Expanded.Value);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
@ -0,0 +1,66 @@
|
||||
// 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 NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public partial class TestSceneModCustomisationPanel : OsuManualInputManagerTestScene
|
||||
{
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||
|
||||
private ModCustomisationPanel panel = null!;
|
||||
|
||||
[SetUp]
|
||||
public void SetUp() => Schedule(() =>
|
||||
{
|
||||
Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(20f),
|
||||
Child = panel = new ModCustomisationPanel
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Width = 400f,
|
||||
State = { Value = Visibility.Visible },
|
||||
SelectedMods = { BindTarget = SelectedMods },
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestDisplay()
|
||||
{
|
||||
AddStep("set DT", () =>
|
||||
{
|
||||
SelectedMods.Value = new[] { new OsuModDoubleTime() };
|
||||
panel.Enabled.Value = panel.Expanded.Value = true;
|
||||
});
|
||||
AddStep("set DA", () =>
|
||||
{
|
||||
SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() };
|
||||
panel.Enabled.Value = panel.Expanded.Value = true;
|
||||
});
|
||||
AddStep("set FL+WU+DA+AD", () =>
|
||||
{
|
||||
SelectedMods.Value = new Mod[] { new OsuModFlashlight(), new ModWindUp(), new OsuModDifficultyAdjust(), new OsuModApproachDifferent() };
|
||||
panel.Enabled.Value = panel.Expanded.Value = true;
|
||||
});
|
||||
AddStep("set empty", () =>
|
||||
{
|
||||
SelectedMods.Value = Array.Empty<Mod>();
|
||||
panel.Enabled.Value = panel.Expanded.Value = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
@ -7,7 +7,6 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Input;
|
||||
@ -56,6 +55,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddStep("clear contents", Clear);
|
||||
AddStep("reset ruleset", () => Ruleset.Value = rulesetStore.GetRuleset(0));
|
||||
AddStep("reset mods", () => SelectedMods.SetDefault());
|
||||
AddStep("reset config", () => configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, true));
|
||||
AddStep("set beatmap", () => Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo));
|
||||
AddStep("set up presets", () =>
|
||||
{
|
||||
@ -225,7 +225,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
AddStep("dismiss mod customisation via toggle", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(modSelectOverlay.CustomisationButton.AsNonNull());
|
||||
InputManager.MoveMouseTo(modSelectOverlay.ChildrenOfType<ModCustomisationHeader>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
assertCustomisationToggleState(disabled: false, active: false);
|
||||
@ -258,7 +258,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDismissCustomisationViaDimmedArea()
|
||||
public void TestDismissCustomisationViaClickingAway()
|
||||
{
|
||||
createScreen();
|
||||
assertCustomisationToggleState(disabled: true, active: false);
|
||||
@ -266,18 +266,23 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddStep("select difficulty adjust via panel", () => getPanelForMod(typeof(OsuModDifficultyAdjust)).TriggerClick());
|
||||
assertCustomisationToggleState(disabled: false, active: true);
|
||||
|
||||
AddStep("move mouse to settings area", () => InputManager.MoveMouseTo(this.ChildrenOfType<ModSettingsArea>().Single()));
|
||||
AddStep("move mouse to dimmed area", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(new Vector2(
|
||||
modSelectOverlay.ScreenSpaceDrawQuad.TopLeft.X,
|
||||
(modSelectOverlay.ScreenSpaceDrawQuad.TopLeft.Y + modSelectOverlay.ScreenSpaceDrawQuad.BottomLeft.Y) / 2));
|
||||
});
|
||||
AddStep("move mouse to search bar", () => InputManager.MoveMouseTo(modSelectOverlay.ChildrenOfType<ShearedSearchTextBox>().Single()));
|
||||
AddStep("click", () => InputManager.Click(MouseButton.Left));
|
||||
assertCustomisationToggleState(disabled: false, active: false);
|
||||
}
|
||||
|
||||
AddStep("move mouse to first mod panel", () => InputManager.MoveMouseTo(modSelectOverlay.ChildrenOfType<ModPanel>().First()));
|
||||
AddAssert("first mod panel is hovered", () => modSelectOverlay.ChildrenOfType<ModPanel>().First().IsHovered);
|
||||
[Test]
|
||||
public void TestDismissCustomisationWhenHidingOverlay()
|
||||
{
|
||||
createScreen();
|
||||
assertCustomisationToggleState(disabled: true, active: false);
|
||||
|
||||
AddStep("select difficulty adjust via panel", () => getPanelForMod(typeof(OsuModDifficultyAdjust)).TriggerClick());
|
||||
assertCustomisationToggleState(disabled: false, active: true);
|
||||
|
||||
AddStep("hide overlay", () => modSelectOverlay.Hide());
|
||||
AddStep("show overlay again", () => modSelectOverlay.Show());
|
||||
assertCustomisationToggleState(disabled: false, active: false);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -339,7 +344,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
createScreen();
|
||||
changeRuleset(0);
|
||||
|
||||
AddStep("Select all fun mods", () =>
|
||||
AddStep("Select all difficulty-increase mods", () =>
|
||||
{
|
||||
modSelectOverlay.ChildrenOfType<ModColumn>()
|
||||
.Single(c => c.ModType == ModType.DifficultyIncrease)
|
||||
@ -641,13 +646,15 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddStep("select DT", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime() });
|
||||
AddAssert("DT selected", () => modSelectOverlay.ChildrenOfType<ModPanel>().Count(panel => panel.Active.Value), () => Is.EqualTo(1));
|
||||
|
||||
AddStep("open customisation area", () => modSelectOverlay.CustomisationButton!.TriggerClick());
|
||||
assertCustomisationToggleState(false, true);
|
||||
AddStep("open customisation area", () => modSelectOverlay.ChildrenOfType<ModCustomisationHeader>().Single().TriggerClick());
|
||||
assertCustomisationToggleState(disabled: false, active: true);
|
||||
|
||||
AddStep("hover over mod settings slider", () =>
|
||||
{
|
||||
var slider = modSelectOverlay.ChildrenOfType<ModSettingsArea>().Single().ChildrenOfType<OsuSliderBar<double>>().First();
|
||||
var slider = modSelectOverlay.ChildrenOfType<ModCustomisationPanel>().Single().ChildrenOfType<OsuSliderBar<double>>().First();
|
||||
InputManager.MoveMouseTo(slider);
|
||||
});
|
||||
|
||||
AddStep("press right arrow", () => InputManager.PressKey(Key.Right));
|
||||
AddAssert("DT speed changed", () => !SelectedMods.Value.OfType<OsuModDoubleTime>().Single().SpeedChange.IsDefault);
|
||||
|
||||
@ -744,9 +751,10 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
AddStep("select difficulty adjust via panel", () => getPanelForMod(typeof(OsuModDifficultyAdjust)).TriggerClick());
|
||||
assertCustomisationToggleState(disabled: false, active: true);
|
||||
AddAssert("back button disabled", () => !modSelectOverlay.BackButton.Enabled.Value);
|
||||
|
||||
AddStep("dismiss customisation area", () => InputManager.Key(Key.Escape));
|
||||
AddAssert("mod select still visible", () => modSelectOverlay.State.Value == Visibility.Visible);
|
||||
|
||||
AddStep("click back button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(modSelectOverlay.BackButton);
|
||||
@ -755,6 +763,19 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
AddAssert("mod select hidden", () => modSelectOverlay.State.Value == Visibility.Hidden);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCloseViaToggleModSelectionBinding()
|
||||
{
|
||||
createScreen();
|
||||
changeRuleset(0);
|
||||
|
||||
AddStep("select difficulty adjust via panel", () => getPanelForMod(typeof(OsuModDifficultyAdjust)).TriggerClick());
|
||||
assertCustomisationToggleState(disabled: false, active: true);
|
||||
|
||||
AddStep("press F1", () => InputManager.Key(Key.F1));
|
||||
AddAssert("mod select hidden", () => modSelectOverlay.State.Value == Visibility.Hidden);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Covers columns hiding/unhiding on changes of <see cref="ModSelectOverlay.IsValidMod"/>.
|
||||
/// </summary>
|
||||
@ -870,8 +891,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
// it is instrumental in the reproduction of the failure scenario that this test is supposed to cover.
|
||||
AddStep("force collection", GC.Collect);
|
||||
|
||||
AddStep("open customisation area", () => modSelectOverlay.CustomisationButton!.TriggerClick());
|
||||
AddStep("reset half time speed to default", () => modSelectOverlay.ChildrenOfType<ModSettingsArea>().Single()
|
||||
AddStep("open customisation area", () => modSelectOverlay.ChildrenOfType<ModCustomisationHeader>().Single().TriggerClick());
|
||||
AddStep("reset half time speed to default", () => modSelectOverlay.ChildrenOfType<ModCustomisationPanel>().Single()
|
||||
.ChildrenOfType<RevertToDefaultButton<double>>().Single().TriggerClick());
|
||||
AddUntilStep("difficulty multiplier display shows correct value",
|
||||
() => modSelectOverlay.ChildrenOfType<RankingInformationDisplay>().Single().ModMultiplier.Value, () => Is.EqualTo(0.3).Within(Precision.DOUBLE_EPSILON));
|
||||
@ -883,24 +904,91 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
createScreen();
|
||||
|
||||
AddStep("select DT + HD + DF", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime(), new OsuModHidden(), new OsuModDeflate() });
|
||||
AddStep("open customisation panel", () => this.ChildrenOfType<ModCustomisationHeader>().Single().TriggerClick());
|
||||
AddAssert("mod settings order: DT, HD, DF", () =>
|
||||
{
|
||||
var columns = this.ChildrenOfType<ModSettingsArea>().Single().ChildrenOfType<ModSettingsArea.ModSettingsColumn>();
|
||||
var columns = this.ChildrenOfType<ModCustomisationSection>();
|
||||
return columns.ElementAt(0).Mod is OsuModDoubleTime &&
|
||||
columns.ElementAt(1).Mod is OsuModHidden &&
|
||||
columns.ElementAt(2).Mod is OsuModDeflate;
|
||||
});
|
||||
|
||||
AddStep("replace DT with NC", () => SelectedMods.Value = SelectedMods.Value.Where(m => m is not ModDoubleTime).Append(new OsuModNightcore()).ToList());
|
||||
AddStep("replace DT with NC", () =>
|
||||
{
|
||||
SelectedMods.Value = SelectedMods.Value.Where(m => m is not ModDoubleTime).Append(new OsuModNightcore()).ToList();
|
||||
this.ChildrenOfType<ModCustomisationHeader>().Single().TriggerClick();
|
||||
});
|
||||
AddAssert("mod settings order: NC, HD, DF", () =>
|
||||
{
|
||||
var columns = this.ChildrenOfType<ModSettingsArea>().Single().ChildrenOfType<ModSettingsArea.ModSettingsColumn>();
|
||||
var columns = this.ChildrenOfType<ModCustomisationSection>();
|
||||
return columns.ElementAt(0).Mod is OsuModNightcore &&
|
||||
columns.ElementAt(1).Mod is OsuModHidden &&
|
||||
columns.ElementAt(2).Mod is OsuModDeflate;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestOpeningCustomisationHidesPresetPopover()
|
||||
{
|
||||
createScreen();
|
||||
|
||||
AddStep("select DT", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime() });
|
||||
AddStep("click new preset", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<AddPresetButton>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddAssert("preset popover shown", () => this.ChildrenOfType<AddPresetPopover>().SingleOrDefault()?.IsPresent, () => Is.True);
|
||||
|
||||
AddStep("click customisation header", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<ModCustomisationHeader>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddUntilStep("preset popover hidden", () => this.ChildrenOfType<AddPresetPopover>().SingleOrDefault()?.IsPresent, () => Is.Not.True);
|
||||
AddAssert("customisation panel shown", () => this.ChildrenOfType<ModCustomisationPanel>().Single().State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCustomisationPanelAbsorbsInput([Values] bool textSearchStartsActive)
|
||||
{
|
||||
AddStep($"text search starts active = {textSearchStartsActive}", () => configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, textSearchStartsActive));
|
||||
createScreen();
|
||||
|
||||
AddStep("select DT", () => SelectedMods.Value = new Mod[] { new OsuModDoubleTime() });
|
||||
AddStep("open customisation panel", () => this.ChildrenOfType<ModCustomisationHeader>().Single().TriggerClick());
|
||||
AddAssert("search lost focus", () => !this.ChildrenOfType<ShearedSearchTextBox>().Single().HasFocus);
|
||||
|
||||
AddStep("press tab", () => InputManager.Key(Key.Tab));
|
||||
AddAssert("search still not focused", () => !this.ChildrenOfType<ShearedSearchTextBox>().Single().HasFocus);
|
||||
|
||||
AddStep("press q", () => InputManager.Key(Key.Q));
|
||||
AddAssert("easy not selected", () => SelectedMods.Value.Single() is OsuModDoubleTime);
|
||||
|
||||
// the "deselect all mods" action is intentionally disabled when customisation panel is open to not conflict with pressing backspace to delete characters in a textbox.
|
||||
// this is supposed to be handled by the textbox itself especially since it's focused and thus prioritised in input queue,
|
||||
// but it's not for some reason, and figuring out why is probably not going to be a pleasant experience (read TextBox.OnKeyDown for a head start).
|
||||
AddStep("press backspace", () => InputManager.Key(Key.BackSpace));
|
||||
AddAssert("mods not deselected", () => SelectedMods.Value.Single() is OsuModDoubleTime);
|
||||
|
||||
AddStep("move mouse to scroll bar", () => InputManager.MoveMouseTo(modSelectOverlay.ChildrenOfType<ModSelectOverlay.ColumnScrollContainer>().Single().ScreenSpaceDrawQuad.BottomLeft + new Vector2(10f, -5f)));
|
||||
|
||||
AddStep("scroll down", () => InputManager.ScrollVerticalBy(-10f));
|
||||
AddAssert("column not scrolled", () => modSelectOverlay.ChildrenOfType<ModSelectOverlay.ColumnScrollContainer>().Single().IsScrolledToStart());
|
||||
|
||||
AddStep("press mouse", () => InputManager.PressButton(MouseButton.Left));
|
||||
AddAssert("search still not focused", () => !this.ChildrenOfType<ShearedSearchTextBox>().Single().HasFocus);
|
||||
AddStep("release mouse", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
AddAssert("customisation panel closed by click", () => !this.ChildrenOfType<ModCustomisationPanel>().Single().Expanded.Value);
|
||||
|
||||
if (textSearchStartsActive)
|
||||
AddAssert("search focused", () => this.ChildrenOfType<ShearedSearchTextBox>().Single().HasFocus);
|
||||
else
|
||||
AddAssert("search still not focused", () => !this.ChildrenOfType<ShearedSearchTextBox>().Single().HasFocus);
|
||||
}
|
||||
|
||||
private void waitForColumnLoad() => AddUntilStep("all column content loaded", () =>
|
||||
modSelectOverlay.ChildrenOfType<ModColumn>().Any()
|
||||
&& modSelectOverlay.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded)
|
||||
@ -915,8 +1003,8 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
private void assertCustomisationToggleState(bool disabled, bool active)
|
||||
{
|
||||
AddAssert($"customisation toggle is {(disabled ? "" : "not ")}disabled", () => modSelectOverlay.CustomisationButton.AsNonNull().Active.Disabled == disabled);
|
||||
AddAssert($"customisation toggle is {(active ? "" : "not ")}active", () => modSelectOverlay.CustomisationButton.AsNonNull().Active.Value == active);
|
||||
AddUntilStep($"customisation panel is {(disabled ? "" : "not ")}disabled", () => modSelectOverlay.ChildrenOfType<ModCustomisationPanel>().Single().Enabled.Value == !disabled);
|
||||
AddAssert($"customisation panel is {(active ? "" : "not ")}active", () => modSelectOverlay.ChildrenOfType<ModCustomisationPanel>().Single().Expanded.Value == active);
|
||||
}
|
||||
|
||||
private T getSelectedMod<T>() where T : Mod => SelectedMods.Value.OfType<T>().Single();
|
||||
@ -929,7 +1017,6 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
protected override bool ShowPresets => true;
|
||||
|
||||
public new ShearedButton BackButton => base.BackButton;
|
||||
public new ShearedToggleButton? CustomisationButton => base.CustomisationButton;
|
||||
}
|
||||
|
||||
private class TestUnimplementedMod : Mod
|
||||
|
@ -1,42 +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.
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
[TestFixture]
|
||||
public partial class TestSceneModSettingsArea : OsuTestScene
|
||||
{
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Green);
|
||||
|
||||
[Test]
|
||||
public void TestModToggleArea()
|
||||
{
|
||||
ModSettingsArea modSettingsArea = null;
|
||||
|
||||
AddStep("create content", () => Child = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Child = modSettingsArea = new ModSettingsArea()
|
||||
});
|
||||
AddStep("set DT", () => modSettingsArea.SelectedMods.Value = new[] { new OsuModDoubleTime() });
|
||||
AddStep("set DA", () => modSettingsArea.SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() });
|
||||
AddStep("set FL+WU+DA+AD", () => modSettingsArea.SelectedMods.Value = new Mod[] { new OsuModFlashlight(), new ModWindUp(), new OsuModDifficultyAdjust(), new OsuModApproachDifferent() });
|
||||
AddStep("set empty", () => modSettingsArea.SelectedMods.Value = Array.Empty<Mod>());
|
||||
}
|
||||
}
|
||||
}
|
@ -75,6 +75,16 @@ namespace osu.Game.Localisation
|
||||
/// </summary>
|
||||
public static LocalisableString UnrankedExplanation => new TranslatableString(getKey(@"unranked_explanation"), @"Performance points will not be granted due to active mods.");
|
||||
|
||||
/// <summary>
|
||||
/// "Customise"
|
||||
/// </summary>
|
||||
public static LocalisableString CustomisationPanelHeader => new TranslatableString(getKey(@"customisation_panel_header"), @"Customise");
|
||||
|
||||
/// <summary>
|
||||
/// "No mod selected which can be customised."
|
||||
/// </summary>
|
||||
public static LocalisableString CustomisationPanelDisabledReason => new TranslatableString(getKey(@"customisation_panel_disabled_reason"), @"No mod selected which can be customised.");
|
||||
|
||||
private static string getKey(string key) => $@"{prefix}:{key}";
|
||||
}
|
||||
}
|
||||
|
95
osu.Game/Overlays/Mods/ModCustomisationHeader.cs
Normal file
95
osu.Game/Overlays/Mods/ModCustomisationHeader.cs
Normal file
@ -0,0 +1,95 @@
|
||||
// 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.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osuTK;
|
||||
using osu.Game.Localisation;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
public partial class ModCustomisationHeader : OsuHoverContainer
|
||||
{
|
||||
private Box background = null!;
|
||||
private SpriteIcon icon = null!;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
protected override IEnumerable<Drawable> EffectTargets => new[] { background };
|
||||
|
||||
public readonly BindableBool Expanded = new BindableBool();
|
||||
|
||||
public ModCustomisationHeader()
|
||||
{
|
||||
Action = Expanded.Toggle;
|
||||
Enabled.Value = false;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
CornerRadius = 10f;
|
||||
Masking = true;
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Text = ModSelectOverlayStrings.CustomisationPanelHeader,
|
||||
UseFullGlyphHeight = false,
|
||||
Font = OsuFont.Torus.With(size: 20f, weight: FontWeight.SemiBold),
|
||||
Margin = new MarginPadding { Left = 20f },
|
||||
},
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Size = new Vector2(16f),
|
||||
Margin = new MarginPadding { Right = 20f },
|
||||
Child = icon = new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Icon = FontAwesome.Solid.ChevronDown,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
IdleColour = colourProvider.Dark3;
|
||||
HoverColour = colourProvider.Light4;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Enabled.BindValueChanged(e =>
|
||||
{
|
||||
TooltipText = e.NewValue
|
||||
? string.Empty
|
||||
: ModSelectOverlayStrings.CustomisationPanelDisabledReason;
|
||||
}, true);
|
||||
|
||||
Expanded.BindValueChanged(v =>
|
||||
{
|
||||
icon.ScaleTo(v.NewValue ? new Vector2(1, -1) : Vector2.One, 300, Easing.OutQuint);
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
}
|
211
osu.Game/Overlays/Mods/ModCustomisationPanel.cs
Normal file
211
osu.Game/Overlays/Mods/ModCustomisationPanel.cs
Normal file
@ -0,0 +1,211 @@
|
||||
// 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.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
public partial class ModCustomisationPanel : OverlayContainer, IKeyBindingHandler<GlobalAction>
|
||||
{
|
||||
private const float header_height = 42f;
|
||||
private const float content_vertical_padding = 20f;
|
||||
private const float content_border_thickness = 2f;
|
||||
|
||||
private Container content = null!;
|
||||
private OsuScrollContainer scrollContainer = null!;
|
||||
private FillFlowContainer sectionsFlow = null!;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
public readonly BindableBool Enabled = new BindableBool();
|
||||
|
||||
public readonly BindableBool Expanded = new BindableBool();
|
||||
|
||||
public Bindable<IReadOnlyList<Mod>> SelectedMods { get; } = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||
|
||||
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
|
||||
|
||||
// Handle{Non}PositionalInput controls whether the panel should act as a blocking layer on the screen. only block when the panel is expanded.
|
||||
// These properties are used because they correctly handle blocking/unblocking hover when mouse is pointing at a drawable outside
|
||||
// (returning Expanded.Value to OnHover or overriding Block{Non}PositionalInput doesn't work).
|
||||
public override bool HandlePositionalInput => Expanded.Value;
|
||||
public override bool HandleNonPositionalInput => Expanded.Value;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new ModCustomisationHeader
|
||||
{
|
||||
Depth = float.MinValue,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = header_height,
|
||||
Enabled = { BindTarget = Enabled },
|
||||
Expanded = { BindTarget = Expanded },
|
||||
},
|
||||
content = new FocusGrabbingContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
BorderColour = colourProvider.Dark3,
|
||||
BorderThickness = content_border_thickness,
|
||||
CornerRadius = 10f,
|
||||
Masking = true,
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
Type = EdgeEffectType.Shadow,
|
||||
Offset = new Vector2(0f, 5f),
|
||||
Radius = 20f,
|
||||
Roundness = 5f,
|
||||
Colour = Color4.Black.Opacity(0.25f),
|
||||
},
|
||||
Expanded = { BindTarget = Expanded },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Dark4,
|
||||
},
|
||||
scrollContainer = new OsuScrollContainer(Direction.Vertical)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Margin = new MarginPadding
|
||||
{
|
||||
Top = header_height + content_border_thickness,
|
||||
Bottom = content_border_thickness
|
||||
},
|
||||
Child = sectionsFlow = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(0f, 40f),
|
||||
Margin = new MarginPadding
|
||||
{
|
||||
Top = content_vertical_padding,
|
||||
Bottom = 5f + content_vertical_padding
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Enabled.BindValueChanged(e =>
|
||||
{
|
||||
this.FadeColour(OsuColour.Gray(e.NewValue ? 1f : 0.6f), 300, Easing.OutQuint);
|
||||
}, true);
|
||||
|
||||
Expanded.BindValueChanged(_ => updateDisplay(), true);
|
||||
SelectedMods.BindValueChanged(_ => updateMods(), true);
|
||||
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
protected override void PopIn() => this.FadeIn(300, Easing.OutQuint);
|
||||
|
||||
protected override void PopOut() => this.FadeOut(300, Easing.OutQuint);
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
Expanded.Value = false;
|
||||
return base.OnClick(e);
|
||||
}
|
||||
|
||||
protected override bool OnKeyDown(KeyDownEvent e) => true;
|
||||
|
||||
protected override bool OnScroll(ScrollEvent e) => true;
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
switch (e.Action)
|
||||
{
|
||||
case GlobalAction.Back:
|
||||
Expanded.Value = false;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
|
||||
{
|
||||
}
|
||||
|
||||
private void updateDisplay()
|
||||
{
|
||||
content.ClearTransforms();
|
||||
|
||||
if (Expanded.Value)
|
||||
{
|
||||
content.AutoSizeDuration = 400;
|
||||
content.AutoSizeEasing = Easing.OutQuint;
|
||||
content.AutoSizeAxes = Axes.Y;
|
||||
content.FadeIn(120, Easing.OutQuint);
|
||||
}
|
||||
else
|
||||
{
|
||||
content.AutoSizeAxes = Axes.None;
|
||||
content.ResizeHeightTo(header_height, 400, Easing.OutQuint);
|
||||
content.FadeOut(400, Easing.OutSine);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateMods()
|
||||
{
|
||||
Expanded.Value = false;
|
||||
sectionsFlow.Clear();
|
||||
|
||||
// Importantly, the selected mods bindable is already ordered by the mod select overlay (following the order of mod columns and panels).
|
||||
// Using AsOrdered produces a slightly different order (e.g. DT and NC no longer becoming adjacent),
|
||||
// which breaks user expectations when interacting with the overlay.
|
||||
foreach (var mod in SelectedMods.Value)
|
||||
{
|
||||
var settings = mod.CreateSettingsControls().ToList();
|
||||
|
||||
if (settings.Count > 0)
|
||||
sectionsFlow.Add(new ModCustomisationSection(mod, settings));
|
||||
}
|
||||
}
|
||||
|
||||
protected override void Update()
|
||||
{
|
||||
base.Update();
|
||||
scrollContainer.Height = Math.Min(scrollContainer.AvailableContent, DrawHeight - header_height);
|
||||
}
|
||||
|
||||
private partial class FocusGrabbingContainer : InputBlockingContainer
|
||||
{
|
||||
public IBindable<bool> Expanded { get; } = new BindableBool();
|
||||
|
||||
public override bool RequestsFocus => Expanded.Value;
|
||||
public override bool AcceptsFocus => Expanded.Value;
|
||||
}
|
||||
}
|
||||
}
|
82
osu.Game/Overlays/Mods/ModCustomisationSection.cs
Normal file
82
osu.Game/Overlays/Mods/ModCustomisationSection.cs
Normal file
@ -0,0 +1,82 @@
|
||||
// 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.Collections.Generic;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
public partial class ModCustomisationSection : CompositeDrawable
|
||||
{
|
||||
public readonly Mod Mod;
|
||||
|
||||
private readonly IReadOnlyList<Drawable> settings;
|
||||
|
||||
public ModCustomisationSection(Mod mod, IReadOnlyList<Drawable> settings)
|
||||
{
|
||||
Mod = mod;
|
||||
|
||||
this.settings = settings;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
FillFlowContainer flow;
|
||||
|
||||
InternalChild = flow = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0f, 8f),
|
||||
Padding = new MarginPadding { Left = 7f },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Left = 20f, Right = 27f },
|
||||
Margin = new MarginPadding { Bottom = 4f },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Text = Mod.Name,
|
||||
Font = OsuFont.TorusAlternate.With(size: 20, weight: FontWeight.SemiBold),
|
||||
},
|
||||
new ModSwitchTiny(Mod)
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Active = { Value = true },
|
||||
Scale = new Vector2(0.5f),
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
flow.AddRange(settings);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
FinishTransforms(true);
|
||||
}
|
||||
}
|
||||
}
|
@ -29,6 +29,7 @@ using osu.Game.Localisation;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
@ -109,15 +110,6 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
protected virtual IEnumerable<ShearedButton> CreateFooterButtons()
|
||||
{
|
||||
if (AllowCustomisation)
|
||||
{
|
||||
yield return CustomisationButton = new ShearedToggleButton(BUTTON_WIDTH)
|
||||
{
|
||||
Text = ModSelectOverlayStrings.ModCustomisation,
|
||||
Active = { BindTarget = customisationVisible }
|
||||
};
|
||||
}
|
||||
|
||||
yield return deselectAllModsButton = new DeselectAllModsButton(this);
|
||||
}
|
||||
|
||||
@ -125,10 +117,8 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
public IEnumerable<ModState> AllAvailableMods => AvailableMods.Value.SelectMany(pair => pair.Value);
|
||||
|
||||
private readonly BindableBool customisationVisible = new BindableBool();
|
||||
private Bindable<bool> textSearchStartsActive = null!;
|
||||
|
||||
private ModSettingsArea modSettingsArea = null!;
|
||||
private ColumnScrollContainer columnScroll = null!;
|
||||
private ColumnFlowContainer columnFlow = null!;
|
||||
private FillFlowContainer<ShearedButton> footerButtonFlow = null!;
|
||||
@ -138,9 +128,9 @@ namespace osu.Game.Overlays.Mods
|
||||
private Container aboveColumnsContent = null!;
|
||||
private RankingInformationDisplay? rankingInformationDisplay;
|
||||
private BeatmapAttributesDisplay? beatmapAttributesDisplay;
|
||||
private ModCustomisationPanel customisationPanel = null!;
|
||||
|
||||
protected ShearedButton BackButton { get; private set; } = null!;
|
||||
protected ShearedToggleButton? CustomisationButton { get; private set; }
|
||||
protected SelectAllModsButton? SelectAllModsButton { get; set; }
|
||||
|
||||
private Sample? columnAppearSample;
|
||||
@ -173,70 +163,67 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
columnAppearSample = audio.Samples.Get(@"SongSelect/mod-column-pop-in");
|
||||
|
||||
AddRange(new Drawable[]
|
||||
MainAreaContent.Add(new OsuContextMenuContainer
|
||||
{
|
||||
new ClickToReturnContainer
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new PopoverContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
HandleMouse = { BindTarget = customisationVisible },
|
||||
OnClicked = () => customisationVisible.Value = false
|
||||
},
|
||||
modSettingsArea = new ModSettingsArea
|
||||
{
|
||||
Anchor = Anchor.BottomCentre,
|
||||
Origin = Anchor.BottomCentre,
|
||||
Height = 0
|
||||
},
|
||||
});
|
||||
|
||||
MainAreaContent.AddRange(new Drawable[]
|
||||
{
|
||||
aboveColumnsContent = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = RankingInformationDisplay.HEIGHT,
|
||||
Padding = new MarginPadding { Horizontal = 100 },
|
||||
Child = SearchTextBox = new ShearedSearchTextBox
|
||||
Children = new Drawable[]
|
||||
{
|
||||
HoldFocus = false,
|
||||
Width = 300
|
||||
}
|
||||
},
|
||||
new OsuContextMenuContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new PopoverContainer
|
||||
{
|
||||
Padding = new MarginPadding
|
||||
new Container
|
||||
{
|
||||
Top = RankingInformationDisplay.HEIGHT + PADDING,
|
||||
Bottom = PADDING
|
||||
},
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RelativePositionAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
columnScroll = new ColumnScrollContainer
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = false,
|
||||
ClampExtension = 100,
|
||||
ScrollbarOverlapsContent = false,
|
||||
Child = columnFlow = new ColumnFlowContainer
|
||||
Top = RankingInformationDisplay.HEIGHT + PADDING,
|
||||
Bottom = PADDING
|
||||
},
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RelativePositionAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
columnScroll = new ColumnScrollContainer
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Shear = new Vector2(OsuGame.SHEAR, 0),
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X,
|
||||
Margin = new MarginPadding { Horizontal = 70 },
|
||||
Padding = new MarginPadding { Bottom = 10 },
|
||||
ChildrenEnumerable = createColumns()
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = false,
|
||||
ClampExtension = 100,
|
||||
ScrollbarOverlapsContent = false,
|
||||
Child = columnFlow = new ColumnFlowContainer
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Shear = new Vector2(OsuGame.SHEAR, 0),
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
AutoSizeAxes = Axes.X,
|
||||
Margin = new MarginPadding { Horizontal = 70 },
|
||||
Padding = new MarginPadding { Bottom = 10 },
|
||||
ChildrenEnumerable = createColumns()
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
aboveColumnsContent = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding { Horizontal = 100, Bottom = 15f },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
SearchTextBox = new ShearedSearchTextBox
|
||||
{
|
||||
HoldFocus = false,
|
||||
Width = 300,
|
||||
},
|
||||
customisationPanel = new ModCustomisationPanel
|
||||
{
|
||||
Anchor = Anchor.TopRight,
|
||||
Origin = Anchor.TopRight,
|
||||
Width = 400,
|
||||
State = { Value = Visibility.Visible },
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
});
|
||||
|
||||
@ -320,7 +307,7 @@ namespace osu.Game.Overlays.Mods
|
||||
// This is an optimisation to prevent refreshing the available settings controls when it can be
|
||||
// reasonably assumed that the settings panel is never to be displayed (e.g. FreeModSelectOverlay).
|
||||
if (AllowCustomisation)
|
||||
((IBindable<IReadOnlyList<Mod>>)modSettingsArea.SelectedMods).BindTo(SelectedMods);
|
||||
((IBindable<IReadOnlyList<Mod>>)customisationPanel.SelectedMods).BindTo(SelectedMods);
|
||||
|
||||
SelectedMods.BindValueChanged(_ =>
|
||||
{
|
||||
@ -347,7 +334,7 @@ namespace osu.Game.Overlays.Mods
|
||||
}
|
||||
}, true);
|
||||
|
||||
customisationVisible.BindValueChanged(_ => updateCustomisationVisualState(), true);
|
||||
customisationPanel.Expanded.BindValueChanged(_ => updateCustomisationVisualState(), true);
|
||||
|
||||
SearchTextBox.Current.BindValueChanged(query =>
|
||||
{
|
||||
@ -390,6 +377,7 @@ namespace osu.Game.Overlays.Mods
|
||||
footerContentFlow.LayoutDuration = 200;
|
||||
footerContentFlow.LayoutEasing = Easing.OutQuint;
|
||||
footerContentFlow.Direction = screenIsntWideEnough ? FillDirection.Vertical : FillDirection.Horizontal;
|
||||
aboveColumnsContent.Padding = aboveColumnsContent.Padding with { Bottom = screenIsntWideEnough ? 70f : 15f };
|
||||
}
|
||||
}
|
||||
|
||||
@ -491,7 +479,7 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
private void updateCustomisation()
|
||||
{
|
||||
if (CustomisationButton == null)
|
||||
if (!AllowCustomisation)
|
||||
return;
|
||||
|
||||
bool anyCustomisableModActive = false;
|
||||
@ -506,41 +494,32 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
if (anyCustomisableModActive)
|
||||
{
|
||||
customisationVisible.Disabled = false;
|
||||
customisationPanel.Enabled.Value = true;
|
||||
|
||||
if (anyModPendingConfiguration && !customisationVisible.Value)
|
||||
customisationVisible.Value = true;
|
||||
if (anyModPendingConfiguration)
|
||||
customisationPanel.Expanded.Value = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (customisationVisible.Value)
|
||||
customisationVisible.Value = false;
|
||||
|
||||
customisationVisible.Disabled = true;
|
||||
customisationPanel.Expanded.Value = false;
|
||||
customisationPanel.Enabled.Value = false;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateCustomisationVisualState()
|
||||
{
|
||||
const double transition_duration = 300;
|
||||
|
||||
MainAreaContent.FadeColour(customisationVisible.Value ? Colour4.Gray : Colour4.White, transition_duration, Easing.InOutCubic);
|
||||
|
||||
foreach (var button in footerButtonFlow)
|
||||
if (customisationPanel.Expanded.Value)
|
||||
{
|
||||
if (button != CustomisationButton)
|
||||
button.Enabled.Value = !customisationVisible.Value;
|
||||
}
|
||||
|
||||
float modAreaHeight = customisationVisible.Value ? ModSettingsArea.HEIGHT : 0;
|
||||
|
||||
modSettingsArea.ResizeHeightTo(modAreaHeight, transition_duration, Easing.InOutCubic);
|
||||
TopLevelContent.MoveToY(-modAreaHeight, transition_duration, Easing.InOutCubic);
|
||||
|
||||
if (customisationVisible.Value)
|
||||
columnScroll.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint);
|
||||
SearchTextBox.FadeColour(OsuColour.Gray(0.5f), 400, Easing.OutQuint);
|
||||
SearchTextBox.KillFocus();
|
||||
}
|
||||
else
|
||||
{
|
||||
columnScroll.FadeColour(Color4.White, 400, Easing.OutQuint);
|
||||
SearchTextBox.FadeColour(Color4.White, 400, Easing.OutQuint);
|
||||
setTextBoxFocus(textSearchStartsActive.Value);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@ -693,6 +672,8 @@ namespace osu.Game.Overlays.Mods
|
||||
if (!allFiltered)
|
||||
nonFilteredColumnCount += 1;
|
||||
}
|
||||
|
||||
customisationPanel.Expanded.Value = false;
|
||||
}
|
||||
|
||||
#endregion
|
||||
@ -706,16 +687,12 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
switch (e.Action)
|
||||
{
|
||||
// If the customisation panel is expanded, the back action will be handled by it first.
|
||||
case GlobalAction.Back:
|
||||
// Pressing the back binding should only go back one step at a time.
|
||||
hideOverlay(false);
|
||||
return true;
|
||||
|
||||
// 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.
|
||||
case GlobalAction.ToggleModSelection:
|
||||
// Pressing toggle should completely hide the overlay in one shot.
|
||||
hideOverlay(true);
|
||||
hideOverlay();
|
||||
return true;
|
||||
|
||||
// This is handled locally here due to conflicts in input handling between the search text box and the deselect all mods button.
|
||||
@ -723,7 +700,7 @@ namespace osu.Game.Overlays.Mods
|
||||
// wherein activating the binding will both change the contents of the search text box and deselect all mods.
|
||||
case GlobalAction.DeselectAllMods:
|
||||
{
|
||||
if (!SearchTextBox.HasFocus)
|
||||
if (!SearchTextBox.HasFocus && !customisationPanel.Expanded.Value)
|
||||
{
|
||||
deselectAllModsButton.TriggerClick();
|
||||
return true;
|
||||
@ -738,7 +715,7 @@ namespace osu.Game.Overlays.Mods
|
||||
// If there is no search in progress, it should exit the dialog (a bit weird, but this is the expectation from stable).
|
||||
if (string.IsNullOrEmpty(SearchTerm))
|
||||
{
|
||||
hideOverlay(true);
|
||||
hideOverlay();
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -756,19 +733,7 @@ namespace osu.Game.Overlays.Mods
|
||||
|
||||
return base.OnPressed(e);
|
||||
|
||||
void hideOverlay(bool immediate)
|
||||
{
|
||||
if (customisationVisible.Value)
|
||||
{
|
||||
Debug.Assert(CustomisationButton != null);
|
||||
CustomisationButton.TriggerClick();
|
||||
|
||||
if (!immediate)
|
||||
return;
|
||||
}
|
||||
|
||||
BackButton.TriggerClick();
|
||||
}
|
||||
void hideOverlay() => BackButton.TriggerClick();
|
||||
}
|
||||
|
||||
/// <inheritdoc cref="IKeyBindingHandler{PlatformAction}"/>
|
||||
@ -795,6 +760,9 @@ namespace osu.Game.Overlays.Mods
|
||||
if (e.Repeat || e.Key != Key.Tab)
|
||||
return false;
|
||||
|
||||
if (customisationPanel.Expanded.Value)
|
||||
return true;
|
||||
|
||||
// TODO: should probably eventually support typical platform search shortcuts (`Ctrl-F`, `/`)
|
||||
setTextBoxFocus(!SearchTextBox.HasFocus);
|
||||
return true;
|
||||
@ -967,38 +935,5 @@ namespace osu.Game.Overlays.Mods
|
||||
updateState();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A container which blocks and handles input, managing the "return from customisation" state change.
|
||||
/// </summary>
|
||||
private partial class ClickToReturnContainer : Container
|
||||
{
|
||||
public BindableBool HandleMouse { get; } = new BindableBool();
|
||||
|
||||
public Action? OnClicked { get; set; }
|
||||
|
||||
public override bool HandlePositionalInput => base.HandlePositionalInput && HandleMouse.Value;
|
||||
|
||||
protected override bool Handle(UIEvent e)
|
||||
{
|
||||
if (!HandleMouse.Value)
|
||||
return base.Handle(e);
|
||||
|
||||
switch (e)
|
||||
{
|
||||
case ClickEvent:
|
||||
OnClicked?.Invoke();
|
||||
return true;
|
||||
|
||||
case HoverEvent:
|
||||
return false;
|
||||
|
||||
case MouseEvent:
|
||||
return true;
|
||||
}
|
||||
|
||||
return base.Handle(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,189 +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 System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.UI;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Overlays.Mods
|
||||
{
|
||||
public partial class ModSettingsArea : CompositeDrawable
|
||||
{
|
||||
public Bindable<IReadOnlyList<Mod>> SelectedMods { get; } = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
|
||||
|
||||
public const float HEIGHT = 250;
|
||||
|
||||
private readonly Box background;
|
||||
private readonly FillFlowContainer modSettingsFlow;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
public override bool AcceptsFocus => true;
|
||||
|
||||
public ModSettingsArea()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = HEIGHT;
|
||||
|
||||
Anchor = Anchor.BottomRight;
|
||||
Origin = Anchor.BottomRight;
|
||||
|
||||
InternalChild = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Masking = true,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both
|
||||
},
|
||||
new OsuScrollContainer(Direction.Horizontal)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ScrollbarOverlapsContent = false,
|
||||
ClampExtension = 100,
|
||||
Child = modSettingsFlow = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.X,
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Vertical = 7, Horizontal = 70 },
|
||||
Spacing = new Vector2(7),
|
||||
Direction = FillDirection.Horizontal
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
background.Colour = colourProvider.Dark3;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
SelectedMods.BindValueChanged(_ => updateMods(), true);
|
||||
}
|
||||
|
||||
private void updateMods()
|
||||
{
|
||||
modSettingsFlow.Clear();
|
||||
|
||||
// Importantly, the selected mods bindable is already ordered by the mod select overlay (following the order of mod columns and panels).
|
||||
// Using AsOrdered produces a slightly different order (e.g. DT and NC no longer becoming adjacent),
|
||||
// which breaks user expectations when interacting with the overlay.
|
||||
foreach (var mod in SelectedMods.Value)
|
||||
{
|
||||
var settings = mod.CreateSettingsControls().ToList();
|
||||
|
||||
if (settings.Count > 0)
|
||||
{
|
||||
if (modSettingsFlow.Any())
|
||||
{
|
||||
modSettingsFlow.Add(new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y,
|
||||
Width = 2,
|
||||
Colour = colourProvider.Dark4,
|
||||
});
|
||||
}
|
||||
|
||||
modSettingsFlow.Add(new ModSettingsColumn(mod, settings));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected override bool OnMouseDown(MouseDownEvent e) => true;
|
||||
protected override bool OnHover(HoverEvent e) => true;
|
||||
|
||||
public partial class ModSettingsColumn : CompositeDrawable
|
||||
{
|
||||
public readonly Mod Mod;
|
||||
|
||||
public ModSettingsColumn(Mod mod, IEnumerable<Drawable> settingsControls)
|
||||
{
|
||||
Mod = mod;
|
||||
|
||||
Width = 250;
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
Padding = new MarginPadding { Bottom = 7 };
|
||||
|
||||
InternalChild = new GridContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RowDimensions = new[]
|
||||
{
|
||||
new Dimension(GridSizeMode.AutoSize),
|
||||
new Dimension(GridSizeMode.Absolute, 10),
|
||||
new Dimension()
|
||||
},
|
||||
Content = new[]
|
||||
{
|
||||
new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(7),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new ModSwitchTiny(mod)
|
||||
{
|
||||
Active = { Value = true },
|
||||
Scale = new Vector2(0.6f),
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Text = mod.Name,
|
||||
Font = OsuFont.Default.With(size: 16, weight: FontWeight.SemiBold),
|
||||
Origin = Anchor.CentreLeft,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Margin = new MarginPadding { Bottom = 2 }
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
new[] { Empty() },
|
||||
new Drawable[]
|
||||
{
|
||||
new OsuScrollContainer(Direction.Vertical)
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
ClampExtension = 100,
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Right = 7 },
|
||||
ChildrenEnumerable = settingsControls,
|
||||
Spacing = new Vector2(0, 7)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user