mirror of
https://github.com/ppy/osu.git
synced 2025-02-14 17:45:20 +08:00
Merge branch 'master' into sorted-breaks
This commit is contained in:
commit
877b5768fc
@ -1,10 +1,13 @@
|
||||
// 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.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.Edit;
|
||||
using osu.Game.Screens.Edit.Components;
|
||||
@ -19,9 +22,10 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
[Cached]
|
||||
private EditorBeatmap editorBeatmap = new EditorBeatmap(new TestBeatmap(new OsuRuleset().RulesetInfo));
|
||||
|
||||
public TestSceneEditorClock()
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
Add(new FillFlowContainer
|
||||
AddStep("create content", () => Add(new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
@ -39,19 +43,17 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
Size = new Vector2(200, 100)
|
||||
}
|
||||
}
|
||||
}));
|
||||
AddStep("set working beatmap", () =>
|
||||
{
|
||||
Beatmap.Disabled = false;
|
||||
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||
// ensure that music controller does not change this beatmap due to it
|
||||
// completing naturally as part of the test.
|
||||
Beatmap.Disabled = true;
|
||||
});
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Beatmap.Value = CreateWorkingBeatmap(new OsuRuleset().RulesetInfo);
|
||||
// ensure that music controller does not change this beatmap due to it
|
||||
// completing naturally as part of the test.
|
||||
Beatmap.Disabled = true;
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestStopAtTrackEnd()
|
||||
{
|
||||
@ -102,6 +104,29 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
AddUntilStep("time is clamped to track length", () => EditorClock.CurrentTime, () => Is.EqualTo(EditorClock.TrackLength));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestCurrentTimeDoubleTransform()
|
||||
{
|
||||
AddAssert("seek smoothly twice and current time is accurate", () =>
|
||||
{
|
||||
EditorClock.SeekSmoothlyTo(1000);
|
||||
EditorClock.SeekSmoothlyTo(2000);
|
||||
return 2000 == EditorClock.CurrentTimeAccurate;
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAdjustmentsRemovedOnDisposal()
|
||||
{
|
||||
AddStep("reset clock", () => EditorClock.Seek(0));
|
||||
|
||||
AddStep("set 0.25x speed", () => this.ChildrenOfType<OsuTabControl<double>>().First().Current.Value = 0.25);
|
||||
AddAssert("track has 0.25x tempo", () => Beatmap.Value.Track.AggregateTempo.Value, () => Is.EqualTo(0.25));
|
||||
|
||||
AddStep("dispose playback control", () => Clear(disposeChildren: true));
|
||||
AddAssert("track has 1x tempo", () => Beatmap.Value.Track.AggregateTempo.Value, () => Is.EqualTo(1));
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
Beatmap.Disabled = false;
|
||||
|
180
osu.Game.Tests/Visual/Menus/TestSceneAudioDucking.cs
Normal file
180
osu.Game.Tests/Visual/Menus/TestSceneAudioDucking.cs
Normal file
@ -0,0 +1,180 @@
|
||||
// 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.Game.Audio.Effects;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Menus
|
||||
{
|
||||
public partial class TestSceneAudioDucking : OsuGameTestScene
|
||||
{
|
||||
[Test]
|
||||
public void TestMomentaryDuck()
|
||||
{
|
||||
AddStep("duck momentarily", () => Game.MusicController.DuckMomentarily(1000));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultipleDucks()
|
||||
{
|
||||
IDisposable duckOp1 = null!;
|
||||
IDisposable duckOp2 = null!;
|
||||
|
||||
double normalVolume = 1;
|
||||
|
||||
AddStep("get initial volume", () =>
|
||||
{
|
||||
normalVolume = Game.Audio.Tracks.AggregateVolume.Value;
|
||||
});
|
||||
|
||||
AddStep("duck one", () =>
|
||||
{
|
||||
duckOp1 = Game.MusicController.Duck(new DuckParameters
|
||||
{
|
||||
DuckVolumeTo = 0.5,
|
||||
});
|
||||
});
|
||||
|
||||
AddUntilStep("wait for duck to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.5f).Within(0.01));
|
||||
|
||||
AddStep("duck two", () =>
|
||||
{
|
||||
duckOp2 = Game.MusicController.Duck(new DuckParameters
|
||||
{
|
||||
DuckVolumeTo = 0.2,
|
||||
});
|
||||
});
|
||||
|
||||
AddUntilStep("wait for duck to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.2f).Within(0.01));
|
||||
|
||||
AddStep("restore two", () => duckOp2.Dispose());
|
||||
AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.5f).Within(0.01));
|
||||
|
||||
AddStep("restore one", () => duckOp1.Dispose());
|
||||
AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume).Within(0.01));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultipleDucksSameParameters()
|
||||
{
|
||||
var duckParameters = new DuckParameters
|
||||
{
|
||||
DuckVolumeTo = 0.5,
|
||||
};
|
||||
|
||||
IDisposable duckOp1 = null!;
|
||||
IDisposable duckOp2 = null!;
|
||||
|
||||
double normalVolume = 1;
|
||||
|
||||
AddStep("get initial volume", () =>
|
||||
{
|
||||
normalVolume = Game.Audio.Tracks.AggregateVolume.Value;
|
||||
});
|
||||
|
||||
AddStep("duck one", () =>
|
||||
{
|
||||
duckOp1 = Game.MusicController.Duck(duckParameters);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for duck to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.5f).Within(0.01));
|
||||
|
||||
AddStep("duck two", () =>
|
||||
{
|
||||
duckOp2 = Game.MusicController.Duck(duckParameters);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for duck to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.5f).Within(0.01));
|
||||
|
||||
AddStep("restore two", () => duckOp2.Dispose());
|
||||
AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.5f).Within(0.01));
|
||||
|
||||
AddStep("restore one", () => duckOp1.Dispose());
|
||||
AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume).Within(0.01));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultipleDucksReverseOrder()
|
||||
{
|
||||
IDisposable duckOp1 = null!;
|
||||
IDisposable duckOp2 = null!;
|
||||
|
||||
double normalVolume = 1;
|
||||
|
||||
AddStep("get initial volume", () =>
|
||||
{
|
||||
normalVolume = Game.Audio.Tracks.AggregateVolume.Value;
|
||||
});
|
||||
|
||||
AddStep("duck one", () =>
|
||||
{
|
||||
duckOp1 = Game.MusicController.Duck(new DuckParameters
|
||||
{
|
||||
DuckVolumeTo = 0.5,
|
||||
});
|
||||
});
|
||||
|
||||
AddUntilStep("wait for duck to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.5f).Within(0.01));
|
||||
|
||||
AddStep("duck two", () =>
|
||||
{
|
||||
duckOp2 = Game.MusicController.Duck(new DuckParameters
|
||||
{
|
||||
DuckVolumeTo = 0.2,
|
||||
});
|
||||
});
|
||||
|
||||
AddUntilStep("wait for duck to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.2f).Within(0.01));
|
||||
|
||||
AddStep("restore one", () => duckOp1.Dispose());
|
||||
|
||||
// reverse order, less extreme duck removed so won't change
|
||||
AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume * 0.2f).Within(0.01));
|
||||
|
||||
AddStep("restore two", () => duckOp2.Dispose());
|
||||
AddUntilStep("wait for restore to complete", () => Game.Audio.Tracks.AggregateVolume.Value, () => Is.EqualTo(normalVolume).Within(0.01));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultipleDisposalIsNoop()
|
||||
{
|
||||
IDisposable duckOp1 = null!;
|
||||
|
||||
AddStep("duck", () => duckOp1 = Game.MusicController.Duck());
|
||||
AddStep("restore", () => duckOp1.Dispose());
|
||||
AddStep("restore", () => duckOp1.Dispose());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestMultipleDucksDifferentPieces()
|
||||
{
|
||||
IDisposable duckOp1 = null!;
|
||||
IDisposable duckOp2 = null!;
|
||||
|
||||
AddStep("duck volume", () =>
|
||||
{
|
||||
duckOp1 = Game.MusicController.Duck(new DuckParameters
|
||||
{
|
||||
DuckVolumeTo = 0.2,
|
||||
DuckCutoffTo = AudioFilter.MAX_LOWPASS_CUTOFF,
|
||||
DuckDuration = 500,
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("duck lowpass", () =>
|
||||
{
|
||||
duckOp2 = Game.MusicController.Duck(new DuckParameters
|
||||
{
|
||||
DuckVolumeTo = 1,
|
||||
DuckCutoffTo = 300,
|
||||
DuckDuration = 500,
|
||||
});
|
||||
});
|
||||
|
||||
AddStep("restore lowpass", () => duckOp2.Dispose());
|
||||
AddStep("restore volume", () => duckOp1.Dispose());
|
||||
}
|
||||
}
|
||||
}
|
@ -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]
|
||||
|
@ -47,9 +47,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
public void TestArrowDirection()
|
||||
{
|
||||
AddStep("Set upwards", () => button.SetIconDirection(true));
|
||||
AddAssert("Icon facing upwards", () => button.Icon.Scale.Y == -1);
|
||||
AddUntilStep("Icon facing upwards", () => button.Icon.Scale.Y == -1);
|
||||
AddStep("Set downwards", () => button.SetIconDirection(false));
|
||||
AddAssert("Icon facing downwards", () => button.Icon.Scale.Y == 1);
|
||||
AddUntilStep("Icon facing downwards", () => button.Icon.Scale.Y == 1);
|
||||
}
|
||||
|
||||
private partial class TestButton : CommentRepliesButton
|
||||
|
@ -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>());
|
||||
}
|
||||
}
|
||||
}
|
@ -163,8 +163,8 @@ namespace osu.Game.Collections
|
||||
public CollectionDropdownHeader()
|
||||
{
|
||||
Height = 25;
|
||||
Icon.Size = new Vector2(16);
|
||||
Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 4 };
|
||||
Chevron.Size = new Vector2(12);
|
||||
Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 8, Right = 8 };
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,17 +1,17 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Audio.Effects;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Collections
|
||||
@ -21,11 +21,14 @@ namespace osu.Game.Collections
|
||||
private const double enter_duration = 500;
|
||||
private const double exit_duration = 200;
|
||||
|
||||
private AudioFilter lowPassFilter = null!;
|
||||
|
||||
protected override string PopInSampleName => @"UI/overlay-big-pop-in";
|
||||
protected override string PopOutSampleName => @"UI/overlay-big-pop-out";
|
||||
|
||||
private IDisposable? duckOperation;
|
||||
|
||||
[Resolved]
|
||||
private MusicController? musicController { get; set; }
|
||||
|
||||
public ManageCollectionsDialog()
|
||||
{
|
||||
Anchor = Anchor.Centre;
|
||||
@ -39,7 +42,7 @@ namespace osu.Game.Collections
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours, AudioManager audio)
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
Children = new Drawable[]
|
||||
{
|
||||
@ -110,19 +113,25 @@ namespace osu.Game.Collections
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
lowPassFilter = new AudioFilter(audio.TrackMixer)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public override bool IsPresent => base.IsPresent
|
||||
// Safety for low pass filter potentially getting stuck in applied state due to
|
||||
// transforms on `this` causing children to no longer be updated.
|
||||
|| lowPassFilter.IsAttached;
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
base.Dispose(isDisposing);
|
||||
duckOperation?.Dispose();
|
||||
}
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
lowPassFilter.CutoffTo(300, 100, Easing.OutCubic);
|
||||
duckOperation = musicController?.Duck(new DuckParameters
|
||||
{
|
||||
DuckVolumeTo = 1,
|
||||
DuckDuration = 100,
|
||||
RestoreDuration = 100,
|
||||
});
|
||||
|
||||
this.FadeIn(enter_duration, Easing.OutQuint);
|
||||
this.ScaleTo(0.9f).Then().ScaleTo(1f, enter_duration, Easing.OutQuint);
|
||||
}
|
||||
@ -131,7 +140,7 @@ namespace osu.Game.Collections
|
||||
{
|
||||
base.PopOut();
|
||||
|
||||
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, 100, Easing.InCubic);
|
||||
duckOperation?.Dispose();
|
||||
|
||||
this.FadeOut(exit_duration, Easing.OutQuint);
|
||||
this.ScaleTo(0.9f, exit_duration);
|
||||
|
@ -1,6 +1,7 @@
|
||||
// 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.Diagnostics;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
@ -30,6 +31,12 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
protected override DropdownMenu CreateMenu() => new OsuDropdownMenu();
|
||||
|
||||
public OsuDropdown()
|
||||
{
|
||||
if (Header is OsuDropdownHeader osuHeader)
|
||||
osuHeader.Dropdown = this;
|
||||
}
|
||||
|
||||
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
|
||||
{
|
||||
if (e.Repeat) return false;
|
||||
@ -307,7 +314,9 @@ namespace osu.Game.Graphics.UserInterface
|
||||
set => Text.Text = value;
|
||||
}
|
||||
|
||||
protected readonly SpriteIcon Icon;
|
||||
protected readonly SpriteIcon Chevron;
|
||||
|
||||
public OsuDropdown<T>? Dropdown { get; set; }
|
||||
|
||||
public OsuDropdownHeader()
|
||||
{
|
||||
@ -341,7 +350,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
Origin = Anchor.CentreLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
},
|
||||
Icon = new SpriteIcon
|
||||
Chevron = new SpriteIcon
|
||||
{
|
||||
Icon = FontAwesome.Solid.ChevronDown,
|
||||
Anchor = Anchor.CentreRight,
|
||||
@ -365,6 +374,9 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
if (Dropdown != null)
|
||||
Dropdown.Menu.StateChanged += _ => updateChevron();
|
||||
|
||||
SearchBar.State.ValueChanged += _ => updateColour();
|
||||
Enabled.BindValueChanged(_ => updateColour());
|
||||
updateColour();
|
||||
@ -392,16 +404,23 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
if (SearchBar.State.Value == Visibility.Visible)
|
||||
{
|
||||
Icon.Colour = hovered ? hoveredColour.Lighten(0.5f) : Colour4.White;
|
||||
Chevron.Colour = hovered ? hoveredColour.Lighten(0.5f) : Colour4.White;
|
||||
Background.Colour = unhoveredColour;
|
||||
}
|
||||
else
|
||||
{
|
||||
Icon.Colour = Color4.White;
|
||||
Chevron.Colour = Color4.White;
|
||||
Background.Colour = hovered ? hoveredColour : unhoveredColour;
|
||||
}
|
||||
}
|
||||
|
||||
private void updateChevron()
|
||||
{
|
||||
Debug.Assert(Dropdown != null);
|
||||
bool open = Dropdown.Menu.State == MenuState.Open;
|
||||
Chevron.ScaleTo(open ? new Vector2(1f, -1f) : Vector2.One, 300, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override DropdownSearchBar CreateSearchBar() => new OsuDropdownSearchBar
|
||||
{
|
||||
Padding = new MarginPadding { Right = 26 },
|
||||
|
@ -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}";
|
||||
}
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ namespace osu.Game.Overlays.Comments.Buttons
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(12),
|
||||
Icon = FontAwesome.Solid.ChevronDown
|
||||
};
|
||||
}
|
||||
|
||||
@ -38,11 +39,12 @@ namespace osu.Game.Overlays.Comments.Buttons
|
||||
base.LoadComplete();
|
||||
Action = Expanded.Toggle;
|
||||
Expanded.BindValueChanged(onExpandedChanged, true);
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
private void onExpandedChanged(ValueChangedEvent<bool> expanded)
|
||||
{
|
||||
icon.Icon = expanded.NewValue ? FontAwesome.Solid.ChevronUp : FontAwesome.Solid.ChevronDown;
|
||||
icon.ScaleTo(expanded.NewValue ? new Vector2(1f, -1f) : Vector2.One, 300, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -89,7 +89,7 @@ namespace osu.Game.Overlays.Comments.Buttons
|
||||
background.Colour = colourProvider.Background2;
|
||||
}
|
||||
|
||||
protected void SetIconDirection(bool upwards) => icon.ScaleTo(new Vector2(1, upwards ? -1 : 1));
|
||||
protected void SetIconDirection(bool upwards) => icon.ScaleTo(upwards ? new Vector2(1f, -1f) : Vector2.One, 300, Easing.OutQuint);
|
||||
|
||||
public void ToggleTextVisibility(bool visible) => text.FadeTo(visible ? 1 : 0, 200, Easing.OutQuint);
|
||||
|
||||
|
@ -3,7 +3,6 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
@ -11,7 +10,6 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Audio.Effects;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
|
||||
@ -57,7 +55,6 @@ namespace osu.Game.Overlays.Dialog
|
||||
private Sample tickSample;
|
||||
private Sample confirmSample;
|
||||
private double lastTickPlaybackTime;
|
||||
private AudioFilter lowPassFilter = null!;
|
||||
private bool mouseDown;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
@ -65,8 +62,6 @@ namespace osu.Game.Overlays.Dialog
|
||||
{
|
||||
tickSample = audio.Samples.Get(@"UI/dialog-dangerous-tick");
|
||||
confirmSample = audio.Samples.Get(@"UI/dialog-dangerous-select");
|
||||
|
||||
AddInternal(lowPassFilter = new AudioFilter(audio.SampleMixer));
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -75,15 +70,8 @@ namespace osu.Game.Overlays.Dialog
|
||||
Progress.BindValueChanged(progressChanged, true);
|
||||
}
|
||||
|
||||
protected override void AbortConfirm()
|
||||
{
|
||||
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF);
|
||||
base.AbortConfirm();
|
||||
}
|
||||
|
||||
protected override void Confirm()
|
||||
{
|
||||
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF);
|
||||
confirmSample?.Play();
|
||||
base.Confirm();
|
||||
}
|
||||
@ -123,8 +111,6 @@ namespace osu.Game.Overlays.Dialog
|
||||
|
||||
private void progressChanged(ValueChangedEvent<double> progress)
|
||||
{
|
||||
lowPassFilter.Cutoff = Math.Max(1, (int)(progress.NewValue * AudioFilter.MAX_LOWPASS_CUTOFF * 0.5));
|
||||
|
||||
if (progress.NewValue < progress.OldValue)
|
||||
return;
|
||||
|
||||
|
@ -3,16 +3,16 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Game.Overlays.Dialog;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Input.Bindings;
|
||||
using System.Linq;
|
||||
using JetBrains.Annotations;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Game.Audio.Effects;
|
||||
|
||||
namespace osu.Game.Overlays
|
||||
{
|
||||
@ -23,15 +23,16 @@ namespace osu.Game.Overlays
|
||||
protected override string PopInSampleName => "UI/dialog-pop-in";
|
||||
protected override string PopOutSampleName => "UI/dialog-pop-out";
|
||||
|
||||
private AudioFilter lowPassFilter;
|
||||
[Resolved]
|
||||
private MusicController musicController { get; set; }
|
||||
|
||||
public PopupDialog CurrentDialog { get; private set; }
|
||||
|
||||
public override bool IsPresent => Scheduler.HasPendingTasks
|
||||
|| dialogContainer.Children.Count > 0
|
||||
// Safety for low pass filter potentially getting stuck in applied state due to
|
||||
// transforms on `this` causing children to no longer be updated.
|
||||
|| lowPassFilter.IsAttached;
|
||||
|| dialogContainer.Children.Count > 0;
|
||||
|
||||
[CanBeNull]
|
||||
private IDisposable duckOperation;
|
||||
|
||||
public DialogOverlay()
|
||||
{
|
||||
@ -49,10 +50,10 @@ namespace osu.Game.Overlays
|
||||
Origin = Anchor.Centre;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
AddInternal(lowPassFilter = new AudioFilter(audio.TrackMixer));
|
||||
base.Dispose(isDisposing);
|
||||
duckOperation?.Dispose();
|
||||
}
|
||||
|
||||
public void Push(PopupDialog dialog)
|
||||
@ -105,13 +106,18 @@ namespace osu.Game.Overlays
|
||||
|
||||
protected override void PopIn()
|
||||
{
|
||||
lowPassFilter.CutoffTo(300, 100, Easing.OutCubic);
|
||||
duckOperation = musicController?.Duck(new DuckParameters
|
||||
{
|
||||
DuckVolumeTo = 1,
|
||||
DuckDuration = 100,
|
||||
RestoreDuration = 100,
|
||||
});
|
||||
}
|
||||
|
||||
protected override void PopOut()
|
||||
{
|
||||
base.PopOut();
|
||||
lowPassFilter.CutoffTo(AudioFilter.MAX_LOWPASS_CUTOFF, 100, Easing.InCubic);
|
||||
duckOperation?.Dispose();
|
||||
|
||||
// PopOut gets called initially, but we only want to hide dialog when we have been loaded and are present.
|
||||
if (IsLoaded && CurrentDialog?.State.Value == Visibility.Visible)
|
||||
|
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -53,8 +53,8 @@ namespace osu.Game.Overlays.Music
|
||||
{
|
||||
CornerRadius = 5;
|
||||
Height = 30;
|
||||
Icon.Size = new Vector2(14);
|
||||
Icon.Margin = new MarginPadding(0);
|
||||
Chevron.Size = new Vector2(14);
|
||||
Chevron.Margin = new MarginPadding(0);
|
||||
Foreground.Padding = new MarginPadding { Top = 4, Bottom = 4, Left = 10, Right = 10 };
|
||||
EdgeEffect = new EdgeEffectParameters
|
||||
{
|
||||
|
@ -13,6 +13,7 @@ using osu.Framework.Graphics.Audio;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Audio.Effects;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Database;
|
||||
using osu.Game.Rulesets.Mods;
|
||||
@ -59,6 +60,17 @@ namespace osu.Game.Overlays
|
||||
[Resolved]
|
||||
private RealmAccess realm { get; set; } = null!;
|
||||
|
||||
private readonly BindableDouble audioDuckVolume = new BindableDouble(1);
|
||||
|
||||
private AudioFilter audioDuckFilter = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
AddInternal(audioDuckFilter = new AudioFilter(audio.TrackMixer));
|
||||
audio.Tracks.AddAdjustment(AdjustableProperty.Volume, audioDuckVolume);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
@ -246,6 +258,54 @@ namespace osu.Game.Overlays
|
||||
onSuccess?.Invoke();
|
||||
});
|
||||
|
||||
private readonly List<DuckParameters> duckOperations = new List<DuckParameters>();
|
||||
|
||||
/// <summary>
|
||||
/// Applies ducking, attenuating the volume and/or low-pass cutoff of the currently playing track to make headroom for effects (or just to apply an effect).
|
||||
/// </summary>
|
||||
/// <returns>A <see cref="IDisposable"/> which will restore the duck operation when disposed.</returns>
|
||||
public IDisposable Duck(DuckParameters? parameters = null)
|
||||
{
|
||||
parameters ??= new DuckParameters();
|
||||
|
||||
duckOperations.Add(parameters);
|
||||
|
||||
DuckParameters volumeOperation = duckOperations.MinBy(p => p.DuckVolumeTo)!;
|
||||
DuckParameters lowPassOperation = duckOperations.MinBy(p => p.DuckCutoffTo)!;
|
||||
|
||||
audioDuckFilter.CutoffTo(lowPassOperation.DuckCutoffTo, lowPassOperation.DuckDuration, lowPassOperation.DuckEasing);
|
||||
this.TransformBindableTo(audioDuckVolume, volumeOperation.DuckVolumeTo, volumeOperation.DuckDuration, volumeOperation.DuckEasing);
|
||||
|
||||
return new InvokeOnDisposal(restoreDucking);
|
||||
|
||||
void restoreDucking() => Schedule(() =>
|
||||
{
|
||||
if (!duckOperations.Remove(parameters))
|
||||
return;
|
||||
|
||||
DuckParameters? restoreVolumeOperation = duckOperations.MinBy(p => p.DuckVolumeTo);
|
||||
DuckParameters? restoreLowPassOperation = duckOperations.MinBy(p => p.DuckCutoffTo);
|
||||
|
||||
// If another duck operation is in the list, restore ducking to its level, else reset back to defaults.
|
||||
audioDuckFilter.CutoffTo(restoreLowPassOperation?.DuckCutoffTo ?? AudioFilter.MAX_LOWPASS_CUTOFF, parameters.RestoreDuration, parameters.RestoreEasing);
|
||||
this.TransformBindableTo(audioDuckVolume, restoreVolumeOperation?.DuckVolumeTo ?? 1, parameters.RestoreDuration, parameters.RestoreEasing);
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// A convenience method that ducks the currently playing track, then after a delay, restores automatically.
|
||||
/// </summary>
|
||||
/// <param name="delayUntilRestore">A delay in milliseconds which defines how long to delay restoration after ducking completes.</param>
|
||||
/// <param name="parameters">Parameters defining the ducking operation.</param>
|
||||
public void DuckMomentarily(double delayUntilRestore, DuckParameters? parameters = null)
|
||||
{
|
||||
parameters ??= new DuckParameters();
|
||||
|
||||
IDisposable duckOperation = Duck(parameters);
|
||||
|
||||
Scheduler.AddDelayed(() => duckOperation.Dispose(), delayUntilRestore);
|
||||
}
|
||||
|
||||
private bool next()
|
||||
{
|
||||
if (beatmap.Disabled || !AllowTrackControl.Value)
|
||||
@ -419,6 +479,45 @@ namespace osu.Game.Overlays
|
||||
}
|
||||
}
|
||||
|
||||
public class DuckParameters
|
||||
{
|
||||
/// <summary>
|
||||
/// The duration of the ducking transition in milliseconds.
|
||||
/// Defaults to 100 ms.
|
||||
/// </summary>
|
||||
public double DuckDuration = 100;
|
||||
|
||||
/// <summary>
|
||||
/// The final volume which should be reached during ducking, when 0 is silent and 1 is original volume.
|
||||
/// Defaults to 25%.
|
||||
/// </summary>
|
||||
public double DuckVolumeTo = 0.25;
|
||||
|
||||
/// <summary>
|
||||
/// The low-pass cutoff frequency which should be reached during ducking. If not required, set to <see cref="AudioFilter.MAX_LOWPASS_CUTOFF"/>.
|
||||
/// Defaults to 300 Hz.
|
||||
/// </summary>
|
||||
public int DuckCutoffTo = 300;
|
||||
|
||||
/// <summary>
|
||||
/// The easing curve to be applied during ducking.
|
||||
/// Defaults to <see cref="Easing.Out"/>.
|
||||
/// </summary>
|
||||
public Easing DuckEasing = Easing.Out;
|
||||
|
||||
/// <summary>
|
||||
/// The duration of the restoration transition in milliseconds.
|
||||
/// Defaults to 500 ms.
|
||||
/// </summary>
|
||||
public double RestoreDuration = 500;
|
||||
|
||||
/// <summary>
|
||||
/// The easing curve to be applied during restoration.
|
||||
/// Defaults to <see cref="Easing.In"/>.
|
||||
/// </summary>
|
||||
public Easing RestoreEasing = Easing.In;
|
||||
}
|
||||
|
||||
public enum TrackChangeDirection
|
||||
{
|
||||
None,
|
||||
|
@ -118,7 +118,7 @@ namespace osu.Game.Overlays.News.Sidebar
|
||||
|
||||
Expanded.BindValueChanged(open =>
|
||||
{
|
||||
icon.Scale = new Vector2(1, open.NewValue ? -1 : 1);
|
||||
icon.ScaleTo(open.NewValue ? new Vector2(1f, -1f) : Vector2.One, 300, Easing.OutQuint);
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
|
@ -163,7 +163,7 @@ namespace osu.Game.Overlays
|
||||
|
||||
LastScrollTarget.BindValueChanged(target =>
|
||||
{
|
||||
spriteIcon.RotateTo(target.NewValue != null ? 180 : 0, fade_duration, Easing.OutQuint);
|
||||
spriteIcon.ScaleTo(target.NewValue != null ? new Vector2(1f, -1f) : Vector2.One, fade_duration, Easing.OutQuint);
|
||||
TooltipText = target.NewValue != null ? CommonStrings.ButtonsBackToPrevious : CommonStrings.ButtonsBackToTop;
|
||||
}, true);
|
||||
}
|
||||
|
@ -50,12 +50,13 @@ namespace osu.Game.Overlays.Profile.Header.Components
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Size = new Vector2(10.5f, 12)
|
||||
Size = new Vector2(10.5f, 12),
|
||||
Icon = FontAwesome.Solid.ChevronDown,
|
||||
};
|
||||
|
||||
CoverExpanded.BindValueChanged(visible => updateState(visible.NewValue), true);
|
||||
}
|
||||
|
||||
private void updateState(bool detailsVisible) => icon.Icon = detailsVisible ? FontAwesome.Solid.ChevronUp : FontAwesome.Solid.ChevronDown;
|
||||
private void updateState(bool detailsVisible) => icon.ScaleTo(detailsVisible ? new Vector2(1f, -1f) : Vector2.One, 300, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
@ -200,7 +200,7 @@ namespace osu.Game.Overlays.Rankings
|
||||
Text.Font = OsuFont.GetFont(size: 15);
|
||||
Text.Padding = new MarginPadding { Vertical = 1.5f }; // osu-web line-height difference compensation
|
||||
Foreground.Padding = new MarginPadding { Horizontal = 10, Vertical = 15 };
|
||||
Margin = Icon.Margin = new MarginPadding(0);
|
||||
Margin = Chevron.Margin = new MarginPadding(0);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
@ -3,8 +3,12 @@
|
||||
|
||||
#nullable disable
|
||||
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
@ -21,6 +25,13 @@ namespace osu.Game.Overlays.Toolbar
|
||||
{
|
||||
protected Drawable ModeButtonLine { get; private set; }
|
||||
|
||||
[Resolved]
|
||||
private MusicController musicController { get; set; }
|
||||
|
||||
private readonly Dictionary<RulesetInfo, Sample> rulesetSelectionSample = new Dictionary<RulesetInfo, Sample>();
|
||||
private readonly Dictionary<RulesetInfo, SampleChannel> rulesetSelectionChannel = new Dictionary<RulesetInfo, SampleChannel>();
|
||||
private Sample defaultSelectSample;
|
||||
|
||||
public ToolbarRulesetSelector()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
@ -28,7 +39,7 @@ namespace osu.Game.Overlays.Toolbar
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
AddRangeInternal(new[]
|
||||
{
|
||||
@ -54,6 +65,13 @@ namespace osu.Game.Overlays.Toolbar
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
foreach (var r in Rulesets.AvailableRulesets)
|
||||
rulesetSelectionSample[r] = audio.Samples.Get($@"UI/ruleset-select-{r.ShortName}");
|
||||
|
||||
defaultSelectSample = audio.Samples.Get(@"UI/default-select");
|
||||
|
||||
Current.ValueChanged += playRulesetSelectionSample;
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@ -84,6 +102,29 @@ namespace osu.Game.Overlays.Toolbar
|
||||
}
|
||||
}
|
||||
|
||||
private void playRulesetSelectionSample(ValueChangedEvent<RulesetInfo> r)
|
||||
{
|
||||
// Don't play sample on first setting of value
|
||||
if (r.OldValue == null)
|
||||
return;
|
||||
|
||||
var channel = rulesetSelectionSample[r.NewValue]?.GetChannel();
|
||||
|
||||
// Skip sample choking and ducking for the default/fallback sample
|
||||
if (channel == null)
|
||||
{
|
||||
defaultSelectSample.Play();
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var pair in rulesetSelectionChannel)
|
||||
pair.Value?.Stop();
|
||||
|
||||
rulesetSelectionChannel[r.NewValue] = channel;
|
||||
channel.Play();
|
||||
musicController?.DuckMomentarily(500, new DuckParameters { DuckDuration = 0 });
|
||||
}
|
||||
|
||||
public override bool HandleNonPositionalInput => !Current.Disabled && base.HandleNonPositionalInput;
|
||||
|
||||
public override bool HandlePositionalInput => !Current.Disabled && base.HandlePositionalInput;
|
||||
|
@ -2,8 +2,6 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
@ -19,8 +17,6 @@ namespace osu.Game.Overlays.Toolbar
|
||||
{
|
||||
private readonly RulesetButton ruleset;
|
||||
|
||||
private Sample? selectSample;
|
||||
|
||||
public ToolbarRulesetTabButton(RulesetInfo value)
|
||||
: base(value)
|
||||
{
|
||||
@ -38,18 +34,10 @@ namespace osu.Game.Overlays.Toolbar
|
||||
ruleset.SetIcon(rInstance.CreateIcon());
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
selectSample = audio.Samples.Get($@"UI/ruleset-select-{Value.ShortName}");
|
||||
}
|
||||
|
||||
protected override void OnActivated() => ruleset.Active = true;
|
||||
|
||||
protected override void OnDeactivated() => ruleset.Active = false;
|
||||
|
||||
protected override void OnActivatedByUser() => selectSample?.Play();
|
||||
|
||||
private partial class RulesetButton : ToolbarButton
|
||||
{
|
||||
protected override HoverSounds CreateHoverSounds(HoverSampleSet sampleSet) => new HoverSounds();
|
||||
|
@ -71,7 +71,10 @@ namespace osu.Game.Screens.Edit.Components.Menus
|
||||
});
|
||||
}
|
||||
|
||||
protected override Framework.Graphics.UserInterface.Menu CreateSubMenu() => new SubMenu();
|
||||
protected override Framework.Graphics.UserInterface.Menu CreateSubMenu() => new SubMenu
|
||||
{
|
||||
MaxHeight = MaxHeight,
|
||||
};
|
||||
|
||||
protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableEditorBarMenuItem(item);
|
||||
|
||||
@ -143,7 +146,10 @@ namespace osu.Game.Screens.Edit.Components.Menus
|
||||
BackgroundColour = colourProvider.Background2;
|
||||
}
|
||||
|
||||
protected override Framework.Graphics.UserInterface.Menu CreateSubMenu() => new SubMenu();
|
||||
protected override Framework.Graphics.UserInterface.Menu CreateSubMenu() => new SubMenu
|
||||
{
|
||||
MaxHeight = MaxHeight,
|
||||
};
|
||||
|
||||
protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item)
|
||||
{
|
||||
|
@ -105,7 +105,7 @@ namespace osu.Game.Screens.Edit.Components
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
{
|
||||
Track.Value?.RemoveAdjustment(AdjustableProperty.Frequency, tempoAdjustment);
|
||||
Track.Value?.RemoveAdjustment(AdjustableProperty.Tempo, tempoAdjustment);
|
||||
|
||||
base.Dispose(isDisposing);
|
||||
}
|
||||
|
@ -343,6 +343,7 @@ namespace osu.Game.Screens.Edit
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
MaxHeight = 600,
|
||||
Items = new[]
|
||||
{
|
||||
new MenuItem(CommonStrings.MenuBarFile)
|
||||
|
@ -154,7 +154,7 @@ namespace osu.Game.Screens.Edit
|
||||
/// The current time of this clock, include any active transform seeks performed via <see cref="SeekSmoothlyTo"/>.
|
||||
/// </summary>
|
||||
public double CurrentTimeAccurate =>
|
||||
Transforms.OfType<TransformSeek>().FirstOrDefault()?.EndValue ?? CurrentTime;
|
||||
Transforms.OfType<TransformSeek>().LastOrDefault()?.EndValue ?? CurrentTime;
|
||||
|
||||
public double CurrentTime => underlyingClock.CurrentTime;
|
||||
|
||||
|
@ -72,7 +72,11 @@ namespace osu.Game.Screens.Edit.GameplayTest
|
||||
foreach (var hitObject in enumerateHitObjects(DrawableRuleset.Objects, editorState.Time))
|
||||
{
|
||||
var judgement = hitObject.Judgement;
|
||||
var result = new JudgementResult(hitObject, judgement) { Type = judgement.MaxResult };
|
||||
var result = new JudgementResult(hitObject, judgement)
|
||||
{
|
||||
Type = judgement.MaxResult,
|
||||
GameplayRate = GameplayClockContainer.GetTrueGameplayRate(),
|
||||
};
|
||||
|
||||
HealthProcessor.ApplyResult(result);
|
||||
ScoreProcessor.ApplyResult(result);
|
||||
|
@ -36,7 +36,7 @@
|
||||
</PackageReference>
|
||||
<PackageReference Include="Realm" Version="11.5.0" />
|
||||
<PackageReference Include="ppy.osu.Framework" Version="2024.702.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.622.0" />
|
||||
<PackageReference Include="ppy.osu.Game.Resources" Version="2024.705.0" />
|
||||
<PackageReference Include="Sentry" Version="4.3.0" />
|
||||
<!-- Held back due to 0.34.0 failing AOT compilation on ZstdSharp.dll dependency. -->
|
||||
<PackageReference Include="SharpCompress" Version="0.36.0" />
|
||||
|
Loading…
Reference in New Issue
Block a user