1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-14 09:32:55 +08:00

Add different display for mod customisation

This commit is contained in:
Salman Ahmed 2024-06-23 08:22:13 +03:00
parent 3bf742bda2
commit 04efa61156
5 changed files with 381 additions and 0 deletions

View File

@ -0,0 +1,47 @@
// 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);
[SetUp]
public void SetUp() => Schedule(() =>
{
Child = new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding(20f),
Child = new ModCustomisationPanel
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Width = 400f,
SelectedMods = { BindTarget = SelectedMods },
}
};
});
[Test]
public void TestDisplay()
{
AddStep("set DT", () => SelectedMods.Value = new[] { new OsuModDoubleTime() });
AddStep("set DA", () => SelectedMods.Value = new Mod[] { new OsuModDifficultyAdjust() });
AddStep("set FL+WU+DA+AD", () => SelectedMods.Value = new Mod[] { new OsuModFlashlight(), new ModWindUp(), new OsuModDifficultyAdjust(), new OsuModApproachDifferent() });
AddStep("set empty", () => SelectedMods.Value = Array.Empty<Mod>());
}
}
}

View File

@ -75,6 +75,11 @@ 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");
private static string getKey(string key) => $@"{prefix}:{key}";
}
}

View File

@ -0,0 +1,86 @@
// 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
{
public override bool HandlePositionalInput => true;
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();
[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();
Expanded.BindValueChanged(v =>
{
icon.RotateTo(v.NewValue ? 180 : 0);
}, true);
Action = Expanded.Toggle;
}
}
}

View File

@ -0,0 +1,161 @@
// 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.Effects;
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.Rulesets.Mods;
using osuTK;
namespace osu.Game.Overlays.Mods
{
public partial class ModCustomisationPanel : VisibilityContainer
{
private const float header_height = 42f;
private const float content_vertical_padding = 20f;
private Container content = null!;
private OsuScrollContainer scrollContainer = null!;
private FillFlowContainer sectionsFlow = null!;
[Resolved]
private OverlayColourProvider colourProvider { get; set; } = null!;
public readonly BindableBool Expanded = new BindableBool();
public Bindable<IReadOnlyList<Mod>> SelectedMods { get; } = new Bindable<IReadOnlyList<Mod>>(Array.Empty<Mod>());
[BackgroundDependencyLoader]
private void load()
{
RelativeSizeAxes = Axes.Y;
InternalChildren = new Drawable[]
{
new ModCustomisationHeader
{
Depth = float.MinValue,
RelativeSizeAxes = Axes.X,
Height = header_height,
Expanded = { BindTarget = Expanded },
},
content = new InputBlockingContainer
{
RelativeSizeAxes = Axes.X,
BorderColour = colourProvider.Dark3,
BorderThickness = 2f,
CornerRadius = 10f,
Masking = true,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Offset = new Vector2(0f, 5f),
Radius = 20f,
Roundness = 5f,
},
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colourProvider.Dark4,
},
scrollContainer = new OsuScrollContainer(Direction.Vertical)
{
RelativeSizeAxes = Axes.X,
ScrollbarOverlapsContent = false,
Margin = new MarginPadding { Top = header_height },
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();
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);
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => true;
protected override bool OnMouseDown(MouseDownEvent e)
{
if (Expanded.Value && !content.ReceivePositionalInputAt(e.ScreenSpaceMousePosition))
Expanded.Value = false;
return base.OnMouseDown(e);
}
private void updateDisplay()
{
content.ClearTransforms();
if (Expanded.Value)
{
content.AutoSizeDuration = 400;
content.AutoSizeEasing = Easing.OutQuint;
content.AutoSizeAxes = Axes.Y;
content.FadeEdgeEffectTo(0.25f, 120, Easing.OutQuint);
}
else
{
content.AutoSizeAxes = Axes.None;
content.ResizeHeightTo(header_height, 400, Easing.OutQuint);
content.FadeEdgeEffectTo(0f, 400, Easing.OutQuint);
}
}
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);
}
}
}

View 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);
}
}
}