mirror of
https://github.com/ppy/osu.git
synced 2024-11-14 16:37:26 +08:00
Merge pull request #11666 from smoogipoo/freemod-select-overlay
Implement the freemod selection overlay
This commit is contained in:
commit
4730cf02d0
@ -0,0 +1,21 @@
|
|||||||
|
// 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.Screens.OnlinePlay;
|
||||||
|
|
||||||
|
namespace osu.Game.Tests.Visual.Multiplayer
|
||||||
|
{
|
||||||
|
public class TestSceneFreeModSelectOverlay : MultiplayerTestScene
|
||||||
|
{
|
||||||
|
[SetUp]
|
||||||
|
public new void Setup() => Schedule(() =>
|
||||||
|
{
|
||||||
|
Child = new FreeModSelectOverlay
|
||||||
|
{
|
||||||
|
State = { Value = Visibility.Visible }
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -8,6 +8,7 @@ using NUnit.Framework;
|
|||||||
using osu.Framework.Allocation;
|
using osu.Framework.Allocation;
|
||||||
using osu.Framework.Bindables;
|
using osu.Framework.Bindables;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Containers;
|
||||||
using osu.Framework.Testing;
|
using osu.Framework.Testing;
|
||||||
using osu.Game.Graphics.Sprites;
|
using osu.Game.Graphics.Sprites;
|
||||||
using osu.Game.Graphics.UserInterface;
|
using osu.Game.Graphics.UserInterface;
|
||||||
@ -46,6 +47,32 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddStep("show", () => modSelect.Show());
|
AddStep("show", () => modSelect.Show());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Test]
|
||||||
|
public void TestAnimationFlushOnClose()
|
||||||
|
{
|
||||||
|
changeRuleset(0);
|
||||||
|
|
||||||
|
AddStep("Select all fun mods", () =>
|
||||||
|
{
|
||||||
|
modSelect.ModSectionsContainer
|
||||||
|
.Single(c => c.ModType == ModType.DifficultyIncrease)
|
||||||
|
.SelectAll();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddUntilStep("many mods selected", () => modDisplay.Current.Value.Count >= 5);
|
||||||
|
|
||||||
|
AddStep("trigger deselect and close overlay", () =>
|
||||||
|
{
|
||||||
|
modSelect.ModSectionsContainer
|
||||||
|
.Single(c => c.ModType == ModType.DifficultyIncrease)
|
||||||
|
.DeselectAll();
|
||||||
|
|
||||||
|
modSelect.Hide();
|
||||||
|
});
|
||||||
|
|
||||||
|
AddAssert("all mods deselected", () => modDisplay.Current.Value.Count == 0);
|
||||||
|
}
|
||||||
|
|
||||||
[Test]
|
[Test]
|
||||||
public void TestOsuMods()
|
public void TestOsuMods()
|
||||||
{
|
{
|
||||||
@ -145,11 +172,11 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
AddAssert("double time visible", () => modSelect.ChildrenOfType<ModButton>().Any(b => b.Mods.Any(m => m is OsuModDoubleTime)));
|
AddAssert("double time visible", () => modSelect.ChildrenOfType<ModButton>().Any(b => b.Mods.Any(m => m is OsuModDoubleTime)));
|
||||||
|
|
||||||
AddStep("make double time invalid", () => modSelect.IsValidMod = m => !(m is OsuModDoubleTime));
|
AddStep("make double time invalid", () => modSelect.IsValidMod = m => !(m is OsuModDoubleTime));
|
||||||
AddAssert("double time not visible", () => modSelect.ChildrenOfType<ModButton>().All(b => !b.Mods.Any(m => m is OsuModDoubleTime)));
|
AddUntilStep("double time not visible", () => modSelect.ChildrenOfType<ModButton>().All(b => !b.Mods.Any(m => m is OsuModDoubleTime)));
|
||||||
AddAssert("nightcore still visible", () => modSelect.ChildrenOfType<ModButton>().Any(b => b.Mods.Any(m => m is OsuModNightcore)));
|
AddAssert("nightcore still visible", () => modSelect.ChildrenOfType<ModButton>().Any(b => b.Mods.Any(m => m is OsuModNightcore)));
|
||||||
|
|
||||||
AddStep("make double time valid again", () => modSelect.IsValidMod = m => true);
|
AddStep("make double time valid again", () => modSelect.IsValidMod = m => true);
|
||||||
AddAssert("double time visible", () => modSelect.ChildrenOfType<ModButton>().Any(b => b.Mods.Any(m => m is OsuModDoubleTime)));
|
AddUntilStep("double time visible", () => modSelect.ChildrenOfType<ModButton>().Any(b => b.Mods.Any(m => m is OsuModDoubleTime)));
|
||||||
AddAssert("nightcore still visible", () => modSelect.ChildrenOfType<ModButton>().Any(b => b.Mods.Any(m => m is OsuModNightcore)));
|
AddAssert("nightcore still visible", () => modSelect.ChildrenOfType<ModButton>().Any(b => b.Mods.Any(m => m is OsuModNightcore)));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -312,6 +339,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
|||||||
|
|
||||||
public bool AllLoaded => ModSectionsContainer.Children.All(c => c.ModIconsLoaded);
|
public bool AllLoaded => ModSectionsContainer.Children.All(c => c.ModIconsLoaded);
|
||||||
|
|
||||||
|
public new FillFlowContainer<ModSection> ModSectionsContainer =>
|
||||||
|
base.ModSectionsContainer;
|
||||||
|
|
||||||
public ModButton GetModButton(Mod mod)
|
public ModButton GetModButton(Mod mod)
|
||||||
{
|
{
|
||||||
var section = ModSectionsContainer.Children.Single(s => s.ModType == mod.Type);
|
var section = ModSectionsContainer.Children.Single(s => s.ModType == mod.Type);
|
||||||
|
@ -5,6 +5,7 @@ using osu.Framework.Allocation;
|
|||||||
using osu.Framework.Audio;
|
using osu.Framework.Audio;
|
||||||
using osu.Framework.Audio.Sample;
|
using osu.Framework.Audio.Sample;
|
||||||
using osu.Framework.Graphics;
|
using osu.Framework.Graphics;
|
||||||
|
using osu.Framework.Graphics.Sprites;
|
||||||
using osu.Framework.Graphics.UserInterface;
|
using osu.Framework.Graphics.UserInterface;
|
||||||
using osu.Framework.Input.Events;
|
using osu.Framework.Input.Events;
|
||||||
using osu.Game.Graphics.Containers;
|
using osu.Game.Graphics.Containers;
|
||||||
@ -18,6 +19,11 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
public Color4 UncheckedColor { get; set; } = Color4.White;
|
public Color4 UncheckedColor { get; set; } = Color4.White;
|
||||||
public int FadeDuration { get; set; }
|
public int FadeDuration { get; set; }
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Whether to play sounds when the state changes as a result of user interaction.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual bool PlaySoundsOnUserChange => true;
|
||||||
|
|
||||||
public string LabelText
|
public string LabelText
|
||||||
{
|
{
|
||||||
set
|
set
|
||||||
@ -43,7 +49,7 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
private SampleChannel sampleChecked;
|
private SampleChannel sampleChecked;
|
||||||
private SampleChannel sampleUnchecked;
|
private SampleChannel sampleUnchecked;
|
||||||
|
|
||||||
public OsuCheckbox()
|
public OsuCheckbox(bool nubOnRight = true)
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Y;
|
AutoSizeAxes = Axes.Y;
|
||||||
RelativeSizeAxes = Axes.X;
|
RelativeSizeAxes = Axes.X;
|
||||||
@ -52,26 +58,42 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
|
|
||||||
Children = new Drawable[]
|
Children = new Drawable[]
|
||||||
{
|
{
|
||||||
labelText = new OsuTextFlowContainer
|
labelText = new OsuTextFlowContainer(ApplyLabelParameters)
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Y,
|
AutoSizeAxes = Axes.Y,
|
||||||
RelativeSizeAxes = Axes.X,
|
RelativeSizeAxes = Axes.X,
|
||||||
Padding = new MarginPadding { Right = Nub.EXPANDED_SIZE + nub_padding }
|
|
||||||
},
|
|
||||||
Nub = new Nub
|
|
||||||
{
|
|
||||||
Anchor = Anchor.CentreRight,
|
|
||||||
Origin = Anchor.CentreRight,
|
|
||||||
Margin = new MarginPadding { Right = nub_padding },
|
|
||||||
},
|
},
|
||||||
|
Nub = new Nub(),
|
||||||
new HoverClickSounds()
|
new HoverClickSounds()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (nubOnRight)
|
||||||
|
{
|
||||||
|
Nub.Anchor = Anchor.CentreRight;
|
||||||
|
Nub.Origin = Anchor.CentreRight;
|
||||||
|
Nub.Margin = new MarginPadding { Right = nub_padding };
|
||||||
|
labelText.Padding = new MarginPadding { Right = Nub.EXPANDED_SIZE + nub_padding * 2 };
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
Nub.Anchor = Anchor.CentreLeft;
|
||||||
|
Nub.Origin = Anchor.CentreLeft;
|
||||||
|
Nub.Margin = new MarginPadding { Left = nub_padding };
|
||||||
|
labelText.Padding = new MarginPadding { Left = Nub.EXPANDED_SIZE + nub_padding * 2 };
|
||||||
|
}
|
||||||
|
|
||||||
Nub.Current.BindTo(Current);
|
Nub.Current.BindTo(Current);
|
||||||
|
|
||||||
Current.DisabledChanged += disabled => labelText.Alpha = Nub.Alpha = disabled ? 0.3f : 1;
|
Current.DisabledChanged += disabled => labelText.Alpha = Nub.Alpha = disabled ? 0.3f : 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// A function which can be overridden to change the parameters of the label's text.
|
||||||
|
/// </summary>
|
||||||
|
protected virtual void ApplyLabelParameters(SpriteText text)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
[BackgroundDependencyLoader]
|
[BackgroundDependencyLoader]
|
||||||
private void load(AudioManager audio)
|
private void load(AudioManager audio)
|
||||||
{
|
{
|
||||||
@ -96,6 +118,9 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
protected override void OnUserChange(bool value)
|
protected override void OnUserChange(bool value)
|
||||||
{
|
{
|
||||||
base.OnUserChange(value);
|
base.OnUserChange(value);
|
||||||
|
|
||||||
|
if (PlaySoundsOnUserChange)
|
||||||
|
{
|
||||||
if (value)
|
if (value)
|
||||||
sampleChecked?.Play();
|
sampleChecked?.Play();
|
||||||
else
|
else
|
||||||
@ -103,3 +128,4 @@ namespace osu.Game.Graphics.UserInterface
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -33,6 +33,8 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
private CancellationTokenSource modsLoadCts;
|
private CancellationTokenSource modsLoadCts;
|
||||||
|
|
||||||
|
protected bool SelectionAnimationRunning => pendingSelectionOperations.Count > 0;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// True when all mod icons have completed loading.
|
/// True when all mod icons have completed loading.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@ -49,7 +51,11 @@ namespace osu.Game.Overlays.Mods
|
|||||||
|
|
||||||
return new ModButton(m)
|
return new ModButton(m)
|
||||||
{
|
{
|
||||||
SelectionChanged = Action,
|
SelectionChanged = mod =>
|
||||||
|
{
|
||||||
|
ModButtonStateChanged(mod);
|
||||||
|
Action?.Invoke(mod);
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}).ToArray();
|
}).ToArray();
|
||||||
|
|
||||||
@ -78,6 +84,10 @@ namespace osu.Game.Overlays.Mods
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected virtual void ModButtonStateChanged(Mod mod)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
private ModButton[] buttons = Array.Empty<ModButton>();
|
private ModButton[] buttons = Array.Empty<ModButton>();
|
||||||
|
|
||||||
protected override bool OnKeyDown(KeyDownEvent e)
|
protected override bool OnKeyDown(KeyDownEvent e)
|
||||||
@ -94,30 +104,75 @@ namespace osu.Game.Overlays.Mods
|
|||||||
return base.OnKeyDown(e);
|
return base.OnKeyDown(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void DeselectAll() => DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null));
|
private const double initial_multiple_selection_delay = 120;
|
||||||
|
|
||||||
|
private double selectionDelay = initial_multiple_selection_delay;
|
||||||
|
private double lastSelection;
|
||||||
|
|
||||||
|
private readonly Queue<Action> pendingSelectionOperations = new Queue<Action>();
|
||||||
|
|
||||||
|
protected override void Update()
|
||||||
|
{
|
||||||
|
base.Update();
|
||||||
|
|
||||||
|
if (selectionDelay == initial_multiple_selection_delay || Time.Current - lastSelection >= selectionDelay)
|
||||||
|
{
|
||||||
|
if (pendingSelectionOperations.TryDequeue(out var dequeuedAction))
|
||||||
|
{
|
||||||
|
dequeuedAction();
|
||||||
|
|
||||||
|
// each time we play an animation, we decrease the time until the next animation (to ramp the visual and audible elements).
|
||||||
|
selectionDelay = Math.Max(30, selectionDelay * 0.8f);
|
||||||
|
lastSelection = Time.Current;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// reset the selection delay after all animations have been completed.
|
||||||
|
// this will cause the next action to be immediately performed.
|
||||||
|
selectionDelay = initial_multiple_selection_delay;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Selects all mods.
|
||||||
|
/// </summary>
|
||||||
|
public void SelectAll()
|
||||||
|
{
|
||||||
|
pendingSelectionOperations.Clear();
|
||||||
|
|
||||||
|
foreach (var button in buttons.Where(b => !b.Selected))
|
||||||
|
pendingSelectionOperations.Enqueue(() => button.SelectAt(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Deselects all mods.
|
||||||
|
/// </summary>
|
||||||
|
public void DeselectAll()
|
||||||
|
{
|
||||||
|
pendingSelectionOperations.Clear();
|
||||||
|
DeselectTypes(buttons.Select(b => b.SelectedMod?.GetType()).Where(t => t != null));
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Deselect one or more mods in this section.
|
/// Deselect one or more mods in this section.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
/// <param name="modTypes">The types of <see cref="Mod"/>s which should be deselected.</param>
|
/// <param name="modTypes">The types of <see cref="Mod"/>s which should be deselected.</param>
|
||||||
/// <param name="immediate">Set to true to bypass animations and update selections immediately.</param>
|
/// <param name="immediate">Whether the deselection should happen immediately. Should only be used when required to ensure correct selection flow.</param>
|
||||||
public void DeselectTypes(IEnumerable<Type> modTypes, bool immediate = false)
|
public void DeselectTypes(IEnumerable<Type> modTypes, bool immediate = false)
|
||||||
{
|
{
|
||||||
int delay = 0;
|
|
||||||
|
|
||||||
foreach (var button in buttons)
|
foreach (var button in buttons)
|
||||||
{
|
{
|
||||||
Mod selected = button.SelectedMod;
|
if (button.SelectedMod == null) continue;
|
||||||
if (selected == null) continue;
|
|
||||||
|
|
||||||
foreach (var type in modTypes)
|
foreach (var type in modTypes)
|
||||||
{
|
{
|
||||||
if (type.IsInstanceOfType(selected))
|
if (type.IsInstanceOfType(button.SelectedMod))
|
||||||
{
|
{
|
||||||
if (immediate)
|
if (immediate)
|
||||||
button.Deselect();
|
button.Deselect();
|
||||||
else
|
else
|
||||||
Scheduler.AddDelayed(button.Deselect, delay += 50);
|
pendingSelectionOperations.Enqueue(button.Deselect);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -184,5 +239,14 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Font = OsuFont.GetFont(weight: FontWeight.Bold),
|
Font = OsuFont.GetFont(weight: FontWeight.Bold),
|
||||||
Text = text
|
Text = text
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Play out all remaining animations immediately to leave mods in a good (final) state.
|
||||||
|
/// </summary>
|
||||||
|
public void FlushAnimation()
|
||||||
|
{
|
||||||
|
while (pendingSelectionOperations.TryDequeue(out var dequeuedAction))
|
||||||
|
dequeuedAction();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,8 +37,11 @@ namespace osu.Game.Overlays.Mods
|
|||||||
protected readonly TriangleButton CustomiseButton;
|
protected readonly TriangleButton CustomiseButton;
|
||||||
protected readonly TriangleButton CloseButton;
|
protected readonly TriangleButton CloseButton;
|
||||||
|
|
||||||
|
protected readonly Drawable MultiplierSection;
|
||||||
protected readonly OsuSpriteText MultiplierLabel;
|
protected readonly OsuSpriteText MultiplierLabel;
|
||||||
|
|
||||||
|
protected readonly FillFlowContainer FooterContainer;
|
||||||
|
|
||||||
protected override bool BlockNonPositionalInput => false;
|
protected override bool BlockNonPositionalInput => false;
|
||||||
|
|
||||||
protected override bool DimMainContent => false;
|
protected override bool DimMainContent => false;
|
||||||
@ -79,8 +82,6 @@ namespace osu.Game.Overlays.Mods
|
|||||||
private const float content_width = 0.8f;
|
private const float content_width = 0.8f;
|
||||||
private const float footer_button_spacing = 20;
|
private const float footer_button_spacing = 20;
|
||||||
|
|
||||||
private readonly FillFlowContainer footerContainer;
|
|
||||||
|
|
||||||
private SampleChannel sampleOn, sampleOff;
|
private SampleChannel sampleOn, sampleOff;
|
||||||
|
|
||||||
protected ModSelectOverlay()
|
protected ModSelectOverlay()
|
||||||
@ -269,7 +270,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Colour = new Color4(172, 20, 116, 255),
|
Colour = new Color4(172, 20, 116, 255),
|
||||||
Alpha = 0.5f,
|
Alpha = 0.5f,
|
||||||
},
|
},
|
||||||
footerContainer = new FillFlowContainer
|
FooterContainer = new FillFlowContainer
|
||||||
{
|
{
|
||||||
Origin = Anchor.BottomCentre,
|
Origin = Anchor.BottomCentre,
|
||||||
Anchor = Anchor.BottomCentre,
|
Anchor = Anchor.BottomCentre,
|
||||||
@ -283,7 +284,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Vertical = 15,
|
Vertical = 15,
|
||||||
Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING
|
Horizontal = OsuScreen.HORIZONTAL_OVERFLOW_PADDING
|
||||||
},
|
},
|
||||||
Children = new Drawable[]
|
Children = new[]
|
||||||
{
|
{
|
||||||
DeselectAllButton = new TriangleButton
|
DeselectAllButton = new TriangleButton
|
||||||
{
|
{
|
||||||
@ -310,7 +311,7 @@ namespace osu.Game.Overlays.Mods
|
|||||||
Origin = Anchor.CentreLeft,
|
Origin = Anchor.CentreLeft,
|
||||||
Anchor = Anchor.CentreLeft,
|
Anchor = Anchor.CentreLeft,
|
||||||
},
|
},
|
||||||
new FillFlowContainer
|
MultiplierSection = new FillFlowContainer
|
||||||
{
|
{
|
||||||
AutoSizeAxes = Axes.Both,
|
AutoSizeAxes = Axes.Both,
|
||||||
Spacing = new Vector2(footer_button_spacing / 2, 0),
|
Spacing = new Vector2(footer_button_spacing / 2, 0),
|
||||||
@ -378,8 +379,13 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
base.PopOut();
|
base.PopOut();
|
||||||
|
|
||||||
footerContainer.MoveToX(content_width, WaveContainer.DISAPPEAR_DURATION, Easing.InSine);
|
foreach (var section in ModSectionsContainer)
|
||||||
footerContainer.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InSine);
|
{
|
||||||
|
section.FlushAnimation();
|
||||||
|
}
|
||||||
|
|
||||||
|
FooterContainer.MoveToX(content_width, WaveContainer.DISAPPEAR_DURATION, Easing.InSine);
|
||||||
|
FooterContainer.FadeOut(WaveContainer.DISAPPEAR_DURATION, Easing.InSine);
|
||||||
|
|
||||||
foreach (var section in ModSectionsContainer.Children)
|
foreach (var section in ModSectionsContainer.Children)
|
||||||
{
|
{
|
||||||
@ -393,8 +399,8 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
base.PopIn();
|
base.PopIn();
|
||||||
|
|
||||||
footerContainer.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint);
|
FooterContainer.MoveToX(0, WaveContainer.APPEAR_DURATION, Easing.OutQuint);
|
||||||
footerContainer.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint);
|
FooterContainer.FadeIn(WaveContainer.APPEAR_DURATION, Easing.OutQuint);
|
||||||
|
|
||||||
foreach (var section in ModSectionsContainer.Children)
|
foreach (var section in ModSectionsContainer.Children)
|
||||||
{
|
{
|
||||||
@ -498,7 +504,8 @@ namespace osu.Game.Overlays.Mods
|
|||||||
{
|
{
|
||||||
if (selectedMod != null)
|
if (selectedMod != null)
|
||||||
{
|
{
|
||||||
if (State.Value == Visibility.Visible) sampleOn?.Play();
|
if (State.Value == Visibility.Visible)
|
||||||
|
Scheduler.AddOnce(playSelectedSound);
|
||||||
|
|
||||||
OnModSelected(selectedMod);
|
OnModSelected(selectedMod);
|
||||||
|
|
||||||
@ -506,12 +513,16 @@ namespace osu.Game.Overlays.Mods
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if (State.Value == Visibility.Visible) sampleOff?.Play();
|
if (State.Value == Visibility.Visible)
|
||||||
|
Scheduler.AddOnce(playDeselectedSound);
|
||||||
}
|
}
|
||||||
|
|
||||||
refreshSelectedMods();
|
refreshSelectedMods();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void playSelectedSound() => sampleOn?.Play();
|
||||||
|
private void playDeselectedSound() => sampleOff?.Play();
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Invoked when a new <see cref="Mod"/> has been selected.
|
/// Invoked when a new <see cref="Mod"/> has been selected.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
|
145
osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs
Normal file
145
osu.Game/Screens/OnlinePlay/FreeModSelectOverlay.cs
Normal file
@ -0,0 +1,145 @@
|
|||||||
|
// 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.Graphics.Sprites;
|
||||||
|
using osu.Game.Graphics;
|
||||||
|
using osu.Game.Graphics.UserInterface;
|
||||||
|
using osu.Game.Overlays.Mods;
|
||||||
|
using osu.Game.Rulesets.Mods;
|
||||||
|
|
||||||
|
namespace osu.Game.Screens.OnlinePlay
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// A <see cref="ModSelectOverlay"/> used for free-mod selection in online play.
|
||||||
|
/// </summary>
|
||||||
|
public class FreeModSelectOverlay : ModSelectOverlay
|
||||||
|
{
|
||||||
|
protected override bool Stacked => false;
|
||||||
|
|
||||||
|
public new Func<Mod, bool> IsValidMod
|
||||||
|
{
|
||||||
|
get => base.IsValidMod;
|
||||||
|
set => base.IsValidMod = m => m.HasImplementation && !m.RequiresConfiguration && !(m is ModAutoplay) && value(m);
|
||||||
|
}
|
||||||
|
|
||||||
|
public FreeModSelectOverlay()
|
||||||
|
{
|
||||||
|
IsValidMod = m => true;
|
||||||
|
|
||||||
|
CustomiseButton.Alpha = 0;
|
||||||
|
MultiplierSection.Alpha = 0;
|
||||||
|
DeselectAllButton.Alpha = 0;
|
||||||
|
|
||||||
|
Drawable selectAllButton;
|
||||||
|
Drawable deselectAllButton;
|
||||||
|
|
||||||
|
FooterContainer.AddRange(new[]
|
||||||
|
{
|
||||||
|
selectAllButton = new TriangleButton
|
||||||
|
{
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Width = 180,
|
||||||
|
Text = "Select All",
|
||||||
|
Action = selectAll,
|
||||||
|
},
|
||||||
|
// Unlike the base mod select overlay, this button deselects mods instantaneously.
|
||||||
|
deselectAllButton = new TriangleButton
|
||||||
|
{
|
||||||
|
Origin = Anchor.CentreLeft,
|
||||||
|
Anchor = Anchor.CentreLeft,
|
||||||
|
Width = 180,
|
||||||
|
Text = "Deselect All",
|
||||||
|
Action = deselectAll,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
FooterContainer.SetLayoutPosition(selectAllButton, -2);
|
||||||
|
FooterContainer.SetLayoutPosition(deselectAllButton, -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectAll()
|
||||||
|
{
|
||||||
|
foreach (var section in ModSectionsContainer.Children)
|
||||||
|
section.SelectAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deselectAll()
|
||||||
|
{
|
||||||
|
foreach (var section in ModSectionsContainer.Children)
|
||||||
|
section.DeselectAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override ModSection CreateModSection(ModType type) => new FreeModSection(type);
|
||||||
|
|
||||||
|
private class FreeModSection : ModSection
|
||||||
|
{
|
||||||
|
private HeaderCheckbox checkbox;
|
||||||
|
|
||||||
|
public FreeModSection(ModType type)
|
||||||
|
: base(type)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override Drawable CreateHeader(string text) => new Container
|
||||||
|
{
|
||||||
|
AutoSizeAxes = Axes.Y,
|
||||||
|
RelativeSizeAxes = Axes.X,
|
||||||
|
Child = checkbox = new HeaderCheckbox
|
||||||
|
{
|
||||||
|
LabelText = text,
|
||||||
|
Changed = onCheckboxChanged
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
private void onCheckboxChanged(bool value)
|
||||||
|
{
|
||||||
|
if (value)
|
||||||
|
SelectAll();
|
||||||
|
else
|
||||||
|
DeselectAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ModButtonStateChanged(Mod mod)
|
||||||
|
{
|
||||||
|
base.ModButtonStateChanged(mod);
|
||||||
|
|
||||||
|
if (!SelectionAnimationRunning)
|
||||||
|
{
|
||||||
|
var validButtons = ButtonsContainer.OfType<ModButton>().Where(b => b.Mod.HasImplementation);
|
||||||
|
checkbox.Current.Value = validButtons.All(b => b.Selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private class HeaderCheckbox : OsuCheckbox
|
||||||
|
{
|
||||||
|
public Action<bool> Changed;
|
||||||
|
|
||||||
|
protected override bool PlaySoundsOnUserChange => false;
|
||||||
|
|
||||||
|
public HeaderCheckbox()
|
||||||
|
: base(false)
|
||||||
|
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void ApplyLabelParameters(SpriteText text)
|
||||||
|
{
|
||||||
|
base.ApplyLabelParameters(text);
|
||||||
|
|
||||||
|
text.Font = OsuFont.GetFont(weight: FontWeight.Bold);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected override void OnUserChange(bool value)
|
||||||
|
{
|
||||||
|
base.OnUserChange(value);
|
||||||
|
Changed?.Invoke(value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user