mirror of
https://github.com/ppy/osu.git
synced 2026-06-05 06:43:39 +08:00
Compare commits
260 Commits
+1
-3
@@ -55,9 +55,7 @@ When in doubt, it's probably best to start with a discussion first. We will esca
|
||||
|
||||
While pull requests from unaffiliated contributors are welcome, please note that due to significant community interest and limited review throughput, the core team's primary focus is on the issues which are currently [on the roadmap](https://github.com/orgs/ppy/projects/7/views/6). Reviewing PRs that fall outside of the scope of the roadmap is done on a best-effort basis, so please be aware that it may take a while before a core maintainer gets around to review your change.
|
||||
|
||||
The [issue tracker](https://github.com/ppy/osu/issues) should provide plenty of issues to start with. We also have a [`good first issue`](https://github.com/ppy/osu/issues?q=is%3Aissue+is%3Aopen+label%3A%22good+first+issue%22) label, although from experience it is not used very often, as it is relatively rare that we can spot an issue that will definitively be a good first issue for a new contributor regardless of their programming experience.
|
||||
|
||||
In the case of simple issues, a direct PR is okay. However, if you decide to work on an existing issue which doesn't seem trivial, **please ask us first**. This way we can try to estimate if it is a good fit for you and provide the correct direction on how to address it. In addition, note that while we do not rule out external contributors from working on roadmapped issues, we will generally prefer to handle them ourselves unless they're not very time sensitive.
|
||||
The [issue tracker](https://github.com/ppy/osu/issues) should provide plenty of issues to start with. In the case of simple issues, a direct PR is okay. However, if you decide to work on an existing issue which doesn't seem trivial, **please ask us first**. This way we can try to estimate if it is a good fit for you and provide the correct direction on how to address it. In addition, note that while we do not rule out external contributors from working on roadmapped issues, we will generally prefer to handle them ourselves unless they're not very time sensitive.
|
||||
|
||||
If you'd like to propose a subjective change to one of the visual aspects of the game, or there is a bigger task you'd like to work on, but there is no corresponding issue or discussion thread yet for it, **please open a discussion or issue first** to avoid wasted effort. This in particular applies if you want to work on [one of the available designs from the osu! Figma master library](https://www.figma.com/file/VIkXMYNPMtQem2RJg9k2iQ/Master-Library).
|
||||
|
||||
|
||||
+1
-1
@@ -10,7 +10,7 @@
|
||||
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2025.1229.0" />
|
||||
<PackageReference Include="ppy.osu.Framework.Android" Version="2026.108.0" />
|
||||
</ItemGroup>
|
||||
<PropertyGroup>
|
||||
<!-- Fody does not handle Android build well, and warns when unchanged.
|
||||
|
||||
@@ -7,6 +7,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
|
||||
@@ -32,7 +33,7 @@ namespace osu.Desktop.Security
|
||||
{
|
||||
public ElevatedPrivilegesNotification()
|
||||
{
|
||||
Text = $"Running osu! as {(RuntimeInfo.IsUnix ? "root" : "administrator")} does not improve performance, may break integrations and poses a security risk. Please run the game as a normal user.";
|
||||
Text = NotificationsStrings.ElevatedPrivileges(RuntimeInfo.IsUnix ? "root" : "Administrator");
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
|
||||
@@ -7,7 +7,7 @@ using osu.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets.Mania.Configuration;
|
||||
@@ -31,47 +31,45 @@ namespace osu.Game.Rulesets.Mania
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SettingsEnumDropdown<ManiaScrollingDirection>
|
||||
new SettingsItemV2(new FormEnumDropdown<ManiaScrollingDirection>
|
||||
{
|
||||
LabelText = RulesetSettingsStrings.ScrollingDirection,
|
||||
Caption = RulesetSettingsStrings.ScrollingDirection,
|
||||
Current = config.GetBindable<ManiaScrollingDirection>(ManiaRulesetSetting.ScrollDirection)
|
||||
},
|
||||
new SettingsSlider<double, ManiaScrollSlider>
|
||||
}),
|
||||
new SettingsItemV2(new FormSliderBar<double>
|
||||
{
|
||||
LabelText = RulesetSettingsStrings.ScrollSpeed,
|
||||
Caption = RulesetSettingsStrings.ScrollSpeed,
|
||||
Current = config.GetBindable<double>(ManiaRulesetSetting.ScrollSpeed),
|
||||
KeyboardStep = 1
|
||||
},
|
||||
new SettingsCheckbox
|
||||
KeyboardStep = 1,
|
||||
LabelFormat = v => RulesetSettingsStrings.ScrollSpeedTooltip((int)DrawableManiaRuleset.ComputeScrollTime(v), v),
|
||||
}),
|
||||
new SettingsItemV2(new FormCheckBox
|
||||
{
|
||||
Caption = RulesetSettingsStrings.TimingBasedColouring,
|
||||
Current = config.GetBindable<bool>(ManiaRulesetSetting.TimingBasedNoteColouring),
|
||||
})
|
||||
{
|
||||
Keywords = new[] { "color" },
|
||||
LabelText = RulesetSettingsStrings.TimingBasedColouring,
|
||||
Current = config.GetBindable<bool>(ManiaRulesetSetting.TimingBasedNoteColouring),
|
||||
},
|
||||
};
|
||||
|
||||
Add(new SettingsCheckbox
|
||||
Add(new SettingsItemV2(new FormCheckBox
|
||||
{
|
||||
LabelText = RulesetSettingsStrings.TouchOverlay,
|
||||
Caption = RulesetSettingsStrings.TouchOverlay,
|
||||
Current = config.GetBindable<bool>(ManiaRulesetSetting.TouchOverlay)
|
||||
});
|
||||
}));
|
||||
|
||||
if (RuntimeInfo.IsMobile)
|
||||
{
|
||||
Add(new SettingsEnumDropdown<ManiaMobileLayout>
|
||||
Add(new SettingsItemV2(new FormEnumDropdown<ManiaMobileLayout>
|
||||
{
|
||||
LabelText = RulesetSettingsStrings.MobileLayout,
|
||||
Caption = RulesetSettingsStrings.MobileLayout,
|
||||
Current = config.GetBindable<ManiaMobileLayout>(ManiaRulesetSetting.MobileLayout),
|
||||
#pragma warning disable CS0618 // Type or member is obsolete
|
||||
Items = Enum.GetValues<ManiaMobileLayout>().Where(l => l != ManiaMobileLayout.LandscapeWithOverlay),
|
||||
#pragma warning restore CS0618 // Type or member is obsolete
|
||||
});
|
||||
}));
|
||||
}
|
||||
}
|
||||
|
||||
private partial class ManiaScrollSlider : RoundedSliderBar<double>
|
||||
{
|
||||
public override LocalisableString TooltipText => RulesetSettingsStrings.ScrollSpeedTooltip((int)DrawableManiaRuleset.ComputeScrollTime(Current.Value), Current.Value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -154,7 +154,7 @@ namespace osu.Game.Rulesets.Mania.UI
|
||||
|
||||
var hitWindows = new ManiaHitWindows();
|
||||
|
||||
AddInternal(judgementPooler = new JudgementPooler<DrawableManiaJudgement>(Enum.GetValues<HitResult>().Where(r => hitWindows.IsHitResultAllowed(r))));
|
||||
AddInternal(judgementPooler = new JudgementPooler<DrawableManiaJudgement>(Enum.GetValues<HitResult>().Where(hitWindows.IsHitResultAllowed)));
|
||||
|
||||
RegisterPool<BarLine, DrawableBarLine>(50, 200);
|
||||
}
|
||||
|
||||
@@ -2,7 +2,10 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using NUnit.Framework;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.UI;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
{
|
||||
@@ -18,5 +21,39 @@ namespace osu.Game.Rulesets.Osu.Tests.Mods
|
||||
Autoplay = false,
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSkipToFirstCircleNotSuppressed()
|
||||
{
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModFreezeFrame(),
|
||||
CreateBeatmap = () => new OsuBeatmap
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new HitCircle { StartTime = 5000, Position = OsuPlayfield.BASE_SIZE / 2 }
|
||||
}
|
||||
},
|
||||
PassCondition = () => Player.GameplayClockContainer.GameplayStartTime > 0
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSkipToFirstSpinnerNotSuppressed()
|
||||
{
|
||||
CreateModTest(new ModTestData
|
||||
{
|
||||
Mod = new OsuModFreezeFrame(),
|
||||
CreateBeatmap = () => new OsuBeatmap
|
||||
{
|
||||
HitObjects =
|
||||
{
|
||||
new Spinner { StartTime = 5000, Position = OsuPlayfield.BASE_SIZE / 2 }
|
||||
}
|
||||
},
|
||||
PassCondition = () => Player.GameplayClockContainer.GameplayStartTime > 0
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
// 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.Testing;
|
||||
using osu.Game.Rulesets.Osu.Beatmaps;
|
||||
using osu.Game.Rulesets.Osu.Objects;
|
||||
using osu.Game.Rulesets.Osu.Replays;
|
||||
using osu.Game.Rulesets.Replays;
|
||||
using osu.Game.Tests.Visual;
|
||||
|
||||
namespace osu.Game.Rulesets.Osu.Tests
|
||||
{
|
||||
[TestFixture]
|
||||
[HeadlessTest]
|
||||
public partial class TestSceneAutoGeneration : OsuTestScene
|
||||
{
|
||||
[TestCase(-1, true)]
|
||||
[TestCase(0, false)]
|
||||
[TestCase(1, false)]
|
||||
public void TestAlternating(double offset, bool shouldAlternate)
|
||||
{
|
||||
const double first_object_time = 1000;
|
||||
double secondObjectTime = first_object_time + AutoGenerator.KEY_UP_DELAY + OsuAutoGenerator.MIN_FRAME_SEPARATION_FOR_ALTERNATING + offset;
|
||||
|
||||
var beatmap = new OsuBeatmap();
|
||||
beatmap.HitObjects.Add(new HitCircle { StartTime = first_object_time });
|
||||
beatmap.HitObjects.Add(new HitCircle { StartTime = secondObjectTime });
|
||||
|
||||
var generated = new OsuAutoGenerator(beatmap, []).Generate();
|
||||
var frames = generated.Frames.OfType<OsuReplayFrame>().ToList();
|
||||
|
||||
Assert.That(frames.Exists(f => f.Time == first_object_time && f.Actions.SingleOrDefault() == OsuAction.LeftButton));
|
||||
Assert.That(frames.Exists(f => f.Time == first_object_time + AutoGenerator.KEY_UP_DELAY && !f.Actions.Any()));
|
||||
|
||||
Assert.That(frames.Exists(f => f.Time == secondObjectTime && f.Actions.SingleOrDefault() == (shouldAlternate ? OsuAction.RightButton : OsuAction.LeftButton)));
|
||||
Assert.That(frames.Exists(f => f.Time == secondObjectTime + AutoGenerator.KEY_UP_DELAY && !f.Actions.Any()));
|
||||
}
|
||||
|
||||
[TestCase(300)]
|
||||
[TestCase(600)]
|
||||
[TestCase(1200)]
|
||||
public void TestAlternatingSpecificBPM(double bpm)
|
||||
{
|
||||
const double first_object_time = 1000;
|
||||
double secondObjectTime = first_object_time + 60000 / bpm;
|
||||
|
||||
var beatmap = new OsuBeatmap();
|
||||
beatmap.HitObjects.Add(new HitCircle { StartTime = first_object_time });
|
||||
beatmap.HitObjects.Add(new HitCircle { StartTime = secondObjectTime });
|
||||
|
||||
var generated = new OsuAutoGenerator(beatmap, []).Generate();
|
||||
var frames = generated.Frames.OfType<OsuReplayFrame>().ToList();
|
||||
|
||||
Assert.That(frames.Exists(f => f.Time == first_object_time && f.Actions.SingleOrDefault() == OsuAction.LeftButton));
|
||||
Assert.That(frames.Exists(f => f.Time == first_object_time + AutoGenerator.KEY_UP_DELAY && !f.Actions.Any()));
|
||||
|
||||
Assert.That(frames.Exists(f => f.Time == secondObjectTime && f.Actions.SingleOrDefault() == OsuAction.RightButton));
|
||||
Assert.That(frames.Exists(f => f.Time == secondObjectTime + AutoGenerator.KEY_UP_DELAY && !f.Actions.Any()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -33,7 +33,7 @@ namespace osu.Game.Rulesets.Osu.Tests
|
||||
Debug.Assert(drawableHitObject.HitObject.HitWindows != null);
|
||||
|
||||
double delay = drawableHitObject.HitObject.StartTime - (drawableHitObject.HitObject.HitWindows.WindowFor(HitResult.Miss) + RNG.Next(0, 300)) - Time.Current;
|
||||
scheduledTasks.Add(Scheduler.AddDelayed(() => drawableHitObject.TriggerJudgement(), delay));
|
||||
scheduledTasks.Add(Scheduler.AddDelayed(drawableHitObject.TriggerJudgement, delay));
|
||||
|
||||
return drawableHitObject;
|
||||
}
|
||||
|
||||
@@ -25,10 +25,10 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
public partial class PolygonGenerationPopover : OsuPopover
|
||||
{
|
||||
private SliderWithTextBoxInput<double> distanceSnapInput = null!;
|
||||
private SliderWithTextBoxInput<int> offsetAngleInput = null!;
|
||||
private SliderWithTextBoxInput<int> repeatCountInput = null!;
|
||||
private SliderWithTextBoxInput<int> pointInput = null!;
|
||||
private FormSliderBar<double> distanceSnapInput { get; set; } = null!;
|
||||
private FormSliderBar<int> offsetAngleInput { get; set; } = null!;
|
||||
private FormSliderBar<int> repeatCountInput { get; set; } = null!;
|
||||
private FormSliderBar<int> pointInput { get; set; } = null!;
|
||||
private RoundedButton commitButton = null!;
|
||||
|
||||
private readonly List<HitCircle> insertedCircles = new List<HitCircle>();
|
||||
@@ -64,11 +64,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
Width = 220,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(20),
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
distanceSnapInput = new SliderWithTextBoxInput<double>("Distance snap:")
|
||||
distanceSnapInput = new FormSliderBar<double>
|
||||
{
|
||||
Caption = "Distance snap",
|
||||
Current = new BindableNumber<double>(1)
|
||||
{
|
||||
MinValue = 0.1,
|
||||
@@ -76,37 +77,40 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
Precision = 0.1,
|
||||
Value = ((OsuHitObjectComposer)composer).DistanceSnapProvider.DistanceSpacingMultiplier.Value,
|
||||
},
|
||||
Instantaneous = true
|
||||
TabbableContentContainer = this
|
||||
},
|
||||
offsetAngleInput = new SliderWithTextBoxInput<int>("Offset angle:")
|
||||
offsetAngleInput = new FormSliderBar<int>
|
||||
{
|
||||
Caption = "Offset angle",
|
||||
Current = new BindableNumber<int>
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 180,
|
||||
Precision = 1
|
||||
},
|
||||
Instantaneous = true
|
||||
TabbableContentContainer = this
|
||||
},
|
||||
repeatCountInput = new SliderWithTextBoxInput<int>("Repeats:")
|
||||
repeatCountInput = new FormSliderBar<int>
|
||||
{
|
||||
Caption = "Repeats",
|
||||
Current = new BindableNumber<int>(1)
|
||||
{
|
||||
MinValue = 1,
|
||||
MaxValue = 10,
|
||||
Precision = 1
|
||||
},
|
||||
Instantaneous = true
|
||||
TabbableContentContainer = this
|
||||
},
|
||||
pointInput = new SliderWithTextBoxInput<int>("Vertices:")
|
||||
pointInput = new FormSliderBar<int>
|
||||
{
|
||||
Caption = "Vertices",
|
||||
Current = new BindableNumber<int>(3)
|
||||
{
|
||||
MinValue = 3,
|
||||
MaxValue = 32,
|
||||
Precision = 1,
|
||||
},
|
||||
Instantaneous = true
|
||||
TabbableContentContainer = this
|
||||
},
|
||||
commitButton = new RoundedButton
|
||||
{
|
||||
|
||||
@@ -37,7 +37,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
private BindableNumber<float> xBindable = null!;
|
||||
private BindableNumber<float> yBindable = null!;
|
||||
|
||||
private SliderWithTextBoxInput<float> xInput = null!;
|
||||
private FormSliderBar<float> xInput { get; set; } = null!;
|
||||
private OsuCheckbox relativeCheckbox = null!;
|
||||
|
||||
public PreciseMovementPopover()
|
||||
@@ -52,31 +52,31 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
Width = 220,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(20),
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
xInput = new SliderWithTextBoxInput<float>("X:")
|
||||
xInput = new FormSliderBar<float>
|
||||
{
|
||||
Caption = "X",
|
||||
Current = xBindable = new BindableNumber<float>
|
||||
{
|
||||
Precision = 1,
|
||||
},
|
||||
Instantaneous = true,
|
||||
TabbableContentContainer = this,
|
||||
TabbableContentContainer = this
|
||||
},
|
||||
new SliderWithTextBoxInput<float>("Y:")
|
||||
new FormSliderBar<float>
|
||||
{
|
||||
Caption = "Y",
|
||||
Current = yBindable = new BindableNumber<float>
|
||||
{
|
||||
Precision = 1,
|
||||
},
|
||||
Instantaneous = true,
|
||||
TabbableContentContainer = this,
|
||||
TabbableContentContainer = this
|
||||
},
|
||||
relativeCheckbox = new OsuCheckbox(false)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
LabelText = "Relative movement",
|
||||
LabelText = "Relative movement"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
private readonly Bindable<PreciseRotationInfo> rotationInfo = new Bindable<PreciseRotationInfo>(new PreciseRotationInfo(0, EditorOrigin.GridCentre));
|
||||
|
||||
private SliderWithTextBoxInput<float> angleInput = null!;
|
||||
private FormSliderBar<float> angleInput { get; set; } = null!;
|
||||
private EditorRadioButtonCollection rotationOrigin = null!;
|
||||
|
||||
private RadioButton gridCentreButton = null!;
|
||||
@@ -54,11 +54,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
Width = 220,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(20),
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
angleInput = new SliderWithTextBoxInput<float>("Angle (degrees):")
|
||||
angleInput = new FormSliderBar<float>
|
||||
{
|
||||
Caption = "Angle (degrees)",
|
||||
Current = new BindableNumber<float>
|
||||
{
|
||||
MinValue = -360,
|
||||
@@ -66,7 +67,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
Precision = 1
|
||||
},
|
||||
KeyboardStep = 1f,
|
||||
Instantaneous = true
|
||||
TabbableContentContainer = this
|
||||
},
|
||||
rotationOrigin = new EditorRadioButtonCollection
|
||||
{
|
||||
|
||||
@@ -32,7 +32,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
|
||||
private readonly Bindable<PreciseScaleInfo> scaleInfo = new Bindable<PreciseScaleInfo>(new PreciseScaleInfo(1, EditorOrigin.GridCentre, true, true));
|
||||
|
||||
private SliderWithTextBoxInput<float> scaleInput = null!;
|
||||
private FormSliderBar<float> scaleInput { get; set; } = null!;
|
||||
private BindableNumber<float> scaleInputBindable = null!;
|
||||
private EditorRadioButtonCollection scaleOrigin = null!;
|
||||
|
||||
@@ -66,11 +66,12 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
{
|
||||
Width = 220,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Spacing = new Vector2(20),
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
scaleInput = new SliderWithTextBoxInput<float>("Scale:")
|
||||
scaleInput = new FormSliderBar<float>
|
||||
{
|
||||
Caption = "Scale",
|
||||
Current = scaleInputBindable = new BindableNumber<float>
|
||||
{
|
||||
MinValue = 0.05f,
|
||||
@@ -80,7 +81,7 @@ namespace osu.Game.Rulesets.Osu.Edit
|
||||
Default = 1,
|
||||
},
|
||||
KeyboardStep = 0.01f,
|
||||
Instantaneous = true
|
||||
TabbableContentContainer = this
|
||||
},
|
||||
scaleOrigin = new EditorRadioButtonCollection
|
||||
{
|
||||
|
||||
@@ -57,7 +57,8 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
|
||||
void applyFadeInAdjustment(OsuHitObject osuObject)
|
||||
{
|
||||
osuObject.TimePreempt += osuObject.StartTime - lastNewComboTime;
|
||||
if (osuObject is not Spinner)
|
||||
osuObject.TimePreempt += osuObject.StartTime - lastNewComboTime;
|
||||
|
||||
foreach (var nested in osuObject.NestedHitObjects.OfType<OsuHitObject>())
|
||||
{
|
||||
|
||||
@@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.Osu.Mods
|
||||
public override string Name => "Traceable";
|
||||
public override string Acronym => "TC";
|
||||
public override IconUsage? Icon => OsuIcon.ModTraceable;
|
||||
public override ModType Type => ModType.Fun;
|
||||
public override ModType Type => ModType.DifficultyIncrease;
|
||||
public override LocalisableString Description => "Put your faith in the approach circles...";
|
||||
public override double ScoreMultiplier => 1;
|
||||
public override bool Ranked => true;
|
||||
|
||||
@@ -175,7 +175,7 @@ namespace osu.Game.Rulesets.Osu
|
||||
new OsuModHardRock(),
|
||||
new MultiMod(new OsuModSuddenDeath(), new OsuModPerfect()),
|
||||
new MultiMod(new OsuModDoubleTime(), new OsuModNightcore()),
|
||||
new OsuModHidden(),
|
||||
new MultiMod(new OsuModHidden(), new OsuModTraceable()),
|
||||
new MultiMod(new OsuModFlashlight(), new OsuModBlinds()),
|
||||
new OsuModStrictTracking(),
|
||||
new OsuModAccuracyChallenge(),
|
||||
@@ -209,7 +209,6 @@ namespace osu.Game.Rulesets.Osu
|
||||
new OsuModSpinIn(),
|
||||
new MultiMod(new OsuModGrow(), new OsuModDeflate()),
|
||||
new MultiMod(new ModWindUp(), new ModWindDown()),
|
||||
new OsuModTraceable(),
|
||||
new OsuModBarrelRoll(),
|
||||
new OsuModApproachDifferent(),
|
||||
new OsuModMuted(),
|
||||
|
||||
@@ -21,6 +21,8 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
{
|
||||
public class OsuAutoGenerator : OsuAutoGeneratorBase
|
||||
{
|
||||
public const double MIN_FRAME_SEPARATION_FOR_ALTERNATING = 266;
|
||||
|
||||
public new OsuBeatmap Beatmap => (OsuBeatmap)base.Beatmap;
|
||||
|
||||
#region Parameters
|
||||
@@ -245,7 +247,7 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
double timeDifference = ApplyModsToTimeDelta(lastFrame.Time, h.StartTime);
|
||||
OsuReplayFrame? lastLastFrame = Frames.Count >= 2 ? (OsuReplayFrame)Frames[^2] : null;
|
||||
|
||||
if (timeDifference > 0)
|
||||
if (timeDifference >= 0)
|
||||
{
|
||||
// If the last frame is a key-up frame and there has been no wait period, adjust the last frame's position such that it begins eased movement instantaneously.
|
||||
if (lastLastFrame != null && lastFrame is OsuKeyUpReplayFrame && !hasWaited)
|
||||
@@ -266,7 +268,7 @@ namespace osu.Game.Rulesets.Osu.Replays
|
||||
}
|
||||
|
||||
// Start alternating once the time separation is too small (faster than ~225BPM).
|
||||
if (timeDifference > 0 && timeDifference < 266)
|
||||
if (timeDifference >= 0 && timeDifference < MIN_FRAME_SEPARATION_FOR_ALTERNATING)
|
||||
buttonIndex++;
|
||||
else
|
||||
buttonIndex = 0;
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets.Osu.Configuration;
|
||||
@@ -27,32 +29,34 @@ namespace osu.Game.Rulesets.Osu.UI
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SettingsCheckbox
|
||||
new SettingsItemV2(new FormCheckBox
|
||||
{
|
||||
LabelText = RulesetSettingsStrings.SnakingInSliders,
|
||||
Caption = RulesetSettingsStrings.SnakingInSliders,
|
||||
Current = config.GetBindable<bool>(OsuRulesetSetting.SnakingInSliders)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
}),
|
||||
new SettingsItemV2(new FormCheckBox
|
||||
{
|
||||
ClassicDefault = false,
|
||||
LabelText = RulesetSettingsStrings.SnakingOutSliders,
|
||||
Caption = RulesetSettingsStrings.SnakingOutSliders,
|
||||
Current = config.GetBindable<bool>(OsuRulesetSetting.SnakingOutSliders)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
})
|
||||
{
|
||||
LabelText = RulesetSettingsStrings.CursorTrail,
|
||||
ApplyClassicDefault = c => ((IHasCurrentValue<bool>)c).Current.Value = false,
|
||||
},
|
||||
new SettingsItemV2(new FormCheckBox
|
||||
{
|
||||
Caption = RulesetSettingsStrings.CursorTrail,
|
||||
Current = config.GetBindable<bool>(OsuRulesetSetting.ShowCursorTrail)
|
||||
},
|
||||
new SettingsCheckbox
|
||||
}),
|
||||
new SettingsItemV2(new FormCheckBox
|
||||
{
|
||||
LabelText = RulesetSettingsStrings.CursorRipples,
|
||||
Caption = RulesetSettingsStrings.CursorRipples,
|
||||
Current = config.GetBindable<bool>(OsuRulesetSetting.ShowCursorRipples)
|
||||
},
|
||||
new SettingsEnumDropdown<PlayfieldBorderStyle>
|
||||
}),
|
||||
new SettingsItemV2(new FormEnumDropdown<PlayfieldBorderStyle>
|
||||
{
|
||||
LabelText = RulesetSettingsStrings.PlayfieldBorderStyle,
|
||||
Caption = RulesetSettingsStrings.PlayfieldBorderStyle,
|
||||
Current = config.GetBindable<PlayfieldBorderStyle>(OsuRulesetSetting.PlayfieldBorderStyle),
|
||||
},
|
||||
}),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Rulesets.Taiko.Configuration;
|
||||
@@ -26,11 +27,11 @@ namespace osu.Game.Rulesets.Taiko
|
||||
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new SettingsEnumDropdown<TaikoTouchControlScheme>
|
||||
new SettingsItemV2(new FormEnumDropdown<TaikoTouchControlScheme>
|
||||
{
|
||||
LabelText = RulesetSettingsStrings.TouchControlScheme,
|
||||
Caption = RulesetSettingsStrings.TouchControlScheme,
|
||||
Current = config.GetBindable<TaikoTouchControlScheme>(TaikoRulesetSetting.TouchControlScheme)
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -194,7 +194,7 @@ namespace osu.Game.Rulesets.Taiko.UI
|
||||
|
||||
var hitWindows = new TaikoHitWindows();
|
||||
|
||||
HitResult[] usableHitResults = Enum.GetValues<HitResult>().Where(r => hitWindows.IsHitResultAllowed(r)).ToArray();
|
||||
HitResult[] usableHitResults = Enum.GetValues<HitResult>().Where(hitWindows.IsHitResultAllowed).ToArray();
|
||||
|
||||
AddInternal(judgementPooler = new JudgementPooler<DrawableTaikoJudgement>(usableHitResults));
|
||||
|
||||
|
||||
@@ -247,7 +247,7 @@ namespace osu.Game.Tests.Beatmaps
|
||||
|
||||
AddStep("change all start times", () =>
|
||||
{
|
||||
editorBeatmap.HitObjectUpdated += h => updatedObjects.Add(h);
|
||||
editorBeatmap.HitObjectUpdated += updatedObjects.Add;
|
||||
|
||||
for (int i = 0; i < 10; i++)
|
||||
allHitObjects[i].StartTime += 10;
|
||||
@@ -282,7 +282,7 @@ namespace osu.Game.Tests.Beatmaps
|
||||
|
||||
AddStep("change start time twice", () =>
|
||||
{
|
||||
editorBeatmap.HitObjectUpdated += h => updatedObjects.Add(h);
|
||||
editorBeatmap.HitObjectUpdated += updatedObjects.Add;
|
||||
|
||||
editorBeatmap.HitObjects[0].StartTime = 10;
|
||||
editorBeatmap.HitObjects[0].StartTime = 20;
|
||||
|
||||
@@ -104,10 +104,7 @@ namespace osu.Game.Tests.Database
|
||||
Assert.AreNotEqual(detachedBeatmapSet.Status, BeatmapOnlineStatus.Ranked);
|
||||
detachedBeatmapSet.Status = BeatmapOnlineStatus.Ranked;
|
||||
|
||||
beatmapSet.PerformWrite(s =>
|
||||
{
|
||||
detachedBeatmapSet.CopyChangesToRealm(s);
|
||||
});
|
||||
beatmapSet.PerformWrite(detachedBeatmapSet.CopyChangesToRealm);
|
||||
|
||||
beatmapSet.PerformRead(s =>
|
||||
{
|
||||
|
||||
@@ -7,7 +7,9 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Beatmaps.Drawables;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osuTK;
|
||||
@@ -31,7 +33,7 @@ namespace osu.Game.Tests.Visual.Colours
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(5f),
|
||||
ChildrenEnumerable = Enumerable.Range(0, 10).Select(i => new FillFlowContainer
|
||||
ChildrenEnumerable = Enumerable.Range(0, 15).Select(i => new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@@ -40,7 +42,9 @@ namespace osu.Game.Tests.Visual.Colours
|
||||
Spacing = new Vector2(10f),
|
||||
ChildrenEnumerable = Enumerable.Range(0, 10).Select(j =>
|
||||
{
|
||||
var colour = colours.ForStarDifficulty(1f * i + 0.1f * j);
|
||||
float difficulty = 1f * i + 0.1f * j;
|
||||
var colour = colours.ForStarDifficulty(difficulty);
|
||||
var textColour = colours.ForStarDifficultyText(difficulty);
|
||||
|
||||
return new FillFlowContainer
|
||||
{
|
||||
@@ -48,36 +52,27 @@ namespace osu.Game.Tests.Visual.Colours
|
||||
Origin = Anchor.Centre,
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0f, 10f),
|
||||
Spacing = new Vector2(0f, 5f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new CircularContainer
|
||||
new OsuSpriteText
|
||||
{
|
||||
Masking = true,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Size = new Vector2(75f, 25f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colour,
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Colour = OsuColour.ForegroundTextColourFor(colour),
|
||||
Text = colour.ToHex(),
|
||||
},
|
||||
}
|
||||
Font = FontUsage.Default.With(size: 10),
|
||||
Text = $"BG: {colour.ToHex()}",
|
||||
},
|
||||
new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Text = $"*{(1f * i + 0.1f * j):0.00}",
|
||||
Font = FontUsage.Default.With(size: 10),
|
||||
Text = $"Text: {textColour.ToHex()}",
|
||||
},
|
||||
new StarRatingDisplay(new StarDifficulty(difficulty, 0))
|
||||
{
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
@@ -37,6 +37,42 @@ namespace osu.Game.Tests.Visual.Editing
|
||||
|
||||
private GlobalActionContainer globalActionContainer => this.ChildrenOfType<GlobalActionContainer>().Single();
|
||||
|
||||
[Test]
|
||||
public void TestPlaceThenUndo()
|
||||
{
|
||||
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
|
||||
AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType<Playfield>().Single()));
|
||||
AddStep("place circle", () => InputManager.Click(MouseButton.Left));
|
||||
|
||||
AddAssert("one circle added", () => EditorBeatmap.HitObjects, () => Has.One.Items);
|
||||
|
||||
AddStep("undo", () => Editor.Undo());
|
||||
|
||||
AddAssert("circle removed", () => EditorBeatmap.HitObjects, () => Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestTimingLost()
|
||||
{
|
||||
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
|
||||
AddStep("move mouse to center of playfield", () => InputManager.MoveMouseTo(this.ChildrenOfType<Playfield>().Single()));
|
||||
|
||||
AddAssert("placement ready", () => this.ChildrenOfType<ComposeBlueprintContainer>().Single().CurrentPlacement, () => Is.Not.Null);
|
||||
|
||||
AddStep("nuke timing", () => EditorBeatmap.ControlPointInfo.Clear());
|
||||
|
||||
AddAssert("placement not available", () => this.ChildrenOfType<ComposeBlueprintContainer>().Single().CurrentPlacement, () => Is.Null);
|
||||
|
||||
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
|
||||
|
||||
AddAssert("placement not available", () => this.ChildrenOfType<ComposeBlueprintContainer>().Single().CurrentPlacement, () => Is.Null);
|
||||
|
||||
AddStep("add back timing", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint()));
|
||||
AddStep("select circle placement tool", () => InputManager.Key(Key.Number2));
|
||||
|
||||
AddAssert("placement ready", () => this.ChildrenOfType<ComposeBlueprintContainer>().Single().CurrentPlacement, () => Is.Not.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDeleteUsingMiddleMouse()
|
||||
{
|
||||
|
||||
@@ -270,7 +270,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
AddStep("create overlay", () =>
|
||||
{
|
||||
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>());
|
||||
hudOverlay = new HUDOverlay(null, Array.Empty<Mod>(), new PlayerConfiguration());
|
||||
|
||||
// Add any key just to display the key counter visually.
|
||||
hudOverlay.InputCountController.Add(new KeyCounterKeyboardTrigger(Key.Space));
|
||||
|
||||
@@ -119,7 +119,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
Children = new Drawable[]
|
||||
{
|
||||
drawableRuleset,
|
||||
new HUDOverlay(drawableRuleset, [])
|
||||
new HUDOverlay(drawableRuleset, [], new PlayerConfiguration())
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
}
|
||||
|
||||
@@ -151,7 +151,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
List<SkinBlueprint> blueprints = new List<SkinBlueprint>();
|
||||
|
||||
AddStep("clear list", () => blueprints.Clear());
|
||||
AddStep("clear list", blueprints.Clear);
|
||||
|
||||
for (int i = 0; i < 3; i++)
|
||||
{
|
||||
|
||||
@@ -60,7 +60,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
|
||||
var drawableRuleset = ruleset.CreateDrawableRulesetWith(beatmap, mods);
|
||||
|
||||
var hudOverlay = new HUDOverlay(drawableRuleset, mods)
|
||||
var hudOverlay = new HUDOverlay(drawableRuleset, mods, new PlayerConfiguration())
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual.Gameplay
|
||||
{
|
||||
SetContents(_ =>
|
||||
{
|
||||
hudOverlay = new HUDOverlay(new DrawableOsuRuleset(new OsuRuleset(), new OsuBeatmap()), Array.Empty<Mod>());
|
||||
hudOverlay = new HUDOverlay(new DrawableOsuRuleset(new OsuRuleset(), new OsuBeatmap()), Array.Empty<Mod>(), new PlayerConfiguration());
|
||||
|
||||
action?.Invoke(hudOverlay);
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Online.API.Requests.Responses;
|
||||
@@ -45,10 +46,46 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
|
||||
AddStep("add panel", () =>
|
||||
{
|
||||
var beatmap = CreateAPIBeatmap();
|
||||
|
||||
beatmap.TopTags =
|
||||
[
|
||||
new APIBeatmapTag { TagId = 4, VoteCount = 1 },
|
||||
new APIBeatmapTag { TagId = 2, VoteCount = 1 },
|
||||
new APIBeatmapTag { TagId = 23, VoteCount = 5 },
|
||||
];
|
||||
|
||||
beatmap.BeatmapSet!.HasExplicitContent = true;
|
||||
beatmap.BeatmapSet!.HasVideo = true;
|
||||
beatmap.BeatmapSet!.HasStoryboard = true;
|
||||
beatmap.BeatmapSet.FeaturedInSpotlight = true;
|
||||
beatmap.BeatmapSet.TrackId = 1;
|
||||
beatmap.BeatmapSet!.RelatedTags =
|
||||
[
|
||||
new APITag
|
||||
{
|
||||
Id = 2,
|
||||
Name = "song representation/simple",
|
||||
Description = "Accessible and straightforward map design."
|
||||
},
|
||||
new APITag
|
||||
{
|
||||
Id = 4,
|
||||
Name = "style/clean",
|
||||
Description = "Visually uncluttered and organised patterns, often involving few overlaps and equal visual spacing between objects."
|
||||
},
|
||||
new APITag
|
||||
{
|
||||
Id = 23,
|
||||
Name = "aim/aim control",
|
||||
Description = "Patterns with velocity or direction changes which strongly go against a player's natural movement pattern."
|
||||
}
|
||||
];
|
||||
|
||||
Child = new OsuContextMenuContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = panel = new MatchmakingSelectPanelBeatmap(new MatchmakingPlaylistItem(new MultiplayerPlaylistItem(), CreateAPIBeatmap(), []))
|
||||
Child = panel = new MatchmakingSelectPanelBeatmap(new MatchmakingPlaylistItem(new MultiplayerPlaylistItem(), beatmap, []))
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
@@ -96,6 +133,12 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
};
|
||||
});
|
||||
|
||||
AddStep("add peppy", () => panel!.AddUser(new APIUser
|
||||
{
|
||||
Id = 2,
|
||||
Username = "peppy",
|
||||
}));
|
||||
|
||||
AddToggleStep("allow selection", value => panel!.AllowSelection = value);
|
||||
|
||||
AddStep("reveal beatmap", () => panel!.PresentAsChosenBeatmap(new MatchmakingPlaylistItem(new MultiplayerPlaylistItem(), CreateAPIBeatmap(), [])));
|
||||
|
||||
@@ -122,5 +122,51 @@ namespace osu.Game.Tests.Visual.Matchmaking
|
||||
AddStep("set download progress 90%", () => MultiplayerClient.ChangeUserBeatmapAvailability(2, BeatmapAvailability.Downloading(0.9f)));
|
||||
AddStep("set locally available", () => MultiplayerClient.ChangeUserBeatmapAvailability(2, BeatmapAvailability.LocallyAvailable()));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestLongUsername()
|
||||
{
|
||||
AddStep("set long username", () =>
|
||||
{
|
||||
MultiplayerClient.ChangeMatchRoomState(new MatchmakingRoomState
|
||||
{
|
||||
Users =
|
||||
{
|
||||
UserDictionary =
|
||||
{
|
||||
{
|
||||
2, new MatchmakingUser
|
||||
{
|
||||
UserId = 2,
|
||||
Placement = 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}).WaitSafely();
|
||||
|
||||
Child = panel = new PlayerPanel(new MultiplayerRoomUser(2)
|
||||
{
|
||||
User = new APIUser
|
||||
{
|
||||
Username = @"ThisIsALongUsername",
|
||||
Id = 2,
|
||||
Colour = "99EB47",
|
||||
CountryCode = CountryCode.AU,
|
||||
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/2/baba245ef60834b769694178f8f6d4f6166c5188c740de084656ad2b80f1eea7.jpeg",
|
||||
Statistics = new UserStatistics { GlobalRank = null, CountryRank = null }
|
||||
}
|
||||
})
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre
|
||||
};
|
||||
});
|
||||
|
||||
foreach (var layout in Enum.GetValues<PlayerPanelDisplayMode>())
|
||||
{
|
||||
AddStep($"set layout to {layout}", () => panel.DisplayMode = layout);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+22
-32
@@ -9,9 +9,7 @@ using osu.Framework.Audio;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Framework.Screens;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Beatmaps;
|
||||
@@ -22,7 +20,6 @@ using osu.Game.Rulesets.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Rulesets.Osu.Mods;
|
||||
using osu.Game.Screens.OnlinePlay;
|
||||
using osu.Game.Screens.OnlinePlay.Components;
|
||||
using osu.Game.Screens.OnlinePlay.Playlists;
|
||||
using osu.Game.Tests.Resources;
|
||||
using osu.Game.Tests.Visual.OnlinePlay;
|
||||
@@ -30,7 +27,7 @@ using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Multiplayer
|
||||
{
|
||||
public partial class TestScenePlaylistsSongSelect : OnlinePlayTestScene
|
||||
public partial class TestScenePlaylistsSongSelectV2 : OnlinePlayTestScene
|
||||
{
|
||||
private RulesetStore rulesets = null!;
|
||||
private BeatmapManager manager = null!;
|
||||
@@ -69,47 +66,45 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
});
|
||||
|
||||
AddStep("create song select", () => LoadScreen(songSelect = new TestPlaylistsSongSelect(room)));
|
||||
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded);
|
||||
AddUntilStep("wait for song select", () => songSelect.IsLoaded && !songSelect.IsFiltering);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestShowScreen()
|
||||
{
|
||||
AddStep("show screen", () => { });
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestItemAddedIfEmptyOnStart()
|
||||
{
|
||||
AddStep("finalise selection", () => songSelect.FinaliseSelection());
|
||||
AddStep("finalise selection", () => InputManager.Key(Key.Enter));
|
||||
AddAssert("playlist has 1 item", () => room.Playlist.Count == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestItemAddedWhenCreateNewItemClicked()
|
||||
{
|
||||
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
|
||||
AddAssert("playlist has 1 item", () => room.Playlist.Count == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestItemNotAddedIfExistingOnStart()
|
||||
{
|
||||
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
|
||||
AddStep("finalise selection", () => songSelect.FinaliseSelection());
|
||||
AddStep("create new item", () => songSelect.AddNewItem());
|
||||
AddAssert("playlist has 1 item", () => room.Playlist.Count == 1);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAddSameItemMultipleTimes()
|
||||
{
|
||||
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
|
||||
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
|
||||
AddStep("create new item", () => songSelect.AddNewItem());
|
||||
AddStep("create new item", () => songSelect.AddNewItem());
|
||||
AddAssert("playlist has 2 items", () => room.Playlist.Count == 2);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAddItemAfterRearrangement()
|
||||
{
|
||||
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
|
||||
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
|
||||
AddStep("create new item", () => songSelect.AddNewItem());
|
||||
AddStep("create new item", () => songSelect.AddNewItem());
|
||||
AddStep("rearrange", () => room.Playlist = room.Playlist.Skip(1).Append(room.Playlist[0]).ToArray());
|
||||
|
||||
AddStep("create new item", () => songSelect.BeatmapDetails.CreateNewItem!());
|
||||
AddStep("create new item", () => songSelect.AddNewItem());
|
||||
AddAssert("new item has id 2", () => room.Playlist.Last().ID == 2);
|
||||
}
|
||||
|
||||
@@ -120,9 +115,9 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
public void TestNewItemHasNewModInstances()
|
||||
{
|
||||
AddStep("set dt mod", () => SelectedMods.Value = new[] { new OsuModDoubleTime() });
|
||||
AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem!());
|
||||
AddStep("create item", () => songSelect.AddNewItem());
|
||||
AddStep("change mod rate", () => ((OsuModDoubleTime)SelectedMods.Value[0]).SpeedChange.Value = 2);
|
||||
AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem!());
|
||||
AddStep("create item", () => songSelect.AddNewItem());
|
||||
|
||||
AddAssert("item 1 has rate 1.5", () =>
|
||||
{
|
||||
@@ -153,7 +148,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
mod = (OsuModDoubleTime)SelectedMods.Value[0];
|
||||
});
|
||||
|
||||
AddStep("create item", () => songSelect.BeatmapDetails.CreateNewItem!());
|
||||
AddStep("create item", () => songSelect.AddNewItem());
|
||||
|
||||
AddStep("change stored mod rate", () => mod.SpeedChange.Value = 2);
|
||||
AddAssert("item has rate 1.5", () =>
|
||||
@@ -166,26 +161,23 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
[Test]
|
||||
public void TestFreeModSelectionDisable()
|
||||
{
|
||||
FooterButtonFreeMods freeMods = null!;
|
||||
|
||||
AddAssert("freestyle enabled", () => songSelect.Freestyle.Value, () => Is.True);
|
||||
AddStep("click icon in free mods button", () =>
|
||||
{
|
||||
freeMods = this.ChildrenOfType<FooterButtonFreeMods>().Single();
|
||||
InputManager.MoveMouseTo(freeMods.ChildrenOfType<SpriteIcon>().Single());
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<FooterButtonFreeModsV2>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("mod select not visible", () => this.ChildrenOfType<FreeModSelectOverlay>().Single().State.Value, () => Is.EqualTo(Visibility.Hidden));
|
||||
|
||||
AddStep("toggle freestyle off", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<FooterButtonFreestyle>().Single());
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<FooterButtonFreestyleV2>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("freestyle disabled", () => songSelect.Freestyle.Value, () => Is.False);
|
||||
AddStep("click icon in free mods button", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(freeMods.ChildrenOfType<SpriteIcon>().Single());
|
||||
InputManager.MoveMouseTo(this.ChildrenOfType<FooterButtonFreeModsV2>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("mod select visible", () => this.ChildrenOfType<FreeModSelectOverlay>().Single().State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
@@ -199,10 +191,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
|
||||
rulesets.Dispose();
|
||||
}
|
||||
|
||||
private partial class TestPlaylistsSongSelect : PlaylistsSongSelect
|
||||
private partial class TestPlaylistsSongSelect : PlaylistsSongSelectV2
|
||||
{
|
||||
public new MatchBeatmapDetailArea BeatmapDetails => (MatchBeatmapDetailArea)base.BeatmapDetails;
|
||||
|
||||
public new IBindable<bool> Freestyle => base.Freestyle;
|
||||
|
||||
public TestPlaylistsSongSelect(Room room)
|
||||
@@ -94,21 +94,22 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
|
||||
AddStep("edit playlist", () => InputManager.Key(Key.Enter));
|
||||
|
||||
AddUntilStep("wait for song select", () => (playlistScreen.CurrentSubScreen as PlaylistsSongSelect)?.BeatmapSetsLoaded == true);
|
||||
AddUntilStep("wait for song select", () => playlistScreen.CurrentSubScreen is PlaylistsSongSelectV2 songSelect && songSelect.IsLoaded && !songSelect.IsFiltering);
|
||||
|
||||
AddUntilStep("wait for selection", () => !Game.Beatmap.IsDefault);
|
||||
|
||||
AddStep("add item", () => InputManager.Key(Key.Enter));
|
||||
AddStep("exit screen", () => InputManager.Key(Key.Escape));
|
||||
|
||||
AddUntilStep("wait for return to playlist screen", () => playlistScreen.CurrentSubScreen is PlaylistsRoomSubScreen);
|
||||
|
||||
AddStep("go back to song select", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(playlistScreen.ChildrenOfType<PurpleRoundedButton>().Single(b => b.Text == "Edit playlist"));
|
||||
InputManager.MoveMouseTo(playlistScreen.ChildrenOfType<PurpleRoundedButton>().Single(b => b.Text == "+ Add more beatmaps"));
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
|
||||
AddUntilStep("wait for song select", () => (playlistScreen.CurrentSubScreen as PlaylistsSongSelect)?.BeatmapSetsLoaded == true);
|
||||
AddUntilStep("wait for song select", () => playlistScreen.CurrentSubScreen is PlaylistsSongSelectV2 songSelect && songSelect.IsLoaded && !songSelect.IsFiltering);
|
||||
|
||||
AddStep("press home button", () =>
|
||||
{
|
||||
@@ -141,13 +142,12 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
public void TestExitSongSelectWithEscape()
|
||||
{
|
||||
SoloSongSelect songSelect = null;
|
||||
ModSelectOverlay modSelect = null;
|
||||
|
||||
PushAndConfirm(() => songSelect = new SoloSongSelect());
|
||||
PushAndConfirm(() => new SoloSongSelect());
|
||||
AddStep("Show mods overlay", () =>
|
||||
{
|
||||
modSelect = songSelect!.ChildrenOfType<ModSelectOverlay>().Single();
|
||||
modSelect = Game!.ChildrenOfType<ModSelectOverlay>().Single();
|
||||
modSelect.Show();
|
||||
});
|
||||
AddAssert("Overlay was shown", () => modSelect.State.Value == Visibility.Visible);
|
||||
@@ -309,11 +309,9 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
[Test]
|
||||
public void TestOpenModSelectOverlayUsingAction()
|
||||
{
|
||||
SoloSongSelect songSelect = null;
|
||||
|
||||
PushAndConfirm(() => songSelect = new SoloSongSelect());
|
||||
PushAndConfirm(() => new SoloSongSelect());
|
||||
AddStep("Show mods overlay", () => InputManager.Key(Key.F1));
|
||||
AddAssert("Overlay was shown", () => songSelect!.ChildrenOfType<ModSelectOverlay>().Single().State.Value == Visibility.Visible);
|
||||
AddAssert("Overlay was shown", () => Game!.ChildrenOfType<ModSelectOverlay>().Single().State.Value == Visibility.Visible);
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -730,7 +728,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
PushAndConfirm(() => songSelect = new SoloSongSelect());
|
||||
AddStep("Show mods overlay", () =>
|
||||
{
|
||||
modSelect = songSelect!.ChildrenOfType<ModSelectOverlay>().Single();
|
||||
modSelect = Game!.ChildrenOfType<ModSelectOverlay>().Single();
|
||||
modSelect.Show();
|
||||
});
|
||||
AddAssert("Overlay was shown", () => modSelect.State.Value == Visibility.Visible);
|
||||
@@ -805,13 +803,12 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
{
|
||||
AddUntilStep("Wait for toolbar to load", () => Game.Toolbar.IsLoaded);
|
||||
|
||||
SoloSongSelect songSelect = null;
|
||||
ModSelectOverlay modSelect = null;
|
||||
|
||||
PushAndConfirm(() => songSelect = new SoloSongSelect());
|
||||
PushAndConfirm(() => new SoloSongSelect());
|
||||
AddStep("Show mods overlay", () =>
|
||||
{
|
||||
modSelect = songSelect!.ChildrenOfType<ModSelectOverlay>().Single();
|
||||
modSelect = Game!.ChildrenOfType<ModSelectOverlay>().Single();
|
||||
modSelect.Show();
|
||||
});
|
||||
AddAssert("Overlay was shown", () => modSelect.State.Value == Visibility.Visible);
|
||||
|
||||
@@ -38,7 +38,7 @@ namespace osu.Game.Tests.Visual.Navigation
|
||||
public partial class TestSceneSkinEditorNavigation : OsuGameTestScene
|
||||
{
|
||||
private SoloSongSelect songSelect;
|
||||
private ModSelectOverlay modSelect => songSelect.ChildrenOfType<ModSelectOverlay>().First();
|
||||
private ModSelectOverlay modSelect => Game.ChildrenOfType<ModSelectOverlay>().First();
|
||||
|
||||
private SkinEditor skinEditor => Game.ChildrenOfType<SkinEditor>().FirstOrDefault();
|
||||
|
||||
|
||||
@@ -240,6 +240,7 @@ namespace osu.Game.Tests.Visual.Online
|
||||
CoverUrl = TestResources.COVER_IMAGE_1,
|
||||
JoinDate = DateTimeOffset.Now.AddDays(-1),
|
||||
LastVisit = DateTimeOffset.Now,
|
||||
PreviousUsernames = ["ForgetMe", "MySpaceLover", "i once was a man named enis", "mr anderson"],
|
||||
Groups = new[]
|
||||
{
|
||||
new APIUserGroup { Colour = "#EB47D0", ShortName = "DEV", Name = "Developers" },
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
// 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.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.OnlinePlay.Playlists;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Playlists
|
||||
{
|
||||
public partial class TestSceneAddToPlaylistFooterButton : OsuTestScene
|
||||
{
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||
|
||||
private AddToPlaylistFooterButton button = null!;
|
||||
|
||||
[SetUp]
|
||||
public void Setup() => Schedule(() =>
|
||||
{
|
||||
Child = button = new AddToPlaylistFooterButton
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Action = () => { }
|
||||
};
|
||||
});
|
||||
|
||||
[Test]
|
||||
public void TestAppearDisappear()
|
||||
{
|
||||
AddStep("appear", () => button.Appear());
|
||||
AddWaitStep("wait for animation", 3);
|
||||
AddStep("disappear", () => button.Disappear());
|
||||
AddWaitStep("wait for animation", 3);
|
||||
AddStep("appear", () => button.Appear());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
// 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.Game.Overlays;
|
||||
using osu.Game.Overlays.Mods;
|
||||
using osu.Game.Rulesets.Osu;
|
||||
using osu.Game.Screens.OnlinePlay;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Playlists
|
||||
{
|
||||
public partial class TestSceneFooterButtonFreeModsV2 : OsuTestScene
|
||||
{
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||
|
||||
private readonly FooterButtonFreeModsV2 button;
|
||||
|
||||
public TestSceneFooterButtonFreeModsV2()
|
||||
{
|
||||
ModSelectOverlay modSelectOverlay;
|
||||
Add(modSelectOverlay = new TestModSelectOverlay());
|
||||
Add(button = new FooterButtonFreeModsV2(modSelectOverlay)
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.CentreLeft,
|
||||
X = -100,
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAllMods()
|
||||
{
|
||||
AddStep("all mods", () => button.FreeMods.Value = new OsuRuleset().CreateAllMods().ToArray());
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNoMods()
|
||||
{
|
||||
AddStep("no mods", () => button.FreeMods.Value = []);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFreestyle()
|
||||
{
|
||||
AddToggleStep("toggle freestyle", v => button.Freestyle.Value = v);
|
||||
}
|
||||
|
||||
private partial class TestModSelectOverlay : UserModSelectOverlay
|
||||
{
|
||||
public TestModSelectOverlay()
|
||||
: base(OverlayColourScheme.Aquamarine)
|
||||
{
|
||||
IsValidMod = _ => true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Screens.OnlinePlay;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Playlists
|
||||
{
|
||||
public partial class TestSceneFooterButtonFreestyleV2 : OsuTestScene
|
||||
{
|
||||
[Cached]
|
||||
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
|
||||
|
||||
public TestSceneFooterButtonFreestyleV2()
|
||||
{
|
||||
Add(new FooterButtonFreestyleV2
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.CentreLeft,
|
||||
X = -100,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
// 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.Graphics;
|
||||
using osu.Game.Online.Rooms;
|
||||
using osu.Game.Screens.OnlinePlay.Playlists;
|
||||
using osu.Game.Tests.Visual.OnlinePlay;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Playlists
|
||||
{
|
||||
public partial class TestScenePlaylistTray : OnlinePlayTestScene
|
||||
{
|
||||
private Room room = null!;
|
||||
|
||||
public override void SetUpSteps()
|
||||
{
|
||||
base.SetUpSteps();
|
||||
|
||||
AddStep("add tray", () => Child = new PlaylistsSongSelectV2.PlaylistTray(room = new Room())
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestAddItem()
|
||||
{
|
||||
AddStep("add playlist item", () =>
|
||||
{
|
||||
room.Playlist = room.Playlist.Append(new PlaylistItem(CreateAPIBeatmap())).ToArray();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -11,6 +11,7 @@ using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Settings.Sections.Audio;
|
||||
using osu.Game.Scoring;
|
||||
using osu.Game.Tests.Visual.Ranking;
|
||||
@@ -25,6 +26,9 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
[Cached]
|
||||
private SessionAverageHitErrorTracker tracker = new SessionAverageHitErrorTracker();
|
||||
|
||||
[Cached]
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||
|
||||
private Container content = null!;
|
||||
protected override Container Content => content;
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Overlays.Settings.Sections.Input;
|
||||
using osu.Game.Rulesets.Taiko;
|
||||
using osuTK.Input;
|
||||
@@ -202,16 +203,16 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
InputManager.ReleaseKey(Key.P);
|
||||
});
|
||||
|
||||
AddUntilStep("restore button shown", () => settingsKeyBindingRow.ChildrenOfType<RevertToDefaultButton<bool>>().First().Alpha > 0);
|
||||
AddUntilStep("restore button shown", () => settingsKeyBindingRow.ChildrenOfType<SettingsRevertToDefaultButton>().First().Alpha > 0);
|
||||
|
||||
AddStep("click reset button for bindings", () =>
|
||||
{
|
||||
var resetButton = settingsKeyBindingRow.ChildrenOfType<RevertToDefaultButton<bool>>().First();
|
||||
var resetButton = settingsKeyBindingRow.ChildrenOfType<SettingsRevertToDefaultButton>().First();
|
||||
|
||||
resetButton.TriggerClick();
|
||||
});
|
||||
|
||||
AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType<RevertToDefaultButton<bool>>().First().Alpha == 0);
|
||||
AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType<SettingsRevertToDefaultButton>().First().Alpha == 0);
|
||||
|
||||
AddAssert("binding cleared",
|
||||
() => settingsKeyBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(0).KeyBinding.Value.KeyCombination.Equals(settingsKeyBindingRow.Defaults.ElementAt(0)));
|
||||
@@ -232,7 +233,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
InputManager.ReleaseKey(Key.P);
|
||||
});
|
||||
|
||||
AddUntilStep("restore button shown", () => settingsKeyBindingRow.ChildrenOfType<RevertToDefaultButton<bool>>().First().Alpha > 0);
|
||||
AddUntilStep("restore button shown", () => settingsKeyBindingRow.ChildrenOfType<SettingsRevertToDefaultButton>().First().Alpha > 0);
|
||||
|
||||
AddStep("click reset button for bindings", () =>
|
||||
{
|
||||
@@ -241,7 +242,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
resetButton.TriggerClick();
|
||||
});
|
||||
|
||||
AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType<RevertToDefaultButton<bool>>().First().Alpha == 0);
|
||||
AddUntilStep("restore button hidden", () => settingsKeyBindingRow.ChildrenOfType<SettingsRevertToDefaultButton>().First().Alpha == 0);
|
||||
|
||||
AddAssert("binding cleared",
|
||||
() => settingsKeyBindingRow.ChildrenOfType<KeyBindingRow.KeyButton>().ElementAt(0).KeyBinding.Value.KeyCombination.Equals(settingsKeyBindingRow.Defaults.ElementAt(0)));
|
||||
@@ -394,7 +395,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
AddStep("reset Left (centre) to default", () =>
|
||||
{
|
||||
var row = panel.ChildrenOfType<KeyBindingRow>().First(r => r.ChildrenOfType<OsuSpriteText>().Any(s => s.Text.ToString() == "Left (centre)"));
|
||||
row.ChildrenOfType<RevertToDefaultButton<bool>>().Single().TriggerClick();
|
||||
row.ChildrenOfType<SettingsRevertToDefaultButton>().Single().TriggerClick();
|
||||
});
|
||||
|
||||
KeyBindingConflictPopover popover = null;
|
||||
@@ -450,7 +451,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
AddStep("revert row to default", () =>
|
||||
{
|
||||
var row = panel.ChildrenOfType<KeyBindingRow>().First(r => r.ChildrenOfType<OsuSpriteText>().Any(s => s.Text.ToString() == "Left (centre)"));
|
||||
InputManager.MoveMouseTo(row.ChildrenOfType<RevertToDefaultButton<bool>>().Single());
|
||||
InputManager.MoveMouseTo(row.ChildrenOfType<SettingsRevertToDefaultButton>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddWaitStep("wait a bit", 3);
|
||||
|
||||
@@ -10,6 +10,7 @@ using osu.Framework.Input.Bindings;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Overlays.Settings.Sections.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.Settings
|
||||
@@ -45,7 +46,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
row.KeyBindings.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.Escape)));
|
||||
row.KeyBindings.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.ExtraMouseButton1)));
|
||||
});
|
||||
AddUntilStep("revert to default button not shown", () => row.ChildrenOfType<RevertToDefaultButton<bool>>().Single().Alpha, () => Is.Zero);
|
||||
AddUntilStep("revert to default button not shown", () => row.ChildrenOfType<SettingsRevertToDefaultButton>().Single().Alpha, () => Is.Zero);
|
||||
|
||||
AddStep("change key bindings", () =>
|
||||
{
|
||||
@@ -54,7 +55,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
row.KeyBindings.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.Z)));
|
||||
row.KeyBindings.Add(new RealmKeyBinding(GlobalAction.Back, new KeyCombination(InputKey.I)));
|
||||
});
|
||||
AddUntilStep("revert to default button not shown", () => row.ChildrenOfType<RevertToDefaultButton<bool>>().Single().Alpha, () => Is.Not.Zero);
|
||||
AddUntilStep("revert to default button not shown", () => row.ChildrenOfType<SettingsRevertToDefaultButton>().Single().Alpha, () => Is.Not.Zero);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,7 @@ using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Containers;
|
||||
@@ -31,7 +32,6 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
|
||||
|
||||
private FormSliderBar<float> sliderBar = null!;
|
||||
private FormSliderBar<float> classicSliderBar = null!;
|
||||
|
||||
private SearchContainer searchContainer = null!;
|
||||
|
||||
@@ -173,7 +173,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
{
|
||||
ShowRevertToDefaultButton = false
|
||||
},
|
||||
new SettingsItemV2(classicSliderBar = new FormSliderBar<float>
|
||||
new SettingsItemV2(new FormSliderBar<float>
|
||||
{
|
||||
Caption = "Slider with classic default",
|
||||
Current = new BindableFloat
|
||||
@@ -185,7 +185,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
},
|
||||
})
|
||||
{
|
||||
ApplyClassicDefault = () => classicSliderBar.Current.Value = 2,
|
||||
ApplyClassicDefault = c => ((IHasCurrentValue<float>)c).Current.Value = 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -8,6 +8,7 @@ using NUnit.Framework;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input.Handlers;
|
||||
using osu.Framework.Input.Handlers.Tablet;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Framework.Utils;
|
||||
@@ -69,7 +70,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
{
|
||||
AddStep("Test with wide tablet", () => tabletHandler.SetTabletSize(new Vector2(160, 100)));
|
||||
|
||||
AddStep("Reset to full area", () => settings.ChildrenOfType<DangerousSettingsButton>().First().TriggerClick());
|
||||
AddStep("Reset to full area", () => settings.ChildrenOfType<DangerousSettingsButtonV2>().First().TriggerClick());
|
||||
ensureValid();
|
||||
|
||||
AddStep("rotate 10", () => tabletHandler.Rotation.Value = 10);
|
||||
@@ -129,7 +130,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
|
||||
private void ensureInvalid() => AddAssert("area invalid", () => !settings.AreaSelection.IsWithinBounds);
|
||||
|
||||
public class TestTabletHandler : ITabletHandler
|
||||
public class TestTabletHandler : InputHandler, ITabletHandler
|
||||
{
|
||||
public Bindable<Vector2> AreaOffset { get; } = new Bindable<Vector2>();
|
||||
public Bindable<Vector2> AreaSize { get; } = new Bindable<Vector2>();
|
||||
@@ -149,7 +150,7 @@ namespace osu.Game.Tests.Visual.Settings
|
||||
|
||||
private readonly Bindable<TabletInfo> tablet = new Bindable<TabletInfo>();
|
||||
|
||||
public BindableBool Enabled { get; } = new BindableBool(true);
|
||||
public override bool IsActive => true;
|
||||
|
||||
public void SetTabletSize(Vector2 size)
|
||||
{
|
||||
|
||||
@@ -44,6 +44,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
|
||||
protected TestBeatmapCarousel Carousel = null!;
|
||||
|
||||
protected bool RetainSelection { get; set; }
|
||||
|
||||
protected OsuScrollContainer<Drawable> Scroll => Carousel.ChildrenOfType<OsuScrollContainer<Drawable>>().Single();
|
||||
|
||||
[Cached(typeof(BeatmapStore))]
|
||||
@@ -78,7 +80,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
Dependencies.Cache(Realm);
|
||||
}
|
||||
|
||||
protected void CreateCarousel()
|
||||
protected void CreateCarousel(bool retainSelection = false)
|
||||
{
|
||||
AddStep("create components", () =>
|
||||
{
|
||||
@@ -87,6 +89,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
BeatmapRecommendationFunction = null;
|
||||
NewItemsPresentedInvocationCount = 0;
|
||||
|
||||
GroupedBeatmap? previousSelection = retainSelection ? Carousel.CurrentGroupedBeatmap : null;
|
||||
|
||||
Box topBox;
|
||||
Children = new Drawable[]
|
||||
{
|
||||
@@ -120,6 +124,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
{
|
||||
Carousel = new TestBeatmapCarousel
|
||||
{
|
||||
CurrentGroupedBeatmap = previousSelection,
|
||||
NewItemsPresented = _ => NewItemsPresentedInvocationCount++,
|
||||
RequestSelection = b =>
|
||||
{
|
||||
@@ -222,6 +227,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
protected void SelectPrevPanel() => AddStep("select prev panel", () => InputManager.Key(Key.Up));
|
||||
protected void SelectNextSet() => AddStep("select next set", () => InputManager.Key(Key.Right));
|
||||
protected void SelectPrevSet() => AddStep("select prev set", () => InputManager.Key(Key.Left));
|
||||
protected void SelectRandomSet() => AddStep("select random set", () => Carousel.NextRandom());
|
||||
|
||||
protected void Select() => AddStep("select", () => InputManager.Key(Key.Enter));
|
||||
|
||||
|
||||
@@ -253,6 +253,54 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
CheckDisplayedBeatmapsCount(30);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGroupDoesExpandAfterRandomTraversal()
|
||||
{
|
||||
SelectNextSet();
|
||||
|
||||
ToggleGroupCollapse();
|
||||
AddAssert("group not expanded", () => Carousel.ExpandedGroup, () => Is.Null);
|
||||
|
||||
SelectRandomSet();
|
||||
|
||||
AddAssert("group expanded", () => Carousel.ExpandedGroup, () => Is.Not.Null);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestFilterWhileCollapsedUpdatesVisualStateCorrectly()
|
||||
{
|
||||
SelectNextSet();
|
||||
|
||||
CheckHasSelection();
|
||||
AddAssert("group expanded", () => Carousel.ExpandedGroup, () => Is.Not.Null);
|
||||
AddAssert("has expanded set", () => Carousel.ExpandedBeatmapSet != null);
|
||||
|
||||
AddAssert("has visible beatmaps", () => Carousel.GetCarouselItems()!.Count(item => item.Model is GroupedBeatmap && item.IsVisible), () => Is.EqualTo(3));
|
||||
AddAssert("has visually expanded set", () => Carousel.GetCarouselItems()!.Count(item => item.Model is GroupedBeatmapSet && item.IsExpanded && item.IsVisible), () => Is.EqualTo(1));
|
||||
|
||||
ToggleGroupCollapse();
|
||||
|
||||
CheckHasSelection();
|
||||
AddAssert("group not expanded", () => Carousel.ExpandedGroup, () => Is.Null);
|
||||
AddAssert("has expanded set", () => Carousel.ExpandedBeatmapSet != null);
|
||||
|
||||
AddAssert("has no visible beatmaps", () => Carousel.GetCarouselItems()!.Count(item => item.Model is GroupedBeatmap && item.IsVisible), () => Is.Zero);
|
||||
AddAssert("has no visually expanded set", () => Carousel.GetCarouselItems()!.Count(item => item.Model is GroupedBeatmapSet && item.IsExpanded && item.IsVisible), () => Is.Zero);
|
||||
|
||||
// filter while collapsed.
|
||||
ApplyToFilterAndWaitForFilter("filter", c => c.SearchText = Carousel.SelectedBeatmapSet!.Metadata.Title);
|
||||
|
||||
// then expand.
|
||||
ToggleGroupCollapse();
|
||||
|
||||
CheckHasSelection();
|
||||
AddAssert("group expanded", () => Carousel.ExpandedGroup, () => Is.Not.Null);
|
||||
AddAssert("has expanded set", () => Carousel.ExpandedBeatmapSet != null);
|
||||
|
||||
AddAssert("has visible beatmaps", () => Carousel.GetCarouselItems()!.Count(item => item.Model is GroupedBeatmap && item.IsVisible), () => Is.EqualTo(3));
|
||||
AddAssert("has visually expanded set", () => Carousel.GetCarouselItems()!.Count(item => item.Model is GroupedBeatmapSet && item.IsExpanded && item.IsVisible), () => Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestGroupDoesNotExpandAgainOnRefilterIfManuallyCollapsed()
|
||||
{
|
||||
|
||||
@@ -23,6 +23,33 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
SortAndGroupBy(SortMode.Title, GroupMode.Length);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInitialVisualState()
|
||||
{
|
||||
AddBeatmaps(3, splitApart: true);
|
||||
|
||||
WaitForDrawablePanels();
|
||||
SelectNextSet();
|
||||
WaitForSetSelection(set: 0, diff: 0);
|
||||
|
||||
AddAssert("selected item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True);
|
||||
AddAssert("has visually expanded set", () => Carousel.GetCarouselItems()!.Count(item => item.Model is GroupedBeatmapSet && item.IsExpanded && item.IsVisible), () => Is.EqualTo(1));
|
||||
|
||||
CreateCarousel(retainSelection: true);
|
||||
WaitForDrawablePanels();
|
||||
WaitForSetSelection(set: 0, diff: 0);
|
||||
|
||||
AddAssert("selected item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True);
|
||||
AddAssert("has visually expanded set", () => Carousel.GetCarouselItems()!.Count(item => item.Model is GroupedBeatmapSet && item.IsExpanded && item.IsVisible), () => Is.EqualTo(1));
|
||||
|
||||
CreateCarousel(retainSelection: true);
|
||||
WaitForDrawablePanels();
|
||||
WaitForSetSelection(set: 0, diff: 0);
|
||||
|
||||
AddAssert("selected item is visible", () => GetSelectedPanel()?.Item?.IsVisible, () => Is.True);
|
||||
AddAssert("has visually expanded set", () => Carousel.GetCarouselItems()!.Count(item => item.Model is GroupedBeatmapSet && item.IsExpanded && item.IsVisible), () => Is.EqualTo(1));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestSetTraversal()
|
||||
{
|
||||
|
||||
@@ -197,15 +197,15 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
AddUntilStep("favourites count is 2345", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("2,345"));
|
||||
|
||||
AddStep("click favourite button", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().TriggerClick());
|
||||
AddStep("allow request to complete", () => resetEvent.Set());
|
||||
AddStep("allow request to complete", resetEvent.Set);
|
||||
AddUntilStep("favourites count is 2346", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("2,346"));
|
||||
|
||||
AddStep("reset event", () => resetEvent.Reset());
|
||||
AddStep("reset event", resetEvent.Reset);
|
||||
AddStep("click favourite button", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().TriggerClick());
|
||||
AddStep("allow request to complete", () => resetEvent.Set());
|
||||
AddStep("allow request to complete", resetEvent.Set);
|
||||
AddUntilStep("favourites count is 2345", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("2,345"));
|
||||
|
||||
AddStep("reset event", () => resetEvent.Reset());
|
||||
AddStep("reset event", resetEvent.Reset);
|
||||
AddStep("click favourite button", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().TriggerClick());
|
||||
AddStep("change to another beatmap", () =>
|
||||
{
|
||||
@@ -217,7 +217,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
Beatmap.Value = working;
|
||||
onlineLookupResult.Value = online;
|
||||
});
|
||||
AddStep("allow request to complete", () => resetEvent.Set());
|
||||
AddStep("allow request to complete", resetEvent.Set);
|
||||
AddUntilStep("favourites count is 9999", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().Text.ToString(), () => Is.EqualTo("9,999"));
|
||||
|
||||
AddStep("set up request handler to fail", () =>
|
||||
@@ -239,11 +239,11 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
}
|
||||
};
|
||||
});
|
||||
AddStep("reset event", () => resetEvent.Reset());
|
||||
AddStep("reset event", resetEvent.Reset);
|
||||
AddStep("click favourite button", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single().TriggerClick());
|
||||
AddAssert("spinner visible", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single()
|
||||
.ChildrenOfType<LoadingSpinner>().Single().State.Value, () => Is.EqualTo(Visibility.Visible));
|
||||
AddStep("allow request to complete", () => resetEvent.Set());
|
||||
AddStep("allow request to complete", resetEvent.Set);
|
||||
AddAssert("spinner hidden", () => this.ChildrenOfType<BeatmapTitleWedge.FavouriteButton>().Single()
|
||||
.ChildrenOfType<LoadingSpinner>().Single().State.Value, () => Is.EqualTo(Visibility.Hidden));
|
||||
}
|
||||
|
||||
@@ -659,6 +659,51 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
AddAssert("options disabled", () => !this.ChildrenOfType<FooterButtonOptions>().Single().Enabled.Value);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// tests that clicking the osu! logo immediately after selecting a different difficulty
|
||||
/// (before the selection debounce completes) starts the correct beatmap.
|
||||
/// this tests the fix for https://github.com/ppy/osu/issues/36074
|
||||
/// </summary>
|
||||
[Test]
|
||||
public void TestPlayCorrectBeatmapWhenSelectionNotFullyLoaded()
|
||||
{
|
||||
// import a beatmap set with multiple difficulties
|
||||
ImportBeatmapForRuleset(0);
|
||||
|
||||
LoadSongSelect();
|
||||
|
||||
// wait for initial beatmap to be selected
|
||||
AddUntilStep("wait for first beatmap selected", () => !Beatmap.IsDefault);
|
||||
|
||||
BeatmapInfo? firstBeatmap = null;
|
||||
AddStep("store first difficulty", () => firstBeatmap = Beatmap.Value.BeatmapInfo);
|
||||
|
||||
// start loading the first difficulty
|
||||
AddStep("click logo to start loading", () => this.ChildrenOfType<OsuLogo>().Single().TriggerClick());
|
||||
AddUntilStep("wait for player loader", () => Stack.CurrentScreen is PlayerLoader);
|
||||
|
||||
// return to song select
|
||||
AddStep("press escape to return", () => InputManager.Key(Key.Escape));
|
||||
AddUntilStep("wait for return to song select", () => SongSelect.IsCurrentScreen());
|
||||
|
||||
// press down and schedule logo click to happen shortly after (but before 150ms debounce)
|
||||
// this reproduces the race condition where Beatmap.Value hasn't updated yet
|
||||
AddStep("select next difficulty and click logo immediately", () =>
|
||||
{
|
||||
InputManager.Key(Key.Down);
|
||||
Schedule(() => this.ChildrenOfType<OsuLogo>().Single().TriggerClick());
|
||||
});
|
||||
|
||||
AddUntilStep("wait for player loader", () => Stack.CurrentScreen is PlayerLoader);
|
||||
|
||||
// verify we're loading the second difficulty, not the first
|
||||
// without the fix, this would fail because Beatmap.Value still has the old value
|
||||
AddAssert("player is loading second difficulty", () =>
|
||||
Beatmap.Value.BeatmapInfo.ID != firstBeatmap!.ID);
|
||||
|
||||
AddUntilStep("wait for return to song select", () => SongSelect.IsCurrentScreen());
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
||||
|
||||
@@ -389,6 +389,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
SortBy(SortMode.Artist);
|
||||
checkMatchedBeatmaps(6);
|
||||
|
||||
AddUntilStep("wait for spread indicator", () => this.ChildrenOfType<PanelBeatmapSet.SpreadDisplay>().Any(d => d.Enabled.Value));
|
||||
AddStep("click spread indicator", () => this.ChildrenOfType<PanelBeatmapSet.SpreadDisplay>().Single(d => d.Enabled.Value).TriggerClick());
|
||||
WaitForFiltering();
|
||||
checkMatchedBeatmaps(3);
|
||||
@@ -398,6 +399,31 @@ namespace osu.Game.Tests.Visual.SongSelectV2
|
||||
checkMatchedBeatmaps(6);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDismissingScopeDoesNotClearSearchTextBox()
|
||||
{
|
||||
ImportBeatmapForRuleset(0);
|
||||
ImportBeatmapForRuleset(0);
|
||||
|
||||
LoadSongSelect();
|
||||
SortBy(SortMode.Artist);
|
||||
checkMatchedBeatmaps(6);
|
||||
|
||||
AddStep("set text filter", () => filterTextBox.Current.Value = Beatmaps.GetAllUsableBeatmapSets().First().Metadata.Title);
|
||||
WaitForFiltering();
|
||||
checkMatchedBeatmaps(3);
|
||||
|
||||
AddUntilStep("wait for spread indicator", () => this.ChildrenOfType<PanelBeatmapSet.SpreadDisplay>().Any(d => d.Enabled.Value));
|
||||
AddStep("click spread indicator", () => this.ChildrenOfType<PanelBeatmapSet.SpreadDisplay>().Single(d => d.Enabled.Value).TriggerClick());
|
||||
WaitForFiltering();
|
||||
checkMatchedBeatmaps(3);
|
||||
|
||||
AddStep("press Escape", () => InputManager.Key(Key.Escape));
|
||||
WaitForFiltering();
|
||||
checkMatchedBeatmaps(3);
|
||||
AddAssert("text filter not emptied", () => filterTextBox.Current.Value, () => Is.Not.Empty);
|
||||
}
|
||||
|
||||
private NoResultsPlaceholder? getPlaceholder() => SongSelect.ChildrenOfType<NoResultsPlaceholder>().FirstOrDefault();
|
||||
|
||||
private void checkMatchedBeatmaps(int expected) => AddUntilStep($"{expected} matching shown", () => Carousel.MatchedBeatmapsCount, () => Is.EqualTo(expected));
|
||||
|
||||
@@ -91,7 +91,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
AddStep("Move cursor to button", () => InputManager.MoveMouseTo(settingsButton));
|
||||
AddAssert("Button is hovered", () => settingsButton.IsHovered);
|
||||
AddStep("Move cursor to padded area", () => InputManager.MoveMouseTo(settingsButton.ScreenSpaceDrawQuad.TopLeft + new Vector2(SettingsPanel.CONTENT_MARGINS / 2f, 10)));
|
||||
AddStep("Move cursor to padded area", () => InputManager.MoveMouseTo(settingsButton.ScreenSpaceDrawQuad.TopLeft + new Vector2(SettingsPanel.CONTENT_PADDING.Left / 2f, 10)));
|
||||
AddAssert("Cursor within a button", () => settingsButton.ScreenSpaceDrawQuad.Contains(InputManager.CurrentState.Mouse.Position));
|
||||
AddAssert("Button is not hovered", () => !settingsButton.IsHovered);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public partial class TestSceneFormButton : ThemeComparisonTestScene
|
||||
{
|
||||
public TestSceneFormButton()
|
||||
: base(false)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Drawable CreateContent() => new OsuContextMenuContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new BackgroundBox
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new PopoverContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new OsuScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Width = 400,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(5),
|
||||
Padding = new MarginPadding(10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FormButton
|
||||
{
|
||||
Caption = "Button with default style",
|
||||
Action = () => { },
|
||||
},
|
||||
new FormButton
|
||||
{
|
||||
Caption = "Button with default style",
|
||||
Enabled = { Value = false },
|
||||
},
|
||||
new FormButton
|
||||
{
|
||||
Caption = "Button with custom style",
|
||||
BackgroundColour = new OsuColour().DangerousButtonColour,
|
||||
ButtonIcon = FontAwesome.Solid.Hamburger,
|
||||
Action = () => { },
|
||||
},
|
||||
new FormButton
|
||||
{
|
||||
Caption = "Button with custom style",
|
||||
BackgroundColour = new OsuColour().DangerousButtonColour,
|
||||
ButtonIcon = FontAwesome.Solid.Hamburger,
|
||||
Enabled = { Value = false },
|
||||
},
|
||||
new FormButton
|
||||
{
|
||||
Caption = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua",
|
||||
BackgroundColour = new OsuColour().Blue3,
|
||||
ButtonIcon = FontAwesome.Solid.Book,
|
||||
Action = () => { },
|
||||
},
|
||||
new FormButton
|
||||
{
|
||||
Caption = "Button with text inside",
|
||||
ButtonText = "Text in button",
|
||||
Action = () => { },
|
||||
},
|
||||
new FormButton
|
||||
{
|
||||
Caption = "Button with text inside",
|
||||
ButtonText = "Text in button",
|
||||
Enabled = { Value = false },
|
||||
},
|
||||
new FormButton
|
||||
{
|
||||
Caption = "Button with text inside",
|
||||
ButtonText = "Text in button",
|
||||
BackgroundColour = new OsuColour().DangerousButtonColour,
|
||||
Action = () => { },
|
||||
},
|
||||
new FormButton
|
||||
{
|
||||
Caption = "Button with text inside",
|
||||
ButtonText = "Text in button",
|
||||
BackgroundColour = new OsuColour().DangerousButtonColour,
|
||||
Enabled = { Value = false },
|
||||
},
|
||||
new FormButton
|
||||
{
|
||||
Caption = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor",
|
||||
ButtonText = "Text in button",
|
||||
BackgroundColour = new OsuColour().Blue3,
|
||||
Action = () => { },
|
||||
},
|
||||
new FormButton
|
||||
{
|
||||
Caption = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor",
|
||||
ButtonText = "Text in button",
|
||||
BackgroundColour = new OsuColour().Blue3,
|
||||
Enabled = { Value = false },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private partial class BackgroundBox : Box
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
Colour = colourProvider.Background4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,171 +42,236 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 400,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(5),
|
||||
Padding = new MarginPadding(10),
|
||||
Children = new Drawable[]
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Children = new[]
|
||||
{
|
||||
new FormTextBox
|
||||
new FillFlowContainer
|
||||
{
|
||||
Caption = "Artist",
|
||||
HintText = "Poot artist here!",
|
||||
PlaceholderText = "Here is an artist",
|
||||
TabbableContentContainer = this,
|
||||
},
|
||||
new FormTextBox
|
||||
{
|
||||
Caption = "Artist",
|
||||
HintText = "Poot artist here!",
|
||||
PlaceholderText = "Here is an artist",
|
||||
Current = { Disabled = true },
|
||||
TabbableContentContainer = this,
|
||||
},
|
||||
new FormNumberBox(allowDecimals: true)
|
||||
{
|
||||
Caption = "Number",
|
||||
HintText = "Insert your favourite number",
|
||||
PlaceholderText = "Mine is 42!",
|
||||
TabbableContentContainer = this,
|
||||
},
|
||||
new FormCheckBox
|
||||
{
|
||||
Caption = EditorSetupStrings.LetterboxDuringBreaks,
|
||||
HintText = EditorSetupStrings.LetterboxDuringBreaksDescription,
|
||||
},
|
||||
new FormCheckBox
|
||||
{
|
||||
Caption = EditorSetupStrings.LetterboxDuringBreaks,
|
||||
HintText = EditorSetupStrings.LetterboxDuringBreaksDescription,
|
||||
Current = { Disabled = true },
|
||||
},
|
||||
new FormCheckBox
|
||||
{
|
||||
Caption = EditorSetupStrings.LetterboxDuringBreaks,
|
||||
HintText = EditorSetupStrings.LetterboxDuringBreaksDescription,
|
||||
Current = { Value = true, Disabled = true },
|
||||
},
|
||||
new FormSliderBar<float>
|
||||
{
|
||||
Caption = "Slider",
|
||||
HintText = "Slider hint",
|
||||
Current = new BindableFloat
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Width = 400,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(5),
|
||||
Padding = new MarginPadding(10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
Value = 5,
|
||||
Precision = 0.1f,
|
||||
new FormTextBox
|
||||
{
|
||||
Caption = "Artist",
|
||||
HintText = "Poot artist here!",
|
||||
PlaceholderText = "Here is an artist",
|
||||
TabbableContentContainer = this,
|
||||
},
|
||||
new FormTextBox
|
||||
{
|
||||
Caption = "Artist",
|
||||
HintText = "Poot artist here!",
|
||||
PlaceholderText = "Here is an artist",
|
||||
Current = { Disabled = true },
|
||||
TabbableContentContainer = this,
|
||||
},
|
||||
new FormNumberBox(allowDecimals: true)
|
||||
{
|
||||
Caption = "Number",
|
||||
HintText = "Insert your favourite number",
|
||||
PlaceholderText = "Mine is 42!",
|
||||
TabbableContentContainer = this,
|
||||
},
|
||||
new FormCheckBox
|
||||
{
|
||||
Caption = EditorSetupStrings.LetterboxDuringBreaks,
|
||||
HintText = EditorSetupStrings.LetterboxDuringBreaksDescription,
|
||||
},
|
||||
new FormCheckBox
|
||||
{
|
||||
Caption = EditorSetupStrings.LetterboxDuringBreaks,
|
||||
HintText = EditorSetupStrings.LetterboxDuringBreaksDescription,
|
||||
Current = { Disabled = true },
|
||||
},
|
||||
new FormCheckBox
|
||||
{
|
||||
Caption = EditorSetupStrings.LetterboxDuringBreaks,
|
||||
HintText = EditorSetupStrings.LetterboxDuringBreaksDescription,
|
||||
Current = { Value = true, Disabled = true },
|
||||
},
|
||||
new FormSliderBar<float>
|
||||
{
|
||||
Caption = "Slider",
|
||||
HintText = "Slider hint",
|
||||
Current = new BindableFloat
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
Value = 5,
|
||||
Precision = 0.1f,
|
||||
},
|
||||
TabbableContentContainer = this,
|
||||
},
|
||||
new FormSliderBar<float>
|
||||
{
|
||||
Caption = "Slider",
|
||||
HintText = "Slider hint",
|
||||
Current = new BindableFloat
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
Value = 5,
|
||||
Precision = 0.1f,
|
||||
Disabled = true,
|
||||
},
|
||||
TransferValueOnCommit = true,
|
||||
TabbableContentContainer = this,
|
||||
},
|
||||
new FormSliderBar<float>
|
||||
{
|
||||
Caption = "Slider (percentage)",
|
||||
HintText = "Percentage slider hint",
|
||||
Current = new BindableFloat
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 1,
|
||||
Value = 0.2f,
|
||||
Precision = 0.0001f,
|
||||
},
|
||||
DisplayAsPercentage = true,
|
||||
TabbableContentContainer = this,
|
||||
},
|
||||
new FormSliderBar<float>
|
||||
{
|
||||
Caption = "Slider (custom)",
|
||||
HintText = "Custom slider hint",
|
||||
Current = new BindableFloat
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 1,
|
||||
Value = 0.2f,
|
||||
Precision = 0.0001f,
|
||||
},
|
||||
LabelFormat = v => $"{v * 100:0.00} funometer",
|
||||
TooltipFormat = v => $"This setting has the value set to {v * 100:0.00} funometer.",
|
||||
TabbableContentContainer = this,
|
||||
},
|
||||
new FormSliderBar<float>
|
||||
{
|
||||
Caption = "Slider (custom)",
|
||||
HintText = "Custom slider hint",
|
||||
Current = new BindableFloat
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 1,
|
||||
Value = 0.2f,
|
||||
Precision = 0.0001f,
|
||||
Disabled = true,
|
||||
},
|
||||
TransferValueOnCommit = true,
|
||||
LabelFormat = v => $"{v * 100:0.00} funometer",
|
||||
TooltipFormat = v => $"This setting has the value set to {v * 100:0.00} funometer.",
|
||||
TabbableContentContainer = this,
|
||||
},
|
||||
new FormEnumDropdown<CountdownType>
|
||||
{
|
||||
Caption = EditorSetupStrings.EnableCountdown,
|
||||
HintText = EditorSetupStrings.CountdownDescription,
|
||||
},
|
||||
new FormEnumDropdown<CountdownType>
|
||||
{
|
||||
Caption = EditorSetupStrings.EnableCountdown,
|
||||
HintText = EditorSetupStrings.CountdownDescription,
|
||||
Current = { Disabled = true },
|
||||
},
|
||||
new FormFileSelector
|
||||
{
|
||||
Caption = "File selector",
|
||||
PlaceholderText = "Select a file",
|
||||
},
|
||||
new FormBeatmapFileSelector(true)
|
||||
{
|
||||
Caption = "File selector with intermediate choice dialog",
|
||||
PlaceholderText = "Select a file",
|
||||
},
|
||||
new FormColourPalette
|
||||
{
|
||||
Caption = "Combo colours",
|
||||
Colours =
|
||||
{
|
||||
Colour4.Red,
|
||||
Colour4.Green,
|
||||
Colour4.Blue,
|
||||
Colour4.Yellow,
|
||||
}
|
||||
},
|
||||
new FormButton
|
||||
{
|
||||
Caption = "No text in button",
|
||||
Action = () => { },
|
||||
},
|
||||
},
|
||||
TabbableContentContainer = this,
|
||||
},
|
||||
new FormSliderBar<float>
|
||||
new FillFlowContainer
|
||||
{
|
||||
Caption = "Slider",
|
||||
HintText = "Slider hint",
|
||||
Current = new BindableFloat
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Width = 400,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(5),
|
||||
Padding = new MarginPadding(10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
Value = 5,
|
||||
Precision = 0.1f,
|
||||
Disabled = true,
|
||||
new FormNumberBox(allowDecimals: true)
|
||||
{
|
||||
Caption = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua",
|
||||
HintText = "Insert your favourite number",
|
||||
PlaceholderText = "Mine is 42!",
|
||||
TabbableContentContainer = this,
|
||||
},
|
||||
new FormCheckBox
|
||||
{
|
||||
Caption = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua",
|
||||
HintText = EditorSetupStrings.LetterboxDuringBreaksDescription,
|
||||
},
|
||||
new FormSliderBar<float>
|
||||
{
|
||||
Caption = "Lorem ipsum dolor sit amet, conse adipiscing elit, sed do eiusmod",
|
||||
HintText = "Slider hint",
|
||||
Current = new BindableFloat
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 10,
|
||||
Value = 5,
|
||||
Precision = 0.1f,
|
||||
},
|
||||
TabbableContentContainer = this,
|
||||
},
|
||||
new FormEnumDropdown<CountdownType>
|
||||
{
|
||||
Caption = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua",
|
||||
HintText = EditorSetupStrings.CountdownDescription,
|
||||
},
|
||||
new FormFileSelector
|
||||
{
|
||||
Caption = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua",
|
||||
HintText = EditorSetupStrings.CountdownDescription,
|
||||
PlaceholderText = "Select a file",
|
||||
},
|
||||
new FormColourPalette
|
||||
{
|
||||
Caption = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua",
|
||||
HintText = EditorSetupStrings.CountdownDescription,
|
||||
Colours =
|
||||
{
|
||||
Colour4.Red,
|
||||
Colour4.Green,
|
||||
Colour4.Blue,
|
||||
Colour4.Yellow,
|
||||
}
|
||||
},
|
||||
},
|
||||
TransferValueOnCommit = true,
|
||||
TabbableContentContainer = this,
|
||||
},
|
||||
new FormSliderBar<float>
|
||||
{
|
||||
Caption = "Slider (percentage)",
|
||||
HintText = "Percentage slider hint",
|
||||
Current = new BindableFloat
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 1,
|
||||
Value = 0.2f,
|
||||
Precision = 0.0001f,
|
||||
},
|
||||
DisplayAsPercentage = true,
|
||||
TabbableContentContainer = this,
|
||||
},
|
||||
new FormSliderBar<float>
|
||||
{
|
||||
Caption = "Slider (custom)",
|
||||
HintText = "Custom slider hint",
|
||||
Current = new BindableFloat
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 1,
|
||||
Value = 0.2f,
|
||||
Precision = 0.0001f,
|
||||
},
|
||||
LabelFormat = v => $"{v * 100:0.00} funometer",
|
||||
TooltipFormat = v => $"This setting has the value set to {v * 100:0.00} funometer.",
|
||||
TabbableContentContainer = this,
|
||||
},
|
||||
new FormSliderBar<float>
|
||||
{
|
||||
Caption = "Slider (custom)",
|
||||
HintText = "Custom slider hint",
|
||||
Current = new BindableFloat
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 1,
|
||||
Value = 0.2f,
|
||||
Precision = 0.0001f,
|
||||
Disabled = true,
|
||||
},
|
||||
TransferValueOnCommit = true,
|
||||
LabelFormat = v => $"{v * 100:0.00} funometer",
|
||||
TooltipFormat = v => $"This setting has the value set to {v * 100:0.00} funometer.",
|
||||
TabbableContentContainer = this,
|
||||
},
|
||||
new FormEnumDropdown<CountdownType>
|
||||
{
|
||||
Caption = EditorSetupStrings.EnableCountdown,
|
||||
HintText = EditorSetupStrings.CountdownDescription,
|
||||
},
|
||||
new FormEnumDropdown<CountdownType>
|
||||
{
|
||||
Caption = EditorSetupStrings.EnableCountdown,
|
||||
HintText = EditorSetupStrings.CountdownDescription,
|
||||
Current = { Disabled = true },
|
||||
},
|
||||
new FormFileSelector
|
||||
{
|
||||
Caption = "File selector",
|
||||
PlaceholderText = "Select a file",
|
||||
},
|
||||
new FormBeatmapFileSelector(true)
|
||||
{
|
||||
Caption = "File selector with intermediate choice dialog",
|
||||
PlaceholderText = "Select a file",
|
||||
},
|
||||
new FormColourPalette
|
||||
{
|
||||
Caption = "Combo colours",
|
||||
Colours =
|
||||
{
|
||||
Colour4.Red,
|
||||
Colour4.Green,
|
||||
Colour4.Blue,
|
||||
Colour4.Yellow,
|
||||
}
|
||||
},
|
||||
new FormButton
|
||||
{
|
||||
Caption = "No text in button",
|
||||
Action = () => { },
|
||||
},
|
||||
new FormButton
|
||||
{
|
||||
Caption = "Text in button which is pretty long and is very likely to wrap",
|
||||
ButtonText = "Foo the bar",
|
||||
Action = () => { },
|
||||
},
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
@@ -0,0 +1,104 @@
|
||||
// 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 osu.Framework.Allocation;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Beatmaps;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osu.Game.Graphics.Cursor;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public partial class TestSceneFormDropdown : ThemeComparisonTestScene
|
||||
{
|
||||
public TestSceneFormDropdown()
|
||||
: base(false)
|
||||
{
|
||||
}
|
||||
|
||||
protected override Drawable CreateContent() => new OsuContextMenuContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new BackgroundBox
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
new PopoverContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new OsuScrollContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.TopCentre,
|
||||
Origin = Anchor.TopCentre,
|
||||
Width = 400,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(5),
|
||||
Padding = new MarginPadding(10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FormEnumDropdown<CountdownType>
|
||||
{
|
||||
Caption = EditorSetupStrings.EnableCountdown,
|
||||
HintText = EditorSetupStrings.CountdownDescription,
|
||||
},
|
||||
new FormEnumDropdown<CountdownType>
|
||||
{
|
||||
Caption = EditorSetupStrings.EnableCountdown,
|
||||
HintText = EditorSetupStrings.CountdownDescription,
|
||||
Current = { Disabled = true },
|
||||
},
|
||||
new FormDropdown<string>
|
||||
{
|
||||
Caption = "Custom dropdown",
|
||||
HintText = "Custom dropdown hint",
|
||||
Items = new[]
|
||||
{
|
||||
"A verrry looooongggg thiiiinngggggg toooooo fittttt iiinnnn thhiisssss droooppdddoowwwnn",
|
||||
"B verrry looooongggg thiiiinngggggg toooooo fittttt iiinnnn thhiisssss droooppdddoowwwnn",
|
||||
"C verrry looooongggg thiiiinngggggg toooooo fittttt iiinnnn thhiisssss droooppdddoowwwnn",
|
||||
"D verrry looooongggg thiiiinngggggg toooooo fittttt iiinnnn thhiisssss droooppdddoowwwnn",
|
||||
},
|
||||
},
|
||||
new FormDropdown<string>
|
||||
{
|
||||
Caption = "Custom dropdown",
|
||||
HintText = "Custom dropdown hint",
|
||||
AlwaysShowSearchBar = true,
|
||||
Items = new[]
|
||||
{
|
||||
"A verrry looooongggg thiiiinngggggg toooooo fittttt iiinnnn thhiisssss droooppdddoowwwnn",
|
||||
"B verrry looooongggg thiiiinngggggg toooooo fittttt iiinnnn thhiisssss droooppdddoowwwnn",
|
||||
"C verrry looooongggg thiiiinngggggg toooooo fittttt iiinnnn thhiisssss droooppdddoowwwnn",
|
||||
"D verrry looooongggg thiiiinngggggg toooooo fittttt iiinnnn thhiisssss droooppdddoowwwnn",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
private partial class BackgroundBox : Box
|
||||
{
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
Colour = colourProvider.Background4;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,9 +64,11 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNubDoubleClickRevertToDefault()
|
||||
[TestCase(false)]
|
||||
[TestCase(true)]
|
||||
public void TestNubDoubleClickRevertToDefault(bool transferValueOnCommit)
|
||||
{
|
||||
OsuSpriteText text;
|
||||
FormSliderBar<float> slider = null!;
|
||||
|
||||
AddStep("create content", () =>
|
||||
@@ -81,9 +83,11 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
Spacing = new Vector2(10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
text = new OsuSpriteText(),
|
||||
slider = new FormSliderBar<float>
|
||||
{
|
||||
Caption = "Slider",
|
||||
TransferValueOnCommit = transferValueOnCommit,
|
||||
Current = new BindableFloat
|
||||
{
|
||||
MinValue = 0,
|
||||
@@ -94,6 +98,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
},
|
||||
}
|
||||
};
|
||||
slider.Current.BindValueChanged(_ => text.Text = $"Current value is: {slider.Current.Value}", true);
|
||||
});
|
||||
AddStep("set slider to 1", () => slider.Current.Value = 1);
|
||||
|
||||
@@ -164,6 +169,17 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
InputManager.Key(Key.Enter);
|
||||
});
|
||||
AddAssert("slider is still at 1", () => slider.Current.Value, () => Is.EqualTo(1));
|
||||
|
||||
AddStep("re-enable slider", () => slider.Current.Disabled = false);
|
||||
|
||||
AddStep("move mouse to nub", () => InputManager.MoveMouseTo(slider.ChildrenOfType<Circle>().Single()));
|
||||
|
||||
AddStep("double click nub", () =>
|
||||
{
|
||||
InputManager.Click(MouseButton.Left);
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("slider is at 5", () => slider.Current.Value, () => Is.EqualTo(5));
|
||||
}
|
||||
|
||||
[Test]
|
||||
@@ -207,5 +223,133 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
});
|
||||
AddAssert("no text selected", () => slider.ChildrenOfType<FormTextBox.InnerTextBox>().Single().SelectedText, () => Is.Empty);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDisplayAsPercentageFloat()
|
||||
{
|
||||
OsuSpriteText text;
|
||||
FormSliderBar<float> slider = null!;
|
||||
|
||||
AddStep("create content", () =>
|
||||
{
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 0.5f,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
text = new OsuSpriteText(),
|
||||
slider = new FormSliderBar<float>
|
||||
{
|
||||
Caption = "Slider",
|
||||
Current = new BindableFloat
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 1,
|
||||
Precision = 0.01f,
|
||||
Default = 0.5f,
|
||||
Value = 0.5f,
|
||||
},
|
||||
DisplayAsPercentage = true,
|
||||
},
|
||||
}
|
||||
};
|
||||
slider.Current.BindValueChanged(_ => text.Text = $"Current value is: {slider.Current.Value}", true);
|
||||
});
|
||||
|
||||
AddStep("click on textbox part", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(slider.ChildrenOfType<FormTextBox.InnerTextBox>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("text selected", () => slider.ChildrenOfType<FormTextBox.InnerTextBox>().Single().SelectedText, () => Is.EqualTo("50"));
|
||||
AddStep("input 9%", () =>
|
||||
{
|
||||
slider.ChildrenOfType<FormTextBox.InnerTextBox>().Single().Text = "9";
|
||||
InputManager.Key(Key.Enter);
|
||||
});
|
||||
AddAssert("slider is at 0.09", () => slider.Current.Value, () => Is.EqualTo(0.09f));
|
||||
|
||||
AddStep("start dragging nub", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(slider.ChildrenOfType<FormSliderBar<float>.InnerSliderNub>().Single());
|
||||
InputManager.PressButton(MouseButton.Left);
|
||||
});
|
||||
AddStep("drag nub to 50%", () =>
|
||||
{
|
||||
var innerSlider = slider.ChildrenOfType<FormSliderBar<float>.InnerSlider>().Single();
|
||||
InputManager.MoveMouseTo((innerSlider.ScreenSpaceDrawQuad.TopLeft + innerSlider.ScreenSpaceDrawQuad.TopRight) / 2);
|
||||
InputManager.ReleaseButton(MouseButton.Left);
|
||||
});
|
||||
AddAssert("slider is at ~0.5", () => slider.Current.Value, () => Is.EqualTo(0.5).Within(0.01f));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestDisplayAsPercentageInt()
|
||||
{
|
||||
OsuSpriteText text;
|
||||
FormSliderBar<int> slider = null!;
|
||||
|
||||
AddStep("create content", () =>
|
||||
{
|
||||
Child = new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 0.5f,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(10),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
text = new OsuSpriteText(),
|
||||
slider = new FormSliderBar<int>
|
||||
{
|
||||
Caption = "Slider",
|
||||
Current = new BindableInt
|
||||
{
|
||||
MinValue = 0,
|
||||
MaxValue = 100,
|
||||
Precision = 1,
|
||||
Default = 50,
|
||||
Value = 50,
|
||||
},
|
||||
DisplayAsPercentage = true,
|
||||
},
|
||||
}
|
||||
};
|
||||
slider.Current.BindValueChanged(_ => text.Text = $"Current value is: {slider.Current.Value}", true);
|
||||
});
|
||||
|
||||
AddStep("click on textbox part", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(slider.ChildrenOfType<FormTextBox.InnerTextBox>().Single());
|
||||
InputManager.Click(MouseButton.Left);
|
||||
});
|
||||
AddAssert("text selected", () => slider.ChildrenOfType<FormTextBox.InnerTextBox>().Single().SelectedText, () => Is.EqualTo("50"));
|
||||
AddStep("input 9%", () =>
|
||||
{
|
||||
slider.ChildrenOfType<FormTextBox.InnerTextBox>().Single().Text = "9";
|
||||
InputManager.Key(Key.Enter);
|
||||
});
|
||||
AddAssert("slider is at 9", () => slider.Current.Value, () => Is.EqualTo(9));
|
||||
|
||||
AddStep("start dragging nub", () =>
|
||||
{
|
||||
InputManager.MoveMouseTo(slider.ChildrenOfType<FormSliderBar<int>.InnerSliderNub>().Single());
|
||||
InputManager.PressButton(MouseButton.Left);
|
||||
});
|
||||
AddStep("drag nub to 50%", () =>
|
||||
{
|
||||
var innerSlider = slider.ChildrenOfType<FormSliderBar<int>.InnerSlider>().Single();
|
||||
InputManager.MoveMouseTo((innerSlider.ScreenSpaceDrawQuad.TopLeft + innerSlider.ScreenSpaceDrawQuad.TopRight) / 2);
|
||||
InputManager.ReleaseButton(MouseButton.Left);
|
||||
});
|
||||
AddAssert("slider is at ~50", () => slider.Current.Value, () => Is.EqualTo(50).Within(1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,20 +40,20 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
firedText
|
||||
};
|
||||
|
||||
AddStep("start confirming", () => overlay.Begin());
|
||||
AddStep("abort confirming", () => overlay.Abort());
|
||||
AddStep("start confirming", overlay.Begin);
|
||||
AddStep("abort confirming", overlay.Abort);
|
||||
|
||||
AddAssert("ensure not fired internally", () => !overlay.Fired);
|
||||
AddAssert("ensure aborted", () => !fired);
|
||||
|
||||
AddStep("start confirming", () => overlay.Begin());
|
||||
AddStep("start confirming", overlay.Begin);
|
||||
|
||||
AddUntilStep("wait until confirmed", () => fired);
|
||||
AddAssert("ensure fired internally", () => overlay.Fired);
|
||||
|
||||
AddStep("abort after fire", () => overlay.Abort());
|
||||
AddStep("abort after fire", overlay.Abort);
|
||||
AddAssert("ensure not fired internally", () => !overlay.Fired);
|
||||
AddStep("start confirming", () => overlay.Begin());
|
||||
AddStep("start confirming", overlay.Begin);
|
||||
AddUntilStep("wait until fired again", () => overlay.Fired);
|
||||
}
|
||||
|
||||
|
||||
@@ -80,6 +80,33 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNumericHotkeys()
|
||||
{
|
||||
AddStep("set osu! ruleset", () => Ruleset.Value = rulesets.GetRuleset(0));
|
||||
AddStep("create content", () => Child = new ModPresetColumn
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
});
|
||||
|
||||
AddStep("select first preset", () => InputManager.Key(Key.Number1));
|
||||
AddAssert("first panel selected", () => this.ChildrenOfType<ModPresetPanel>().ElementAt(0).Active.Value);
|
||||
|
||||
AddAssert("selected mods match correct preset", () => SelectedMods.Value, () => Is.EquivalentTo(createTestPresets().ElementAt(1).Mods));
|
||||
|
||||
AddStep("select third preset", () => InputManager.Key(Key.Number3));
|
||||
AddAssert("first panel not selected", () => !this.ChildrenOfType<ModPresetPanel>().ElementAt(0).Active.Value);
|
||||
AddAssert("third panel selected", () => this.ChildrenOfType<ModPresetPanel>().ElementAt(2).Active.Value);
|
||||
|
||||
AddAssert("selected mods match correct preset", () => SelectedMods.Value, () => Is.EquivalentTo(createTestPresets().ElementAt(2).Mods));
|
||||
|
||||
AddStep("deselect third preset", () => InputManager.Key(Key.Number3));
|
||||
AddAssert("third panel not selected", () => !this.ChildrenOfType<ModPresetPanel>().ElementAt(2).Active.Value);
|
||||
|
||||
AddAssert("no selected mods", () => SelectedMods.Value.Count == 0);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestBasicOperation()
|
||||
{
|
||||
|
||||
@@ -96,7 +96,7 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
private partial class EmptyToast : Toast
|
||||
{
|
||||
public EmptyToast()
|
||||
: base("", "", "")
|
||||
: base("", "")
|
||||
{
|
||||
}
|
||||
}
|
||||
@@ -104,8 +104,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
private partial class LengthyToast : Toast
|
||||
{
|
||||
public LengthyToast()
|
||||
: base("Toast with a very very very long text", "A very very very very very very long text also", "A very very very very very long shortcut")
|
||||
: base("Toast with a very very very long text", "A very very very very very very long text also")
|
||||
{
|
||||
ExtraText = "A very very very very very long shortcut";
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,131 +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.Linq;
|
||||
using NUnit.Framework;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Input;
|
||||
using osu.Framework.Testing;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Graphics.UserInterfaceV2;
|
||||
using osuTK.Input;
|
||||
|
||||
namespace osu.Game.Tests.Visual.UserInterface
|
||||
{
|
||||
public partial class TestSceneSliderWithTextBoxInput : OsuManualInputManagerTestScene
|
||||
{
|
||||
private SliderWithTextBoxInput<float> sliderWithTextBoxInput = null!;
|
||||
|
||||
private OsuSliderBar<float> slider => sliderWithTextBoxInput.ChildrenOfType<OsuSliderBar<float>>().Single();
|
||||
private Nub nub => sliderWithTextBoxInput.ChildrenOfType<Nub>().Single();
|
||||
private OsuTextBox textBox => sliderWithTextBoxInput.ChildrenOfType<OsuTextBox>().Single();
|
||||
|
||||
[SetUpSteps]
|
||||
public void SetUpSteps()
|
||||
{
|
||||
AddStep("create slider", () => Child = sliderWithTextBoxInput = new SliderWithTextBoxInput<float>("Test Slider")
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
Width = 0.5f,
|
||||
Current = new BindableFloat
|
||||
{
|
||||
MinValue = -5,
|
||||
MaxValue = 5,
|
||||
Precision = 0.2f
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestNonInstantaneousMode()
|
||||
{
|
||||
AddStep("set instantaneous to false", () => sliderWithTextBoxInput.Instantaneous = false);
|
||||
|
||||
AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox));
|
||||
AddStep("change text", () => textBox.Text = "3");
|
||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.Zero);
|
||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.Zero);
|
||||
|
||||
AddStep("commit text", () => InputManager.Key(Key.Enter));
|
||||
AddAssert("slider moved", () => slider.Current.Value, () => Is.EqualTo(3));
|
||||
AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(3));
|
||||
|
||||
AddStep("move mouse to nub", () => InputManager.MoveMouseTo(nub));
|
||||
AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left));
|
||||
AddStep("move mouse to minimum", () => InputManager.MoveMouseTo(sliderWithTextBoxInput.ScreenSpaceDrawQuad.BottomLeft));
|
||||
AddAssert("textbox not changed", () => textBox.Current.Value, () => Is.EqualTo("3"));
|
||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(3));
|
||||
|
||||
AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
AddAssert("textbox changed", () => textBox.Current.Value, () => Is.EqualTo("-5"));
|
||||
AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||
|
||||
AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox));
|
||||
AddStep("set text to invalid", () => textBox.Text = "garbage");
|
||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||
|
||||
AddStep("commit text", () => InputManager.Key(Key.Enter));
|
||||
AddAssert("text restored", () => textBox.Text, () => Is.EqualTo("-5"));
|
||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||
|
||||
AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox));
|
||||
AddStep("set text to invalid", () => textBox.Text = "garbage");
|
||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||
|
||||
AddStep("lose focus", () => ((IFocusManager)InputManager).ChangeFocus(null));
|
||||
AddAssert("text restored", () => textBox.Text, () => Is.EqualTo("-5"));
|
||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void TestInstantaneousMode()
|
||||
{
|
||||
AddStep("set instantaneous to true", () => sliderWithTextBoxInput.Instantaneous = true);
|
||||
|
||||
AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox));
|
||||
AddStep("change text", () => textBox.Text = "3");
|
||||
AddAssert("slider moved", () => slider.Current.Value, () => Is.EqualTo(3));
|
||||
AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(3));
|
||||
|
||||
AddStep("commit text", () => InputManager.Key(Key.Enter));
|
||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(3));
|
||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(3));
|
||||
|
||||
AddStep("move mouse to nub", () => InputManager.MoveMouseTo(nub));
|
||||
AddStep("hold left mouse", () => InputManager.PressButton(MouseButton.Left));
|
||||
AddStep("move mouse to minimum", () => InputManager.MoveMouseTo(sliderWithTextBoxInput.ScreenSpaceDrawQuad.BottomLeft));
|
||||
AddAssert("textbox changed", () => textBox.Current.Value, () => Is.EqualTo("-5"));
|
||||
AddAssert("current changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||
|
||||
AddStep("release left mouse", () => InputManager.ReleaseButton(MouseButton.Left));
|
||||
AddAssert("textbox not changed", () => textBox.Current.Value, () => Is.EqualTo("-5"));
|
||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||
|
||||
AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox));
|
||||
AddStep("set text to invalid", () => textBox.Text = "garbage");
|
||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||
|
||||
AddStep("commit text", () => InputManager.Key(Key.Enter));
|
||||
AddAssert("text restored", () => textBox.Text, () => Is.EqualTo("-5"));
|
||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||
|
||||
AddStep("focus textbox", () => ((IFocusManager)InputManager).ChangeFocus(textBox));
|
||||
AddStep("set text to invalid", () => textBox.Text = "garbage");
|
||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||
|
||||
AddStep("lose focus", () => ((IFocusManager)InputManager).ChangeFocus(null));
|
||||
AddAssert("text restored", () => textBox.Text, () => Is.EqualTo("-5"));
|
||||
AddAssert("slider not moved", () => slider.Current.Value, () => Is.EqualTo(-5));
|
||||
AddAssert("current not changed", () => sliderWithTextBoxInput.Current.Value, () => Is.EqualTo(-5));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -133,9 +133,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
var loadedBackgrounds = backgrounds.Where(b => b.ContentLoaded);
|
||||
|
||||
AddUntilStep("some loaded", () => loadedBackgrounds.Any());
|
||||
AddUntilStep("some loaded", loadedBackgrounds.Any);
|
||||
AddStep("scroll to bottom", () => scrollContainer.ScrollToEnd());
|
||||
AddUntilStep("all unloaded", () => !loadedBackgrounds.Any());
|
||||
AddUntilStep("all unloaded", loadedBackgrounds.Any, () => Is.False);
|
||||
}
|
||||
|
||||
private partial class TestUpdateableBeatmapBackgroundSprite : UpdateableBeatmapBackgroundSprite
|
||||
|
||||
@@ -89,9 +89,9 @@ namespace osu.Game.Tests.Visual.UserInterface
|
||||
|
||||
var loadedCovers = covers.Where(c => c.ChildrenOfType<OnlineBeatmapSetCover>().SingleOrDefault()?.IsLoaded ?? false);
|
||||
|
||||
AddUntilStep("some loaded", () => loadedCovers.Any());
|
||||
AddUntilStep("some loaded", loadedCovers.Any);
|
||||
AddStep("scroll to end", () => scroll.ScrollToEnd());
|
||||
AddUntilStep("all unloaded", () => !loadedCovers.Any());
|
||||
AddUntilStep("all unloaded", loadedCovers.Any, () => Is.False);
|
||||
}
|
||||
|
||||
[Test]
|
||||
|
||||
@@ -98,7 +98,7 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
Width = 0.2f,
|
||||
Margin = new MarginPadding(10),
|
||||
Text = "Add beatmap",
|
||||
Action = () => beatmapEditor.CreateNew()
|
||||
Action = beatmapEditor.CreateNew
|
||||
},
|
||||
beatmapEditor
|
||||
}
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
Width = 0.2f,
|
||||
Margin = new MarginPadding(10),
|
||||
Text = "Add beatmap",
|
||||
Action = () => beatmapEditor.CreateNew()
|
||||
Action = beatmapEditor.CreateNew
|
||||
},
|
||||
beatmapEditor
|
||||
}
|
||||
|
||||
@@ -150,7 +150,7 @@ namespace osu.Game.Tournament.Screens.Editors
|
||||
new SettingsButton
|
||||
{
|
||||
Text = "Add player",
|
||||
Action = () => playerEditor.CreateNew()
|
||||
Action = playerEditor.CreateNew
|
||||
},
|
||||
new Container
|
||||
{
|
||||
|
||||
@@ -95,7 +95,7 @@ namespace osu.Game.Beatmaps
|
||||
protected virtual WorkingBeatmapCache CreateWorkingBeatmapCache(AudioManager audioManager, IResourceStore<byte[]> resources, IResourceStore<byte[]> storage, WorkingBeatmap? defaultBeatmap,
|
||||
GameHost? host)
|
||||
{
|
||||
return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host);
|
||||
return new WorkingBeatmapCache(BeatmapTrackStore, audioManager, resources, storage, defaultBeatmap, host, Realm);
|
||||
}
|
||||
|
||||
protected virtual BeatmapImporter CreateBeatmapImporter(Storage storage, RealmAccess realm) => new BeatmapImporter(storage, realm);
|
||||
@@ -334,7 +334,11 @@ namespace osu.Game.Beatmaps
|
||||
/// <returns>A matching local beatmap info if existing and in a valid state.</returns>
|
||||
public BeatmapInfo? QueryOnlineBeatmapId(int id) => Realm.Run(r =>
|
||||
r.All<BeatmapInfo>()
|
||||
.ForOnlineId(id).SingleOrDefault()?.Detach());
|
||||
.ForOnlineId(id)
|
||||
// See https://github.com/ppy/osu/issues/36234 for why this isn't a SingleOrDefault().
|
||||
.FirstOrDefault()
|
||||
?.Detach()
|
||||
);
|
||||
|
||||
/// <summary>
|
||||
/// A default representation of a WorkingBeatmap to use when no beatmap is available.
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
// 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 System.Threading.Tasks;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
@@ -58,8 +57,6 @@ namespace osu.Game.Beatmaps
|
||||
var working = workingBeatmapCache.GetWorkingBeatmap(beatmap);
|
||||
var ruleset = working.BeatmapInfo.Ruleset.CreateInstance();
|
||||
|
||||
Debug.Assert(ruleset != null);
|
||||
|
||||
var calculator = ruleset.CreateDifficultyCalculator(working);
|
||||
|
||||
beatmap.StarRating = calculator.Calculate().StarRating;
|
||||
|
||||
@@ -467,7 +467,6 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
@"2055329 miraie & blackwinterwells - facade.osz",
|
||||
@"2069877 Sephid - Thunderstrike 1988.osz",
|
||||
@"2119716 Aethoro - Snowy.osz",
|
||||
@"2120379 Synthion - VIVIDVELOCITY.osz",
|
||||
@"2124805 Frums (unknown ""lambda"") - 19ZZ.osz",
|
||||
@"2127811 Wiklund - Joy of Living (Cut Ver.).osz",
|
||||
};
|
||||
|
||||
@@ -2,10 +2,10 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using System;
|
||||
using osu.Framework.Extensions.LocalisationExtensions;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Utils;
|
||||
|
||||
namespace osu.Game.Beatmaps.Drawables.Cards.Statistics
|
||||
{
|
||||
@@ -18,7 +18,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Statistics
|
||||
this.dateTime = dateTime;
|
||||
|
||||
Icon = FontAwesome.Regular.CheckCircle;
|
||||
Text = dateTime.ToLocalisableString(@"d MMM yyyy");
|
||||
Text = dateTime.ToLocalisedMediumDate();
|
||||
}
|
||||
|
||||
public override object TooltipContent => dateTime;
|
||||
|
||||
@@ -4,7 +4,6 @@
|
||||
using System;
|
||||
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.Shapes;
|
||||
@@ -12,7 +11,6 @@ using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Game.Graphics;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Utils;
|
||||
using osuTK;
|
||||
using osuTK.Graphics;
|
||||
@@ -43,6 +41,12 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
/// </summary>
|
||||
public Color4 DisplayedDifficultyColour => background.Colour;
|
||||
|
||||
/// <summary>
|
||||
/// The difficulty text colour currently displayed.
|
||||
/// Can be used to have other components match the spectrum animation.
|
||||
/// </summary>
|
||||
public Color4 DisplayedDifficultyTextColour => starsText.Colour;
|
||||
|
||||
private readonly Bindable<double> displayedStars = new BindableDouble();
|
||||
|
||||
/// <summary>
|
||||
@@ -54,9 +58,6 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
[Resolved]
|
||||
private OsuColour colours { get; set; } = null!;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider? colourProvider { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Creates a new <see cref="StarRatingDisplay"/> using an already computed <see cref="StarDifficulty"/>.
|
||||
/// </summary>
|
||||
@@ -160,8 +161,8 @@ namespace osu.Game.Beatmaps.Drawables
|
||||
|
||||
background.Colour = colours.ForStarDifficulty(s.NewValue);
|
||||
|
||||
starIcon.Colour = s.NewValue >= OsuColour.STAR_DIFFICULTY_DEFINED_COLOUR_CUTOFF ? colours.Orange1 : colourProvider?.Background5 ?? Color4Extensions.FromHex("303d47");
|
||||
starsText.Colour = s.NewValue >= OsuColour.STAR_DIFFICULTY_DEFINED_COLOUR_CUTOFF ? colours.Orange1 : colourProvider?.Background5 ?? Color4.Black.Opacity(0.75f);
|
||||
starIcon.Colour = colours.ForStarDifficultyText(s.NewValue);
|
||||
starsText.Colour = colours.ForStarDifficultyText(s.NewValue);
|
||||
}, true);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,21 +170,21 @@ namespace osu.Game.Beatmaps.Formats
|
||||
{
|
||||
SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.StartTime + CONTROL_POINT_LENIENCY + 1)
|
||||
?? SampleControlPoint.DEFAULT;
|
||||
hitObject.Samples = hitObject.Samples.Select(o => sampleControlPoint.ApplyTo(o)).ToList();
|
||||
hitObject.Samples = hitObject.Samples.Select(sampleControlPoint.ApplyTo).ToList();
|
||||
|
||||
for (int i = 0; i < hasRepeats.NodeSamples.Count; i++)
|
||||
{
|
||||
double time = hitObject.StartTime + i * hasRepeats.Duration / hasRepeats.SpanCount() + CONTROL_POINT_LENIENCY;
|
||||
var nodeSamplePoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(time) ?? SampleControlPoint.DEFAULT;
|
||||
|
||||
hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(o => nodeSamplePoint.ApplyTo(o)).ToList();
|
||||
hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(nodeSamplePoint.ApplyTo).ToList();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.GetEndTime() + CONTROL_POINT_LENIENCY)
|
||||
?? SampleControlPoint.DEFAULT;
|
||||
hitObject.Samples = hitObject.Samples.Select(o => sampleControlPoint.ApplyTo(o)).ToList();
|
||||
hitObject.Samples = hitObject.Samples.Select(sampleControlPoint.ApplyTo).ToList();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -47,12 +47,13 @@ namespace osu.Game.Beatmaps
|
||||
private readonly LargeTextureStore beatmapPanelTextureStore;
|
||||
private readonly ITrackStore trackStore;
|
||||
private readonly IResourceStore<byte[]> files;
|
||||
private readonly RealmAccess realm;
|
||||
|
||||
[CanBeNull]
|
||||
private readonly GameHost host;
|
||||
|
||||
public WorkingBeatmapCache(ITrackStore trackStore, AudioManager audioManager, IResourceStore<byte[]> resources, IResourceStore<byte[]> files, WorkingBeatmap defaultBeatmap = null,
|
||||
GameHost host = null)
|
||||
GameHost host = null, RealmAccess realm = null)
|
||||
{
|
||||
DefaultBeatmap = defaultBeatmap;
|
||||
|
||||
@@ -63,6 +64,7 @@ namespace osu.Game.Beatmaps
|
||||
largeTextureStore = new LargeTextureStore(host?.Renderer ?? new DummyRenderer(), host?.CreateTextureLoaderStore(files));
|
||||
beatmapPanelTextureStore = new LargeTextureStore(host?.Renderer ?? new DummyRenderer(), new BeatmapPanelBackgroundTextureLoaderStore(host?.CreateTextureLoaderStore(files)));
|
||||
this.trackStore = trackStore;
|
||||
this.realm = realm;
|
||||
}
|
||||
|
||||
public void Invalidate(BeatmapSetInfo info)
|
||||
@@ -118,7 +120,7 @@ namespace osu.Game.Beatmaps
|
||||
ITrackStore IBeatmapResourceProvider.Tracks => trackStore;
|
||||
IRenderer IStorageResourceProvider.Renderer => host?.Renderer ?? new DummyRenderer();
|
||||
AudioManager IStorageResourceProvider.AudioManager => audioManager;
|
||||
RealmAccess IStorageResourceProvider.RealmAccess => null!;
|
||||
RealmAccess IStorageResourceProvider.RealmAccess => realm;
|
||||
IResourceStore<byte[]> IStorageResourceProvider.Files => files;
|
||||
IResourceStore<byte[]> IStorageResourceProvider.Resources => resources;
|
||||
IResourceStore<TextureUpload> IStorageResourceProvider.CreateTextureLoaderStore(IResourceStore<byte[]> underlyingStore) => host?.CreateTextureLoaderStore(underlyingStore);
|
||||
|
||||
@@ -13,6 +13,7 @@ using osu.Game.Beatmaps.Formats;
|
||||
using osu.Game.Beatmaps.Timing;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Rulesets.Objects;
|
||||
using osu.Game.Rulesets.Objects.Types;
|
||||
@@ -198,7 +199,7 @@ namespace osu.Game.Database
|
||||
ProgressNotification notification = new ProgressNotification
|
||||
{
|
||||
State = ProgressNotificationState.Active,
|
||||
Text = $"Exporting {itemFilename}...",
|
||||
Text = NotificationsStrings.FileExportOngoing(itemFilename),
|
||||
};
|
||||
|
||||
PostNotification?.Invoke(notification);
|
||||
@@ -225,7 +226,7 @@ namespace osu.Game.Database
|
||||
throw;
|
||||
}
|
||||
|
||||
notification.CompletionText = $"Exported {itemFilename}! Click to view.";
|
||||
notification.CompletionText = NotificationsStrings.FileExportFinished(itemFilename);
|
||||
notification.CompletionClickAction = () => ExportStorage.PresentFileExternally(filename);
|
||||
notification.State = ProgressNotificationState.Completed;
|
||||
});
|
||||
|
||||
@@ -10,6 +10,7 @@ using osu.Framework.Logging;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Collections;
|
||||
using osu.Game.IO.Legacy;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
|
||||
namespace osu.Game.Database
|
||||
@@ -63,7 +64,7 @@ namespace osu.Game.Database
|
||||
var notification = new ProgressNotification
|
||||
{
|
||||
State = ProgressNotificationState.Active,
|
||||
Text = "Collections import is initialising..."
|
||||
Text = NotificationsStrings.CollectionsImportInitialising,
|
||||
};
|
||||
|
||||
PostNotification?.Invoke(notification);
|
||||
@@ -71,7 +72,7 @@ namespace osu.Game.Database
|
||||
var importedCollections = readCollections(stream, notification);
|
||||
await importCollections(importedCollections).ConfigureAwait(false);
|
||||
|
||||
notification.CompletionText = $"Imported {importedCollections.Count} collections";
|
||||
notification.CompletionText = NotificationsStrings.CollectionsImportProgress(importedCollections.Count);
|
||||
notification.State = ProgressNotificationState.Completed;
|
||||
}
|
||||
|
||||
@@ -115,7 +116,7 @@ namespace osu.Game.Database
|
||||
{
|
||||
if (notification != null)
|
||||
{
|
||||
notification.Text = "Reading collections...";
|
||||
notification.Text = NotificationsStrings.ReadingCollections;
|
||||
notification.Progress = 0;
|
||||
}
|
||||
|
||||
@@ -150,7 +151,7 @@ namespace osu.Game.Database
|
||||
|
||||
if (notification != null)
|
||||
{
|
||||
notification.Text = $"Imported {i + 1} of {collectionCount} collections";
|
||||
notification.Text = NotificationsStrings.CollectionsImportProgressTotal(i + 1, collectionCount);
|
||||
notification.Progress = (float)(i + 1) / collectionCount;
|
||||
}
|
||||
|
||||
|
||||
@@ -10,6 +10,7 @@ using System.Threading.Tasks;
|
||||
using osu.Framework.Platform;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.IO;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
using osu.Game.Utils;
|
||||
using Realms;
|
||||
@@ -83,7 +84,7 @@ namespace osu.Game.Database
|
||||
ProgressNotification notification = new ProgressNotification
|
||||
{
|
||||
State = ProgressNotificationState.Active,
|
||||
Text = $"Exporting {itemFilename}...",
|
||||
Text = NotificationsStrings.FileExportOngoing(itemFilename),
|
||||
};
|
||||
|
||||
PostNotification?.Invoke(notification);
|
||||
@@ -106,7 +107,7 @@ namespace osu.Game.Database
|
||||
throw;
|
||||
}
|
||||
|
||||
notification.CompletionText = $"Exported {itemFilename}! Click to view.";
|
||||
notification.CompletionText = NotificationsStrings.FileExportFinished(itemFilename);
|
||||
notification.CompletionClickAction = () => ExportStorage.PresentFileExternally(filename);
|
||||
notification.State = ProgressNotificationState.Completed;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ using System.Threading.Tasks;
|
||||
using Humanizer;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Extensions;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Online.API;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
|
||||
@@ -55,7 +56,7 @@ namespace osu.Game.Database
|
||||
|
||||
DownloadNotification notification = new DownloadNotification
|
||||
{
|
||||
Text = $"Downloading {request.Model.GetDisplayString()}",
|
||||
Text = NotificationsStrings.Downloading(request.Model.GetDisplayString()),
|
||||
};
|
||||
|
||||
request.DownloadProgressed += progress =>
|
||||
|
||||
@@ -8,6 +8,8 @@ using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.ExceptionExtensions;
|
||||
using osu.Framework.Logging;
|
||||
using osu.Game.Online.API;
|
||||
|
||||
namespace osu.Game.Database
|
||||
@@ -81,7 +83,7 @@ namespace osu.Game.Database
|
||||
pendingTasks.Enqueue((id, tcs));
|
||||
|
||||
// Create a request task if there's not already one.
|
||||
if (pendingRequestTask == null)
|
||||
if (pendingRequestTask == null || pendingRequestTask.IsFaulted)
|
||||
createNewTask();
|
||||
|
||||
return tcs.Task;
|
||||
@@ -163,6 +165,14 @@ namespace osu.Game.Database
|
||||
}
|
||||
}
|
||||
|
||||
private void createNewTask() => pendingRequestTask = Task.Run(performLookup);
|
||||
private void createNewTask()
|
||||
{
|
||||
var nextTask = Task.Run(performLookup);
|
||||
nextTask.ContinueWith(t =>
|
||||
{
|
||||
Logger.Error(t.Exception.AsSingular(), $"{nameof(OnlineLookupCache<TLookup, TValue, TRequest>)} lookup request failed!");
|
||||
}, TaskContinuationOptions.OnlyOnFaulted);
|
||||
pendingRequestTask = nextTask;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -474,8 +474,10 @@ namespace osu.Game.Database
|
||||
|
||||
foreach (RealmNamedFileUsage file in item.Files.Where(f => HashableFileTypes.Any(ext => f.Filename.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f.Filename))
|
||||
{
|
||||
using (Stream s = Files.Store.GetStream(file.File.GetStoragePath()))
|
||||
s.CopyTo(hashable);
|
||||
using (Stream? s = Files.Store.GetStream(file.File.GetStoragePath()))
|
||||
{
|
||||
s?.CopyTo(hashable);
|
||||
}
|
||||
}
|
||||
|
||||
if (hashable.Length > 0)
|
||||
|
||||
@@ -561,7 +561,12 @@ namespace osu.Game.Graphics.Carousel
|
||||
}
|
||||
}
|
||||
|
||||
private enum TraversalType { Keyboard, Set, Group }
|
||||
private enum TraversalType
|
||||
{
|
||||
Keyboard,
|
||||
Set,
|
||||
Group
|
||||
}
|
||||
|
||||
private record TraversalOperation(TraversalType Type, int Direction);
|
||||
|
||||
|
||||
@@ -220,16 +220,18 @@ namespace osu.Game.Graphics.Cursor
|
||||
{
|
||||
activeCursor.FadeTo(1, 250, Easing.OutQuint);
|
||||
activeCursor.ScaleTo(1, 400, Easing.OutQuint);
|
||||
activeCursor.RotateTo(0, 400, Easing.OutQuint);
|
||||
dragRotationState = DragRotationState.NotDragging;
|
||||
|
||||
if (dragRotationState == DragRotationState.NotDragging)
|
||||
activeCursor.RotateTo(0, 400, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void PopOut()
|
||||
{
|
||||
activeCursor.FadeTo(0, 250, Easing.OutQuint);
|
||||
activeCursor.ScaleTo(0.6f, 250, Easing.In);
|
||||
activeCursor.RotateTo(0, 400, Easing.OutQuint);
|
||||
dragRotationState = DragRotationState.NotDragging;
|
||||
|
||||
if (dragRotationState == DragRotationState.NotDragging)
|
||||
activeCursor.RotateTo(0, 400, Easing.OutQuint);
|
||||
}
|
||||
|
||||
private void playTapSample(double baseFrequency = 1f)
|
||||
|
||||
@@ -11,6 +11,7 @@ using osu.Framework.Graphics.Effects;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Utils;
|
||||
|
||||
namespace osu.Game.Graphics.Cursor
|
||||
{
|
||||
@@ -93,10 +94,10 @@ namespace osu.Game.Graphics.Cursor
|
||||
protected override void PopIn()
|
||||
{
|
||||
instantMovement |= !IsPresent;
|
||||
this.FadeIn(500, Easing.OutQuint);
|
||||
this.FadeIn(300, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void PopOut() => this.Delay(150).FadeOut(500, Easing.OutQuint);
|
||||
protected override void PopOut() => this.Delay(150).FadeOut(300, Easing.OutQuint);
|
||||
|
||||
public override void Move(Vector2 pos)
|
||||
{
|
||||
@@ -107,7 +108,8 @@ namespace osu.Game.Graphics.Cursor
|
||||
}
|
||||
else
|
||||
{
|
||||
this.MoveTo(pos, 200, Easing.OutQuint);
|
||||
// This method is called every frame so we can do this safely here.
|
||||
Position = Interpolation.ValueAt(Time.Elapsed, Position, pos, 0, 120, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,6 +25,11 @@ namespace osu.Game.Graphics
|
||||
/// </summary>
|
||||
public const float STAR_DIFFICULTY_DEFINED_COLOUR_CUTOFF = 6.5f;
|
||||
|
||||
/// <summary>
|
||||
/// Star rating at which display text switches from static colours to a gradient.
|
||||
/// </summary>
|
||||
public const float STAR_DIFFICULTY_TEXT_GRADIENT_CUTOFF = 9.0f;
|
||||
|
||||
public static readonly (float, Color4)[] STAR_DIFFICULTY_SPECTRUM =
|
||||
{
|
||||
(0.1f, Color4Extensions.FromHex("aaaaaa")),
|
||||
@@ -42,11 +47,34 @@ namespace osu.Game.Graphics
|
||||
(10.0f, Color4.Black),
|
||||
};
|
||||
|
||||
public static readonly (float, Color4)[] STAR_DIFFICULTY_TEXT_SPECTRUM =
|
||||
{
|
||||
(9.0f, Color4Extensions.FromHex("f6f05c")),
|
||||
(9.9f, Color4Extensions.FromHex("ff8068")),
|
||||
(10.6f, Color4Extensions.FromHex("ff4e6f")),
|
||||
(11.5f, Color4Extensions.FromHex("c645b8")),
|
||||
(12.4f, Color4Extensions.FromHex("6563de")),
|
||||
};
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the colour for a given point in the star range.
|
||||
/// </summary>
|
||||
public Color4 ForStarDifficulty(double starDifficulty) => ColourUtils.SampleFromLinearGradient(STAR_DIFFICULTY_SPECTRUM, (float)Math.Round(starDifficulty, 2, MidpointRounding.AwayFromZero));
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the colour for the text inside the star rating display.
|
||||
/// </summary>
|
||||
public Color4 ForStarDifficultyText(double starDifficulty)
|
||||
{
|
||||
if (starDifficulty < STAR_DIFFICULTY_DEFINED_COLOUR_CUTOFF)
|
||||
return Color4.Black.Opacity(0.75f);
|
||||
|
||||
if (starDifficulty < STAR_DIFFICULTY_TEXT_GRADIENT_CUTOFF)
|
||||
return Orange1;
|
||||
|
||||
return ColourUtils.SampleFromLinearGradient(STAR_DIFFICULTY_TEXT_SPECTRUM, (float)Math.Round(starDifficulty, 2, MidpointRounding.AwayFromZero));
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Retrieves the colour for a <see cref="ScoreRank"/>.
|
||||
/// </summary>
|
||||
|
||||
@@ -17,6 +17,7 @@ using osu.Framework.Platform;
|
||||
using osu.Framework.Threading;
|
||||
using osu.Game.Configuration;
|
||||
using osu.Game.Input.Bindings;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Online.Multiplayer;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Overlays.Notifications;
|
||||
@@ -169,7 +170,7 @@ namespace osu.Game.Graphics
|
||||
|
||||
notificationOverlay.Post(new SimpleNotification
|
||||
{
|
||||
Text = $"Screenshot {filename} saved!",
|
||||
Text = NotificationsStrings.ScreenshotSaved(filename),
|
||||
Activated = () =>
|
||||
{
|
||||
storage.PresentFileExternally(filename);
|
||||
|
||||
@@ -232,14 +232,14 @@ namespace osu.Game.Graphics.UserInterface
|
||||
private void updateFpsDisplay()
|
||||
{
|
||||
counterDrawFPS.Colour = getColour(displayedFpsCount / aimDrawFPS);
|
||||
counterDrawFPS.Text = $"{displayedFpsCount:#,0}fps";
|
||||
counterDrawFPS.Text = $"{displayedFpsCount:#,0} fps";
|
||||
}
|
||||
|
||||
private void updateFrameTimeDisplay()
|
||||
{
|
||||
counterUpdateFrameTime.Text = displayedFrameTime < 5
|
||||
? $"{displayedFrameTime:N1}ms"
|
||||
: $"{displayedFrameTime:N0}ms";
|
||||
? $"{displayedFrameTime:N1} ms"
|
||||
: $"{displayedFrameTime:N0} ms";
|
||||
|
||||
counterUpdateFrameTime.Colour = getColour((1000 / displayedFrameTime) / aimUpdateFPS);
|
||||
}
|
||||
|
||||
@@ -82,7 +82,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
? $"/{(clock.MaximumUpdateHz > 0 && clock.MaximumUpdateHz < 10000 ? clock.MaximumUpdateHz.ToString("0") : "∞"),4}"
|
||||
: string.Empty;
|
||||
|
||||
textFlow.AddParagraph($"{clock.FramesPerSecond:0}{maximum}fps ({clock.ElapsedFrameTime:0.00}ms)");
|
||||
textFlow.AddParagraph($"{clock.FramesPerSecond:0}{maximum} fps ({clock.ElapsedFrameTime:0.00} ms)");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,10 @@
|
||||
// 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.Linq;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Utils;
|
||||
@@ -21,10 +18,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
/// </summary>
|
||||
public partial class HoverClickSounds : HoverSounds
|
||||
{
|
||||
public Bindable<bool> Enabled = new Bindable<bool>(true);
|
||||
|
||||
private Sample sampleClick;
|
||||
private Sample sampleClickDisabled;
|
||||
private Sample? sampleClick;
|
||||
private Sample? sampleClickDisabled;
|
||||
|
||||
private readonly MouseButton[] buttons;
|
||||
|
||||
@@ -36,7 +31,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
/// Array of button codes which should trigger the click sound.
|
||||
/// If this optional parameter is omitted or set to <code>null</code>, the click sound will only be played on left click.
|
||||
/// </param>
|
||||
public HoverClickSounds(HoverSampleSet sampleSet = HoverSampleSet.Default, MouseButton[] buttons = null)
|
||||
public HoverClickSounds(HoverSampleSet sampleSet = HoverSampleSet.Default, MouseButton[]? buttons = null)
|
||||
: base(sampleSet)
|
||||
{
|
||||
this.buttons = buttons ?? new[] { MouseButton.Left };
|
||||
@@ -60,14 +55,6 @@ namespace osu.Game.Graphics.UserInterface
|
||||
return base.OnClick(e);
|
||||
}
|
||||
|
||||
public override void PlayHoverSample()
|
||||
{
|
||||
if (!Enabled.Value)
|
||||
return;
|
||||
|
||||
base.PlayHoverSample();
|
||||
}
|
||||
|
||||
public void PlayClickSample()
|
||||
{
|
||||
var channel = Enabled.Value ? sampleClick?.GetChannel() : sampleClickDisabled?.GetChannel();
|
||||
|
||||
@@ -6,6 +6,7 @@
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Audio;
|
||||
using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Utils;
|
||||
@@ -18,6 +19,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
/// </summary>
|
||||
public partial class HoverSounds : HoverSampleDebounceComponent
|
||||
{
|
||||
public readonly Bindable<bool> Enabled = new Bindable<bool>(true);
|
||||
|
||||
private Sample sampleHover;
|
||||
|
||||
protected readonly HoverSampleSet SampleSet;
|
||||
@@ -37,6 +40,9 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
public override void PlayHoverSample()
|
||||
{
|
||||
if (!Enabled.Value)
|
||||
return;
|
||||
|
||||
sampleHover.Frequency.Value = 0.98 + RNG.NextDouble(0.04);
|
||||
sampleHover.Play();
|
||||
}
|
||||
|
||||
@@ -92,12 +92,11 @@ namespace osu.Game.Graphics.UserInterface
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Colour = Enabled.Value ? Colour4.White : DimColour;
|
||||
Enabled.BindValueChanged(_ => this.FadeColour(DimColour, 200, Easing.OutQuint), true);
|
||||
Enabled.BindValueChanged(_ => content.FadeColour(DimColour, 200, Easing.OutQuint), true);
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
protected virtual Colour4 DimColour => colours.Gray9;
|
||||
protected virtual Colour4 DimColour => Enabled.Value ? Color4.White : colours.Gray9;
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
|
||||
@@ -42,6 +42,8 @@ namespace osu.Game.Graphics.UserInterface
|
||||
Margin = new MarginPadding { Left = 2 },
|
||||
};
|
||||
|
||||
protected bool DrawBorder { get; init; }
|
||||
|
||||
private OsuCaret? caret;
|
||||
|
||||
private bool selectionStarted;
|
||||
@@ -256,7 +258,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
protected override void OnFocus(FocusEvent e)
|
||||
{
|
||||
if (Masking)
|
||||
if (DrawBorder)
|
||||
BorderThickness = 3;
|
||||
|
||||
base.OnFocus(e);
|
||||
@@ -268,7 +270,7 @@ namespace osu.Game.Graphics.UserInterface
|
||||
|
||||
protected override void OnFocusLost(FocusLostEvent e)
|
||||
{
|
||||
if (Masking)
|
||||
if (DrawBorder)
|
||||
BorderThickness = 0;
|
||||
|
||||
base.OnFocusLost(e);
|
||||
|
||||
@@ -4,11 +4,11 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
@@ -28,62 +28,118 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
/// </summary>
|
||||
public LocalisableString Caption { get; init; }
|
||||
|
||||
/// <summary>
|
||||
/// Sets text inside the button.
|
||||
/// </summary>
|
||||
public LocalisableString ButtonText { get; init; }
|
||||
|
||||
public Action? Action { get; init; }
|
||||
/// <summary>
|
||||
/// Sets a custom button icon. Not shown when <see cref="ButtonText"/> is set.
|
||||
/// </summary>
|
||||
public IconUsage ButtonIcon { get; init; } = FontAwesome.Solid.ChevronRight;
|
||||
|
||||
private readonly Color4? backgroundColour;
|
||||
|
||||
/// <summary>
|
||||
/// Sets a custom background colour for the button.
|
||||
/// </summary>
|
||||
public Color4? BackgroundColour
|
||||
{
|
||||
get => backgroundColour;
|
||||
init
|
||||
{
|
||||
backgroundColour = value;
|
||||
|
||||
if (IsLoaded)
|
||||
updateState();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The action to invoke when the button is clicked.
|
||||
/// </summary>
|
||||
public Action? Action { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// Whether the button is enabled.
|
||||
/// </summary>
|
||||
public readonly BindableBool Enabled = new BindableBool(true);
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
private FormControlBackground background = null!;
|
||||
private OsuTextFlowContainer text = null!;
|
||||
private Button button = null!;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = 50;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
CornerExponent = 2.5f;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
InternalChild = new Container
|
||||
{
|
||||
new Box
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Masking = true,
|
||||
CornerRadius = 5,
|
||||
CornerExponent = 2.5f,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background5,
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding
|
||||
background = new FormControlBackground(),
|
||||
new Container
|
||||
{
|
||||
Left = 9,
|
||||
Right = 5,
|
||||
Vertical = 5,
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new OsuTextFlowContainer
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Width = 0.45f,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Text = Caption,
|
||||
Left = 9,
|
||||
Right = 5,
|
||||
Vertical = 5,
|
||||
},
|
||||
new Button
|
||||
Children = new Drawable[]
|
||||
{
|
||||
Action = Action,
|
||||
Text = ButtonText,
|
||||
RelativeSizeAxes = ButtonText == default ? Axes.None : Axes.X,
|
||||
Width = ButtonText == default ? 90 : 0.45f,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
}
|
||||
text = new OsuTextFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Text = Caption,
|
||||
},
|
||||
button = new Button
|
||||
{
|
||||
Action = () => Action?.Invoke(),
|
||||
Text = ButtonText,
|
||||
Icon = ButtonIcon,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Enabled = { BindTarget = Enabled },
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
if (ButtonText == default)
|
||||
{
|
||||
text.Padding = new MarginPadding { Right = 100 };
|
||||
button.Width = 90;
|
||||
}
|
||||
else
|
||||
{
|
||||
text.Width = 0.55f;
|
||||
text.Padding = new MarginPadding { Right = 10 };
|
||||
button.RelativeSizeAxes = Axes.X;
|
||||
button.Width = 0.45f;
|
||||
}
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
Enabled.BindValueChanged(_ => updateState(), true);
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
@@ -98,12 +154,29 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
updateState();
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
{
|
||||
if (Enabled.Value)
|
||||
{
|
||||
background.Flash();
|
||||
button.TriggerClick();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
BorderThickness = IsHovered ? 2 : 0;
|
||||
text.Colour = Enabled.Value ? colourProvider.Content1 : colourProvider.Background1;
|
||||
|
||||
if (IsHovered)
|
||||
BorderColour = colourProvider.Light4;
|
||||
if (!Enabled.Value)
|
||||
background.VisualStyle = VisualStyle.Disabled;
|
||||
else if (IsHovered)
|
||||
background.VisualStyle = VisualStyle.Hovered;
|
||||
else
|
||||
background.VisualStyle = VisualStyle.Normal;
|
||||
|
||||
// TODO: Support BackgroundColour?
|
||||
}
|
||||
|
||||
public partial class Button : OsuButton
|
||||
@@ -125,6 +198,8 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
}
|
||||
}
|
||||
|
||||
public IconUsage Icon { get; init; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OverlayColourProvider overlayColourProvider)
|
||||
{
|
||||
@@ -135,7 +210,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
Add(new SpriteIcon
|
||||
{
|
||||
Icon = FontAwesome.Solid.ChevronRight,
|
||||
Icon = Icon,
|
||||
Size = new Vector2(16),
|
||||
Shadow = true,
|
||||
Anchor = Anchor.Centre,
|
||||
|
||||
@@ -9,14 +9,10 @@ using osu.Framework.Audio.Sample;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions.IEnumerableExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterfaceV2
|
||||
@@ -41,9 +37,8 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
/// </summary>
|
||||
public LocalisableString HintText { get; init; }
|
||||
|
||||
private Box background = null!;
|
||||
private FormControlBackground background = null!;
|
||||
private FormFieldCaption caption = null!;
|
||||
private OsuSpriteText text = null!;
|
||||
|
||||
private Sample? sampleChecked;
|
||||
private Sample? sampleUnchecked;
|
||||
@@ -56,37 +51,33 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
private void load(AudioManager audio)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = 50;
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
CornerExponent = 2.5f;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background5,
|
||||
},
|
||||
background = new FormControlBackground(),
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding(9),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
caption = new FormFieldCaption
|
||||
{
|
||||
Caption = Caption,
|
||||
TooltipText = HintText,
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
},
|
||||
text = new OsuSpriteText
|
||||
new Container
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding { Right = SwitchButton.WIDTH + 5 },
|
||||
Children = new Drawable[]
|
||||
{
|
||||
caption = new FormFieldCaption
|
||||
{
|
||||
Caption = Caption,
|
||||
TooltipText = HintText,
|
||||
},
|
||||
},
|
||||
},
|
||||
new SwitchButton
|
||||
{
|
||||
@@ -97,7 +88,6 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
sampleChecked = audio.Samples.Get(@"UI/check-on");
|
||||
sampleUnchecked = audio.Samples.Get(@"UI/check-off");
|
||||
sampleDisabled = audio.Samples.Get(@"UI/default-select-disabled");
|
||||
@@ -111,7 +101,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
updateState();
|
||||
playSamples();
|
||||
background.FlashColour(ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark2), 800, Easing.OutQuint);
|
||||
background.Flash();
|
||||
|
||||
ValueChanged?.Invoke();
|
||||
});
|
||||
@@ -151,17 +141,13 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
private void updateState()
|
||||
{
|
||||
caption.Colour = Current.Disabled ? colourProvider.Background1 : colourProvider.Content2;
|
||||
text.Colour = Current.Disabled ? colourProvider.Background1 : colourProvider.Content1;
|
||||
|
||||
text.Text = Current.Value ? CommonStrings.Enabled : CommonStrings.Disabled;
|
||||
|
||||
// use FadeColour to override any existing colour transform (i.e. FlashColour on click).
|
||||
background.FadeColour(IsHovered
|
||||
? ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4)
|
||||
: colourProvider.Background5);
|
||||
|
||||
BorderThickness = IsHovered ? 2 : 0;
|
||||
BorderColour = Current.Disabled ? colourProvider.Dark1 : colourProvider.Light4;
|
||||
if (IsDisabled)
|
||||
background.VisualStyle = VisualStyle.Disabled;
|
||||
else if (IsHovered)
|
||||
background.VisualStyle = VisualStyle.Hovered;
|
||||
else
|
||||
background.VisualStyle = VisualStyle.Normal;
|
||||
}
|
||||
|
||||
public IEnumerable<LocalisableString> FilterTerms => Caption.Yield();
|
||||
|
||||
@@ -33,7 +33,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
public BindableBool CanAdd { get; } = new BindableBool(true);
|
||||
|
||||
private Box background = null!;
|
||||
private FormControlBackground background = null!;
|
||||
private FormFieldCaption caption = null!;
|
||||
private FillFlowContainer flow = null!;
|
||||
private RoundedButton addButton = null!;
|
||||
@@ -47,16 +47,9 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background5,
|
||||
},
|
||||
background = new FormControlBackground(),
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
@@ -140,13 +133,12 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
background.Colour = colourProvider.Background5;
|
||||
caption.Colour = colourProvider.Content2;
|
||||
|
||||
BorderThickness = IsHovered ? 2 : 0;
|
||||
|
||||
if (IsHovered)
|
||||
BorderColour = colourProvider.Light4;
|
||||
background.VisualStyle = VisualStyle.Hovered;
|
||||
else
|
||||
background.VisualStyle = VisualStyle.Normal;
|
||||
}
|
||||
|
||||
private void updateColours()
|
||||
|
||||
@@ -0,0 +1,125 @@
|
||||
// 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.Extensions.Color4Extensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK.Graphics;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
public partial class FormControlBackground : CompositeDrawable
|
||||
{
|
||||
public const float CORNER_EXPONENT = 2.5f;
|
||||
public const float BORDER_THICKNESS = 2.5f;
|
||||
|
||||
private VisualStyle visualStyle;
|
||||
|
||||
public VisualStyle VisualStyle
|
||||
{
|
||||
get => visualStyle;
|
||||
set
|
||||
{
|
||||
visualStyle = value;
|
||||
updateStyle();
|
||||
}
|
||||
}
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
private readonly Box box;
|
||||
|
||||
private readonly HoverSounds sounds;
|
||||
|
||||
public FormControlBackground()
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both;
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
|
||||
CornerExponent = CORNER_EXPONENT;
|
||||
BorderThickness = BORDER_THICKNESS;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
box = new Box
|
||||
{
|
||||
Colour = Color4.White,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
},
|
||||
sounds = new HoverSounds(),
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
updateStyle();
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
public void Flash()
|
||||
{
|
||||
box.FlashColour(ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark2), 800, Easing.OutQuint);
|
||||
}
|
||||
|
||||
private void updateStyle()
|
||||
{
|
||||
sounds.Enabled.Value = visualStyle != VisualStyle.Disabled;
|
||||
|
||||
ColourInfo colour;
|
||||
ColourInfo borderColour;
|
||||
|
||||
bool border = false;
|
||||
|
||||
switch (visualStyle)
|
||||
{
|
||||
case VisualStyle.Normal:
|
||||
colour = colourProvider.Background4.Darken(0.1f);
|
||||
borderColour = colourProvider.Light4;
|
||||
break;
|
||||
|
||||
case VisualStyle.Disabled:
|
||||
colour = colourProvider.Background4;
|
||||
borderColour = colourProvider.Dark1;
|
||||
break;
|
||||
|
||||
case VisualStyle.Hovered:
|
||||
colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4);
|
||||
borderColour = colourProvider.Light4;
|
||||
border = true;
|
||||
break;
|
||||
|
||||
case VisualStyle.Focused:
|
||||
colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark3);
|
||||
border = true;
|
||||
borderColour = colourProvider.Highlight1;
|
||||
break;
|
||||
|
||||
default:
|
||||
throw new ArgumentOutOfRangeException();
|
||||
}
|
||||
|
||||
this.TransformTo(nameof(BorderColour), border ? borderColour : colour, 250, Easing.OutQuint);
|
||||
|
||||
box.FadeColour(colour, 250, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
public enum VisualStyle
|
||||
{
|
||||
Normal,
|
||||
Disabled,
|
||||
Hovered,
|
||||
Focused
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Input.Events;
|
||||
@@ -14,6 +15,7 @@ using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osu.Game.Resources.Localisation.Web;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterfaceV2
|
||||
@@ -28,7 +30,11 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
/// <summary>
|
||||
/// Hint text containing an extended description of this slider bar, displayed in a tooltip when hovering the caption.
|
||||
/// </summary>
|
||||
public LocalisableString HintText { get; init; }
|
||||
public LocalisableString HintText
|
||||
{
|
||||
get => header.HintText;
|
||||
set => header.HintText = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The maximum height of the dropdown's menu.
|
||||
@@ -38,6 +44,8 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
private FormDropdownHeader header = null!;
|
||||
|
||||
private const float header_menu_spacing = 5;
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
@@ -45,6 +53,10 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
header.Caption = Caption;
|
||||
header.HintText = HintText;
|
||||
|
||||
// there's bottom margin applied inside the header to give spacing between the header and the menu.
|
||||
// however when the menu is closed the extra spacing remains present. to remove it, apply negative bottom padding here.
|
||||
Margin = new MarginPadding { Bottom = -header_menu_spacing };
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@@ -133,6 +145,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
private FormFieldCaption caption = null!;
|
||||
private OsuSpriteText label = null!;
|
||||
private SpriteIcon chevron = null!;
|
||||
private FormControlBackground background = null!;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
@@ -140,45 +153,54 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.None;
|
||||
Height = 50;
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
|
||||
Foreground.AutoSizeAxes = Axes.None;
|
||||
Foreground.RelativeSizeAxes = Axes.Both;
|
||||
Foreground.Padding = new MarginPadding(9);
|
||||
// We use our own background for more control.
|
||||
Background.Alpha = 0;
|
||||
|
||||
Foreground.Children = new Drawable[]
|
||||
{
|
||||
caption = new FormFieldCaption
|
||||
{
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
Caption = Caption,
|
||||
TooltipText = HintText,
|
||||
},
|
||||
label = new OsuSpriteText
|
||||
background = new FormControlBackground(),
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
},
|
||||
chevron = new SpriteIcon
|
||||
{
|
||||
Icon = FontAwesome.Solid.ChevronDown,
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
Size = new Vector2(16),
|
||||
Margin = new MarginPadding { Right = 5 },
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding(9),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0, 4),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
caption = new FormFieldCaption
|
||||
{
|
||||
Caption = Caption,
|
||||
TooltipText = HintText,
|
||||
},
|
||||
label = new TruncatingSpriteText
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Padding = new MarginPadding { Right = 25 },
|
||||
AlwaysPresent = true,
|
||||
},
|
||||
}
|
||||
},
|
||||
chevron = new SpriteIcon
|
||||
{
|
||||
Icon = FontAwesome.Solid.ChevronDown,
|
||||
Anchor = Anchor.BottomRight,
|
||||
Origin = Anchor.BottomRight,
|
||||
Size = new Vector2(16),
|
||||
Margin = new MarginPadding { Right = 5 },
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
AddInternal(new HoverClickSounds
|
||||
{
|
||||
Enabled = { BindTarget = Enabled },
|
||||
});
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
@@ -212,8 +234,6 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
label.Alpha = string.IsNullOrEmpty(SearchBar.SearchTerm.Value) ? 1 : 0;
|
||||
|
||||
caption.Colour = Dropdown.Current.Disabled ? colourProvider.Background1 : colourProvider.Content2;
|
||||
label.Colour = Dropdown.Current.Disabled ? colourProvider.Background1 : colourProvider.Content1;
|
||||
chevron.Colour = Dropdown.Current.Disabled ? colourProvider.Background1 : colourProvider.Content1;
|
||||
@@ -221,25 +241,26 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
bool dropdownOpen = Dropdown.Menu.State == MenuState.Open;
|
||||
|
||||
BorderThickness = IsHovered || dropdownOpen ? 2 : 0;
|
||||
if (dropdownOpen)
|
||||
label.Alpha = AlwaysShowSearchBar || !string.IsNullOrEmpty(SearchBar.SearchTerm.Value) ? 0 : 1;
|
||||
else
|
||||
label.Alpha = 1;
|
||||
|
||||
if (Dropdown.Current.Disabled)
|
||||
BorderColour = colourProvider.Dark1;
|
||||
else
|
||||
BorderColour = dropdownOpen ? colourProvider.Highlight1 : colourProvider.Light4;
|
||||
|
||||
if (dropdownOpen)
|
||||
Background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark3);
|
||||
background.VisualStyle = VisualStyle.Disabled;
|
||||
else if (dropdownOpen)
|
||||
background.VisualStyle = VisualStyle.Focused;
|
||||
else if (IsHovered)
|
||||
Background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4);
|
||||
background.VisualStyle = VisualStyle.Hovered;
|
||||
else
|
||||
Background.Colour = colourProvider.Background5;
|
||||
background.VisualStyle = VisualStyle.Normal;
|
||||
}
|
||||
|
||||
private void updateChevron()
|
||||
{
|
||||
bool open = Dropdown.Menu.State == MenuState.Open;
|
||||
chevron.ScaleTo(open ? new Vector2(1f, -1f) : Vector2.One, 300, Easing.OutQuint);
|
||||
chevron.MoveToY(open ? -chevron.DrawHeight : 0, 300, Easing.OutQuint);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -250,7 +271,10 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
protected override void PopIn() => this.FadeIn();
|
||||
protected override void PopOut() => this.FadeOut();
|
||||
|
||||
protected override TextBox CreateTextBox() => TextBox = new FormTextBox.InnerTextBox();
|
||||
protected override TextBox CreateTextBox() => TextBox = new FormTextBox.InnerTextBox
|
||||
{
|
||||
PlaceholderText = HomeStrings.SearchPlaceholder,
|
||||
};
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
@@ -258,7 +282,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
TextBox.Anchor = Anchor.BottomLeft;
|
||||
TextBox.Origin = Anchor.BottomLeft;
|
||||
TextBox.RelativeSizeAxes = Axes.X;
|
||||
TextBox.Margin = new MarginPadding(9);
|
||||
Padding = new MarginPadding { Left = 9, Bottom = 9, Right = 34 };
|
||||
}
|
||||
}
|
||||
|
||||
@@ -268,11 +292,31 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
private void load(OverlayColourProvider colourProvider)
|
||||
{
|
||||
ItemsContainer.Padding = new MarginPadding(9);
|
||||
Margin = new MarginPadding { Top = 5 };
|
||||
|
||||
MaskingContainer.BorderThickness = 2;
|
||||
MaskingContainer.BorderThickness = FormControlBackground.BORDER_THICKNESS;
|
||||
MaskingContainer.CornerExponent = FormControlBackground.CORNER_EXPONENT;
|
||||
MaskingContainer.BorderColour = colourProvider.Highlight1;
|
||||
}
|
||||
|
||||
protected override void AnimateOpen()
|
||||
{
|
||||
base.AnimateOpen();
|
||||
|
||||
// there's negative bottom margin applied on the whole dropdown control to remove extra spacing when the menu is closed.
|
||||
// however, when the menu is open, we want spacing between the menu and the next control below it. therefore apply bottom margin here.
|
||||
// we use a transform to keep the open animation smooth while margin is adjusted.
|
||||
this.TransformTo(nameof(Margin), new MarginPadding
|
||||
{
|
||||
Top = header_menu_spacing,
|
||||
Bottom = header_menu_spacing
|
||||
}, 50, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void AnimateClose()
|
||||
{
|
||||
base.AnimateClose();
|
||||
this.TransformTo(nameof(Margin), new MarginPadding { Bottom = 0 }, 300);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,19 +2,20 @@
|
||||
// See the LICENCE file in the repository root for full licence text.
|
||||
|
||||
using osu.Framework.Allocation;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Sprites;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.Containers;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
public partial class FormFieldCaption : CompositeDrawable, IHasTooltip
|
||||
{
|
||||
private OsuTextFlowContainer textFlow = null!;
|
||||
|
||||
private LocalisableString caption;
|
||||
|
||||
public LocalisableString Caption
|
||||
@@ -24,45 +25,60 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
caption = value;
|
||||
|
||||
if (captionText.IsNotNull())
|
||||
captionText.Text = value;
|
||||
if (IsLoaded)
|
||||
updateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
private OsuSpriteText captionText = null!;
|
||||
private LocalisableString tooltipText;
|
||||
|
||||
public LocalisableString TooltipText { get; set; }
|
||||
public LocalisableString TooltipText
|
||||
{
|
||||
get => tooltipText;
|
||||
set
|
||||
{
|
||||
tooltipText = value;
|
||||
|
||||
if (IsLoaded)
|
||||
updateDisplay();
|
||||
}
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
AutoSizeAxes = Axes.Both;
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
InternalChild = new FillFlowContainer
|
||||
InternalChild = textFlow = new OsuTextFlowContainer(t => t.Font = OsuFont.Style.Caption1)
|
||||
{
|
||||
AutoSizeAxes = Axes.Both,
|
||||
Direction = FillDirection.Horizontal,
|
||||
Spacing = new Vector2(5),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
captionText = new OsuSpriteText
|
||||
{
|
||||
Text = caption,
|
||||
Font = OsuFont.Default.With(size: 12, weight: FontWeight.SemiBold),
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
},
|
||||
new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.CentreLeft,
|
||||
Origin = Anchor.CentreLeft,
|
||||
Alpha = TooltipText == default ? 0 : 1,
|
||||
Size = new Vector2(10),
|
||||
Icon = FontAwesome.Solid.QuestionCircle,
|
||||
Margin = new MarginPadding { Top = 1, },
|
||||
}
|
||||
},
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
updateDisplay();
|
||||
}
|
||||
|
||||
private void updateDisplay()
|
||||
{
|
||||
textFlow.Text = caption;
|
||||
|
||||
if (TooltipText != default)
|
||||
{
|
||||
textFlow.AddArbitraryDrawable(new SpriteIcon
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Size = new Vector2(10),
|
||||
Icon = FontAwesome.Solid.QuestionCircle,
|
||||
Margin = new MarginPadding { Left = 5 },
|
||||
Y = 1f,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,7 +11,6 @@ using osu.Framework.Bindables;
|
||||
using osu.Framework.Extensions;
|
||||
using osu.Framework.Extensions.ObjectExtensions;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Colour;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.Cursor;
|
||||
using osu.Framework.Graphics.Shapes;
|
||||
@@ -70,7 +69,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
public Container PreviewContainer { get; private set; } = null!;
|
||||
|
||||
private Box background = null!;
|
||||
private FormControlBackground background = null!;
|
||||
|
||||
private FormFieldCaption caption = null!;
|
||||
private OsuSpriteText placeholderText = null!;
|
||||
@@ -93,16 +92,9 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background5,
|
||||
},
|
||||
background = new FormControlBackground(),
|
||||
PreviewContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
@@ -117,34 +109,46 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Height = 50,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
Padding = new MarginPadding(9),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
caption = new FormFieldCaption
|
||||
new FillFlowContainer
|
||||
{
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
Caption = Caption,
|
||||
TooltipText = HintText,
|
||||
},
|
||||
placeholderText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 1,
|
||||
Text = PlaceholderText,
|
||||
Colour = colourProvider.Foreground1,
|
||||
},
|
||||
filenameText = new OsuSpriteText
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 1,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0f, 4f),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
caption = new FormFieldCaption
|
||||
{
|
||||
Caption = Caption,
|
||||
TooltipText = HintText,
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Children = new[]
|
||||
{
|
||||
placeholderText = new OsuSpriteText
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 1,
|
||||
Text = PlaceholderText,
|
||||
Colour = colourProvider.Foreground1,
|
||||
},
|
||||
filenameText = new OsuSpriteText
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 1,
|
||||
},
|
||||
}
|
||||
}
|
||||
},
|
||||
},
|
||||
new SpriteIcon
|
||||
{
|
||||
@@ -182,7 +186,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
initialChooserPath = Current.Value?.DirectoryName;
|
||||
placeholderText.Alpha = Current.Value == null ? 1 : 0;
|
||||
filenameText.Text = Current.Value?.Name ?? string.Empty;
|
||||
background.FlashColour(ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark2), 800, Easing.OutQuint);
|
||||
background.Flash();
|
||||
}
|
||||
|
||||
protected override bool OnClick(ClickEvent e)
|
||||
@@ -208,22 +212,14 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
caption.Colour = Current.Disabled ? colourProvider.Foreground1 : colourProvider.Content2;
|
||||
filenameText.Colour = Current.Disabled || Current.Value == null ? colourProvider.Foreground1 : colourProvider.Content1;
|
||||
|
||||
if (!Current.Disabled)
|
||||
{
|
||||
BorderThickness = IsHovered || popoverState.Value == Visibility.Visible ? 2 : 0;
|
||||
BorderColour = popoverState.Value == Visibility.Visible ? colourProvider.Highlight1 : colourProvider.Light4;
|
||||
|
||||
if (popoverState.Value == Visibility.Visible)
|
||||
background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark3);
|
||||
else if (IsHovered)
|
||||
background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4);
|
||||
else
|
||||
background.Colour = colourProvider.Background5;
|
||||
}
|
||||
if (Current.Disabled)
|
||||
background.VisualStyle = VisualStyle.Disabled;
|
||||
else if (popoverState.Value == Visibility.Visible)
|
||||
background.VisualStyle = VisualStyle.Focused;
|
||||
else if (IsHovered)
|
||||
background.VisualStyle = VisualStyle.Hovered;
|
||||
else
|
||||
{
|
||||
background.Colour = colourProvider.Background4;
|
||||
}
|
||||
background.VisualStyle = VisualStyle.Normal;
|
||||
}
|
||||
|
||||
protected override void Dispose(bool isDisposing)
|
||||
@@ -242,7 +238,8 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
Task ICanAcceptFiles.Import(ImportTask[] tasks, ImportParameters parameters) => throw new NotImplementedException();
|
||||
|
||||
protected virtual FileChooserPopover CreatePopover(string[] handledExtensions, Bindable<FileInfo?> current, string? chooserPath) => new FileChooserPopover(handledExtensions, current, chooserPath);
|
||||
protected virtual FileChooserPopover CreatePopover(string[] handledExtensions, Bindable<FileInfo?> current, string? chooserPath) =>
|
||||
new FileChooserPopover(handledExtensions, current, chooserPath);
|
||||
|
||||
public Popover GetPopover()
|
||||
{
|
||||
|
||||
@@ -22,6 +22,8 @@ using osu.Game.Graphics.Sprites;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Localisation;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK.Graphics;
|
||||
using Vector2 = osuTK.Vector2;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
@@ -34,13 +36,20 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
set
|
||||
{
|
||||
current.Current = value;
|
||||
|
||||
// the above `Current` set could have disabled the instantaneous bindable too,
|
||||
// but we still need to copy out `Default` manually,
|
||||
// so lift that disable for a second and then restore it
|
||||
currentNumberInstantaneous.Disabled = false;
|
||||
currentNumberInstantaneous.Default = current.Default;
|
||||
currentNumberInstantaneous.Disabled = current.Disabled;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly BindableNumberWithCurrent<T> current = new BindableNumberWithCurrent<T>();
|
||||
|
||||
private readonly BindableNumber<T> currentNumberInstantaneous = new BindableNumber<T>();
|
||||
private readonly InnerSlider slider;
|
||||
|
||||
/// <summary>
|
||||
/// Whether changes to the value should instantaneously transfer to outside bindables.
|
||||
@@ -83,20 +92,13 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
/// </summary>
|
||||
public LocalisableString HintText { get; init; }
|
||||
|
||||
private float keyboardStep;
|
||||
|
||||
/// <summary>
|
||||
/// A custom step value for each key press which actuates a change on this control.
|
||||
/// </summary>
|
||||
public float KeyboardStep
|
||||
{
|
||||
get => keyboardStep;
|
||||
set
|
||||
{
|
||||
keyboardStep = value;
|
||||
if (IsLoaded)
|
||||
slider.KeyboardStep = value;
|
||||
}
|
||||
get => slider.KeyboardStep;
|
||||
set => slider.KeyboardStep = value;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -107,7 +109,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
/// <summary>
|
||||
/// Whether sound effects should play when adjusting this slider.
|
||||
/// </summary>
|
||||
public bool PlaySamplesOnAdjust { get; init; }
|
||||
public bool PlaySamplesOnAdjust { get; init; } = true;
|
||||
|
||||
/// <summary>
|
||||
/// The string formatting function to use for the value label.
|
||||
@@ -120,11 +122,10 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
/// </summary>
|
||||
public Func<T, LocalisableString> TooltipFormat { get; init; }
|
||||
|
||||
private Box background = null!;
|
||||
private FormControlBackground background = null!;
|
||||
private Box flashLayer = null!;
|
||||
private FormTextBox.InnerTextBox textBox = null!;
|
||||
private OsuSpriteText valueLabel = null!;
|
||||
private InnerSlider slider = null!;
|
||||
private FormFieldCaption captionText = null!;
|
||||
private IFocusManager focusManager = null!;
|
||||
|
||||
@@ -133,113 +134,39 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
private readonly Bindable<Language> currentLanguage = new Bindable<Language>();
|
||||
|
||||
public bool TakeFocus() => GetContainingFocusManager()?.ChangeFocus(textBox) == true;
|
||||
|
||||
public FormSliderBar()
|
||||
{
|
||||
LabelFormat ??= defaultLabelFormat;
|
||||
TooltipFormat ??= v => LabelFormat(v);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours, OsuGame? game)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = 50;
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
// the reason why this slider is created in constructor rather than in BDL like the rest of drawable hierarchy is as follows:
|
||||
// `SliderBar<T>` (the base framework class for all sliders) also does its `Current` initialisation in its ctor.
|
||||
// if that precedent is not followed, it is possible to run into a crippling issue
|
||||
// when a `FormSliderBar` instance is on a screen and said screen is exited before said instance's `LoadComplete()` is invoked.
|
||||
// in that case, the screen exit will unbind the `InnerSlider`'s internal bindings & value change callbacks:
|
||||
// https://github.com/ppy/osu-framework/blob/23ac694fa2c342ce39f563c8a1b975119249d5e9/osu.Framework/Screens/ScreenStack.cs#L353
|
||||
// the callbacks are supposed to propagate `{Min,Max}Value` from `Current` to its internal `currentNumberInstantaneous` bindable:
|
||||
// https://github.com/ppy/osu-framework/blob/64624795b0816261dfc5e930e1d9b9ec7e8bb8c5/osu.Framework/Graphics/UserInterface/SliderBar.cs#L62-L63
|
||||
// thus, the callbacks getting unbound by the screen exit prevents `{Min,Max}Value` from ever correctly propagating, which finally causes a crash at
|
||||
// https://github.com/ppy/osu-framework/blob/64624795b0816261dfc5e930e1d9b9ec7e8bb8c5/osu.Framework/Graphics/UserInterface/SliderBar.cs#L112 ->
|
||||
// https://github.com/ppy/osu-framework/blob/64624795b0816261dfc5e930e1d9b9ec7e8bb8c5/osu.Framework/Graphics/UserInterface/SliderBar.cs#L88-L92.
|
||||
// moving the slider creation & binding to constructor does little to fix the issue other than to make it less likely to be hit.
|
||||
slider = new InnerSlider
|
||||
{
|
||||
background = new Box
|
||||
Current = currentNumberInstantaneous,
|
||||
OnCommit = () => current.Value = currentNumberInstantaneous.Value,
|
||||
TooltipFormat = TooltipFormat,
|
||||
DisplayAsPercentage = DisplayAsPercentage,
|
||||
PlaySamplesOnAdjust = PlaySamplesOnAdjust,
|
||||
ResetToDefault = () =>
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background5,
|
||||
},
|
||||
flashLayer = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Colour4.Transparent,
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Vertical = 9,
|
||||
Left = 9,
|
||||
Right = 5,
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
captionText = new FormFieldCaption
|
||||
{
|
||||
Anchor = Anchor.TopLeft,
|
||||
Origin = Anchor.TopLeft,
|
||||
TooltipText = HintText,
|
||||
},
|
||||
textBox = new FormNumberBox.InnerNumberBox(allowDecimals: true)
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.5f,
|
||||
// the textbox is hidden when the control is unfocused,
|
||||
// but clicking on the label should reach the textbox,
|
||||
// therefore make it always present.
|
||||
AlwaysPresent = true,
|
||||
CommitOnFocusLost = true,
|
||||
SelectAllOnFocus = true,
|
||||
OnInputError = () =>
|
||||
{
|
||||
flashLayer.Colour = ColourInfo.GradientVertical(colours.Red3.Opacity(0), colours.Red3);
|
||||
flashLayer.FadeOutFromOne(200, Easing.OutQuint);
|
||||
},
|
||||
TabbableContentContainer = tabbableContentContainer,
|
||||
},
|
||||
valueLabel = new TruncatingSpriteText
|
||||
{
|
||||
Anchor = Anchor.BottomLeft,
|
||||
Origin = Anchor.BottomLeft,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.5f,
|
||||
Padding = new MarginPadding { Right = 5 },
|
||||
},
|
||||
slider = new InnerSlider
|
||||
{
|
||||
Anchor = Anchor.CentreRight,
|
||||
Origin = Anchor.CentreRight,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Width = 0.5f,
|
||||
Current = currentNumberInstantaneous,
|
||||
OnCommit = () => current.Value = currentNumberInstantaneous.Value,
|
||||
TooltipFormat = TooltipFormat,
|
||||
DisplayAsPercentage = DisplayAsPercentage,
|
||||
PlaySamplesOnAdjust = PlaySamplesOnAdjust,
|
||||
}
|
||||
},
|
||||
},
|
||||
if (!IsDisabled)
|
||||
SetDefault();
|
||||
}
|
||||
};
|
||||
|
||||
if (game != null)
|
||||
currentLanguage.BindTo(game.CurrentLanguage);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
slider.KeyboardStep = keyboardStep;
|
||||
captionText.Caption = caption;
|
||||
|
||||
focusManager = GetContainingFocusManager()!;
|
||||
|
||||
textBox.Focused.BindValueChanged(_ => updateState());
|
||||
textBox.OnCommit += textCommitted;
|
||||
textBox.Current.BindValueChanged(textChanged);
|
||||
|
||||
slider.IsDragging.BindValueChanged(_ => updateState());
|
||||
slider.Focused.BindValueChanged(_ => updateState());
|
||||
|
||||
current.ValueChanged += e =>
|
||||
{
|
||||
currentNumberInstantaneous.Value = e.NewValue;
|
||||
@@ -258,10 +185,122 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
}
|
||||
|
||||
currentNumberInstantaneous.Disabled = disabled;
|
||||
updateState();
|
||||
if (IsLoaded)
|
||||
updateState();
|
||||
};
|
||||
|
||||
current.CopyTo(currentNumberInstantaneous);
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load(OsuColour colours, OsuGame? game)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
CornerExponent = 2.5f;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
background = new FormControlBackground(),
|
||||
flashLayer = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Colour4.Transparent,
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Vertical = 5,
|
||||
Left = 9,
|
||||
Right = 5,
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(0f, 4f),
|
||||
Width = 0.5f,
|
||||
Padding = new MarginPadding
|
||||
{
|
||||
Right = 10,
|
||||
Vertical = 4,
|
||||
},
|
||||
Children = new Drawable[]
|
||||
{
|
||||
captionText = new FormFieldCaption
|
||||
{
|
||||
TooltipText = HintText,
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
textBox = new FormNumberBox.InnerNumberBox(allowDecimals: true)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
// the textbox is hidden when the control is unfocused,
|
||||
// but clicking on the label should reach the textbox,
|
||||
// therefore make it always present.
|
||||
AlwaysPresent = true,
|
||||
CommitOnFocusLost = true,
|
||||
SelectAllOnFocus = true,
|
||||
OnInputError = () =>
|
||||
{
|
||||
flashLayer.Colour = ColourInfo.GradientVertical(colours.Red3.Opacity(0), colours.Red3);
|
||||
flashLayer.FadeOutFromOne(200, Easing.OutQuint);
|
||||
},
|
||||
TabbableContentContainer = tabbableContentContainer,
|
||||
},
|
||||
valueLabel = new TruncatingSpriteText
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
Padding = new MarginPadding { Right = 5 },
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
slider.With(s =>
|
||||
{
|
||||
s.Anchor = Anchor.CentreRight;
|
||||
s.Origin = Anchor.CentreRight;
|
||||
s.RelativeSizeAxes = Axes.X;
|
||||
s.Width = 0.5f;
|
||||
})
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
if (game != null)
|
||||
currentLanguage.BindTo(game.CurrentLanguage);
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
captionText.Caption = caption;
|
||||
|
||||
focusManager = GetContainingFocusManager()!;
|
||||
|
||||
textBox.Focused.BindValueChanged(_ => updateState());
|
||||
textBox.OnCommit += textCommitted;
|
||||
textBox.Current.BindValueChanged(textChanged);
|
||||
|
||||
slider.IsDragging.BindValueChanged(_ => updateState());
|
||||
slider.Focused.BindValueChanged(_ => updateState());
|
||||
|
||||
currentLanguage.BindValueChanged(_ => Schedule(updateValueDisplay));
|
||||
currentNumberInstantaneous.BindDisabledChanged(_ => updateState());
|
||||
currentNumberInstantaneous.BindValueChanged(e =>
|
||||
@@ -288,8 +327,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
currentNumberInstantaneous.TriggerChange();
|
||||
current.Value = currentNumberInstantaneous.Value;
|
||||
|
||||
flashLayer.Colour = ColourInfo.GradientVertical(colourProvider.Dark2.Opacity(0), colourProvider.Dark2);
|
||||
flashLayer.FadeOutFromOne(800, Easing.OutQuint);
|
||||
background.Flash();
|
||||
}
|
||||
|
||||
private void tryUpdateSliderFromTextBox()
|
||||
@@ -305,7 +343,11 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
break;
|
||||
|
||||
case Bindable<double> bindableDouble:
|
||||
bindableDouble.Value = double.Parse(textBox.Current.Value);
|
||||
bindableDouble.Value = double.Parse(textBox.Current.Value) / (DisplayAsPercentage ? 100 : 1);
|
||||
break;
|
||||
|
||||
case Bindable<float> bindableFloat:
|
||||
bindableFloat.Value = float.Parse(textBox.Current.Value) / (DisplayAsPercentage ? 100 : 1);
|
||||
break;
|
||||
|
||||
default:
|
||||
@@ -353,38 +395,59 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
textBox.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Background1 : colourProvider.Content1;
|
||||
valueLabel.Colour = currentNumberInstantaneous.Disabled ? colourProvider.Background1 : colourProvider.Content1;
|
||||
|
||||
BorderThickness = childHasFocus || IsHovered || slider.IsDragging.Value ? 2 : 0;
|
||||
|
||||
if (Current.Disabled)
|
||||
BorderColour = colourProvider.Dark1;
|
||||
else
|
||||
BorderColour = childHasFocus ? colourProvider.Highlight1 : colourProvider.Light4;
|
||||
|
||||
if (childHasFocus)
|
||||
background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark3);
|
||||
background.VisualStyle = VisualStyle.Disabled;
|
||||
else if (childHasFocus)
|
||||
background.VisualStyle = VisualStyle.Focused;
|
||||
else if (IsHovered || slider.IsDragging.Value)
|
||||
background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4);
|
||||
background.VisualStyle = VisualStyle.Hovered;
|
||||
else
|
||||
background.Colour = colourProvider.Background5;
|
||||
background.VisualStyle = VisualStyle.Normal;
|
||||
}
|
||||
|
||||
private void updateValueDisplay()
|
||||
{
|
||||
if (updatingFromTextBox) return;
|
||||
|
||||
textBox.Text = currentNumberInstantaneous.Value.ToStandardFormattedString(OsuSliderBar<T>.MAX_DECIMAL_DIGITS);
|
||||
if (DisplayAsPercentage)
|
||||
{
|
||||
double floatValue = double.CreateTruncating(currentNumberInstantaneous.Value);
|
||||
|
||||
// if `DisplayAsPercentage` is true and `T` is not `int`, then `Current` / `currentNumberInstantaneous` are in the range of [0,1].
|
||||
// in the text box, we want to show the percentage in the range of [0,100], but without the percentage sign.
|
||||
// the reason we don't want a percentage sign is that `TextBox`es with numerical `TextInputType`s
|
||||
// have framework-side limitations on which characters they accept and they won't accept a percentage sign.
|
||||
//
|
||||
// therefore, the instantaneous value needs to be multiplied by 100 if it's not `int`, so that `ToStandardFormattedString()`,
|
||||
// which is called *intentionally* without `asPercentage: true` specified as to not emit the percentage sign, spits out the correct number.
|
||||
//
|
||||
// additionally note that `ToStandardFormattedString()`, when called with `asPercentage: true` specified, does the *inverse* of this,
|
||||
// which is that it brings the formatted number *into* the [0,1] range,
|
||||
// because .NET number formatting *automatically* multiplies the formatted number by 100 when it is told to stringify a number as percentage
|
||||
// (https://learn.microsoft.com/en-us/dotnet/standard/base-types/custom-numeric-format-strings#the--custom-specifier-3).
|
||||
// it's all very confusing.
|
||||
if (currentNumberInstantaneous.Value is not int)
|
||||
floatValue *= 100;
|
||||
|
||||
textBox.Text = floatValue.ToStandardFormattedString(Math.Max(0, OsuSliderBar<T>.MAX_DECIMAL_DIGITS - 2));
|
||||
}
|
||||
else
|
||||
textBox.Text = currentNumberInstantaneous.Value.ToStandardFormattedString(OsuSliderBar<T>.MAX_DECIMAL_DIGITS);
|
||||
|
||||
valueLabel.Text = LabelFormat(currentNumberInstantaneous.Value);
|
||||
}
|
||||
|
||||
private LocalisableString defaultLabelFormat(T value) => currentNumberInstantaneous.Value.ToStandardFormattedString(OsuSliderBar<T>.MAX_DECIMAL_DIGITS, DisplayAsPercentage);
|
||||
|
||||
private partial class InnerSlider : OsuSliderBar<T>
|
||||
public partial class InnerSlider : OsuSliderBar<T>
|
||||
{
|
||||
public BindableBool Focused { get; } = new BindableBool();
|
||||
|
||||
public BindableBool IsDragging { get; set; } = new BindableBool();
|
||||
public BindableBool IsDragging { get; } = new BindableBool();
|
||||
|
||||
public Action? OnCommit { get; set; }
|
||||
public Action? ResetToDefault { get; init; }
|
||||
|
||||
public Action? OnCommit { get; init; }
|
||||
|
||||
public sealed override LocalisableString TooltipText => base.TooltipText;
|
||||
|
||||
@@ -393,7 +456,6 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
private Box leftBox = null!;
|
||||
private Box rightBox = null!;
|
||||
private InnerSliderNub nub = null!;
|
||||
private HoverClickSounds sounds = null!;
|
||||
public const float NUB_WIDTH = 10;
|
||||
|
||||
[Resolved]
|
||||
@@ -435,21 +497,18 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
Padding = new MarginPadding { Horizontal = RangePadding, },
|
||||
Child = nub = new InnerSliderNub
|
||||
{
|
||||
ResetToDefault = () =>
|
||||
{
|
||||
if (!Current.Disabled)
|
||||
Current.SetDefault();
|
||||
}
|
||||
ResetToDefault = ResetToDefault,
|
||||
}
|
||||
},
|
||||
sounds = new HoverClickSounds()
|
||||
};
|
||||
}
|
||||
|
||||
protected override void LoadComplete()
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Current.BindDisabledChanged(_ => updateState(), true);
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
protected override void UpdateAfterChildren()
|
||||
@@ -502,24 +561,29 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
sounds.Enabled.Value = !Current.Disabled;
|
||||
rightBox.Colour = colourProvider.Background6;
|
||||
rightBox.Colour = colourProvider.Background5;
|
||||
|
||||
Color4 leftColour = colourProvider.Light4;
|
||||
Color4 nubColour;
|
||||
|
||||
if (IsHovered || HasFocus || IsDragged)
|
||||
nubColour = colourProvider.Highlight1;
|
||||
else
|
||||
nubColour = colourProvider.Highlight1.Darken(0.1f);
|
||||
|
||||
if (Current.Disabled)
|
||||
{
|
||||
leftBox.Colour = colourProvider.Dark3;
|
||||
nub.Colour = colourProvider.Dark1;
|
||||
}
|
||||
else
|
||||
{
|
||||
leftBox.Colour = HasFocus || IsHovered || IsDragged ? colourProvider.Highlight1.Opacity(0.5f) : colourProvider.Highlight1.Opacity(0.3f);
|
||||
nub.Colour = HasFocus || IsHovered || IsDragged ? colourProvider.Highlight1 : colourProvider.Light4;
|
||||
nubColour = nubColour.Darken(0.4f);
|
||||
leftColour = leftColour.Darken(0.4f);
|
||||
}
|
||||
|
||||
leftBox.FadeColour(leftColour, 250, Easing.OutQuint);
|
||||
nub.FadeColour(nubColour, 250, Easing.OutQuint);
|
||||
}
|
||||
|
||||
protected override void UpdateValue(float value)
|
||||
{
|
||||
nub.MoveToX(value, 200, Easing.OutPow10);
|
||||
nub.MoveToX(value, 250, Easing.OutElasticQuarter);
|
||||
}
|
||||
|
||||
protected override bool Commit()
|
||||
@@ -535,13 +599,14 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
protected sealed override LocalisableString GetTooltipText(T value) => TooltipFormat(value);
|
||||
}
|
||||
|
||||
private partial class InnerSliderNub : Circle
|
||||
public partial class InnerSliderNub : Circle
|
||||
{
|
||||
public Action? ResetToDefault { get; set; }
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
CornerExponent = 2.5f;
|
||||
Width = InnerSlider.NUB_WIDTH;
|
||||
RelativeSizeAxes = Axes.Y;
|
||||
RelativePositionAxes = Axes.X;
|
||||
|
||||
@@ -19,6 +19,7 @@ using osu.Framework.Input.Events;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Graphics.UserInterface;
|
||||
using osu.Game.Overlays;
|
||||
using osuTK;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
@@ -76,7 +77,7 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
/// </summary>
|
||||
public LocalisableString PlaceholderText { get; init; }
|
||||
|
||||
private Box background = null!;
|
||||
private FormControlBackground background = null!;
|
||||
private Box flashLayer = null!;
|
||||
private InnerTextBox textBox = null!;
|
||||
private FormFieldCaption caption = null!;
|
||||
@@ -89,28 +90,22 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
private void load(OsuColour colours)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
Height = 50;
|
||||
|
||||
Masking = true;
|
||||
CornerRadius = 5;
|
||||
CornerExponent = 2.5f;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
background = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = colourProvider.Background5,
|
||||
},
|
||||
background = new FormControlBackground(),
|
||||
flashLayer = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Colour = Colour4.Transparent,
|
||||
},
|
||||
new Container
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Padding = new MarginPadding(9),
|
||||
Spacing = new Vector2(0, 4),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
caption = new FormFieldCaption
|
||||
@@ -122,8 +117,6 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
},
|
||||
textBox = CreateTextBox().With(t =>
|
||||
{
|
||||
t.Anchor = Anchor.BottomRight;
|
||||
t.Origin = Anchor.BottomRight;
|
||||
t.RelativeSizeAxes = Axes.X;
|
||||
t.Width = 1;
|
||||
t.PlaceholderText = PlaceholderText;
|
||||
@@ -192,19 +185,14 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
caption.Colour = disabled ? colourProvider.Background1 : colourProvider.Content2;
|
||||
textBox.Colour = disabled ? colourProvider.Foreground1 : colourProvider.Content1;
|
||||
|
||||
BorderThickness = IsHovered || textBox.Focused.Value ? 2 : 0;
|
||||
|
||||
if (disabled)
|
||||
BorderColour = colourProvider.Dark1;
|
||||
else
|
||||
BorderColour = textBox.Focused.Value ? colourProvider.Highlight1 : colourProvider.Light4;
|
||||
|
||||
if (textBox.Focused.Value)
|
||||
background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark3);
|
||||
if (Current.Disabled)
|
||||
background.VisualStyle = VisualStyle.Disabled;
|
||||
else if (textBox.Focused.Value)
|
||||
background.VisualStyle = VisualStyle.Focused;
|
||||
else if (IsHovered)
|
||||
background.Colour = ColourInfo.GradientVertical(colourProvider.Background5, colourProvider.Dark4);
|
||||
background.VisualStyle = VisualStyle.Hovered;
|
||||
else
|
||||
background.Colour = colourProvider.Background5;
|
||||
background.VisualStyle = VisualStyle.Normal;
|
||||
}
|
||||
|
||||
internal partial class InnerTextBox : OsuTextBox
|
||||
@@ -215,12 +203,16 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
|
||||
protected override float LeftRightPadding => 0;
|
||||
|
||||
public InnerTextBox()
|
||||
{
|
||||
DrawBorder = false;
|
||||
}
|
||||
|
||||
[BackgroundDependencyLoader]
|
||||
private void load()
|
||||
{
|
||||
Height = 16;
|
||||
TextContainer.Height = 1;
|
||||
Masking = false;
|
||||
BackgroundUnfocused = BackgroundFocused = BackgroundCommit = Colour4.Transparent;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,151 +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.Numerics;
|
||||
using System.Globalization;
|
||||
using osu.Framework.Bindables;
|
||||
using osu.Framework.Graphics;
|
||||
using osu.Framework.Graphics.Containers;
|
||||
using osu.Framework.Graphics.UserInterface;
|
||||
using osu.Framework.Localisation;
|
||||
using osu.Game.Overlays.Settings;
|
||||
using osu.Game.Utils;
|
||||
using Vector2 = osuTK.Vector2;
|
||||
|
||||
namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
public partial class SliderWithTextBoxInput<T> : CompositeDrawable, IHasCurrentValue<T>
|
||||
where T : struct, INumber<T>, IMinMaxValue<T>
|
||||
{
|
||||
/// <summary>
|
||||
/// A custom step value for each key press which actuates a change on this control.
|
||||
/// </summary>
|
||||
public float KeyboardStep
|
||||
{
|
||||
get => slider.KeyboardStep;
|
||||
set => slider.KeyboardStep = value;
|
||||
}
|
||||
|
||||
public Bindable<T> Current
|
||||
{
|
||||
get => slider.Current;
|
||||
set => slider.Current = value;
|
||||
}
|
||||
|
||||
public CompositeDrawable TabbableContentContainer
|
||||
{
|
||||
set => textBox.TabbableContentContainer = value;
|
||||
}
|
||||
|
||||
private bool instantaneous;
|
||||
|
||||
/// <summary>
|
||||
/// Whether changes to the slider should instantaneously transfer to the text box (and vice versa).
|
||||
/// If <see langword="false"/>, the transfer will happen on text box commit (explicit, or implicit via focus loss), or on slider drag end.
|
||||
/// </summary>
|
||||
public bool Instantaneous
|
||||
{
|
||||
get => instantaneous;
|
||||
set
|
||||
{
|
||||
instantaneous = value;
|
||||
slider.TransferValueOnCommit = !instantaneous;
|
||||
}
|
||||
}
|
||||
|
||||
private readonly SettingsSlider<T> slider;
|
||||
private readonly LabelledTextBox textBox;
|
||||
|
||||
public SliderWithTextBoxInput(LocalisableString labelText)
|
||||
{
|
||||
RelativeSizeAxes = Axes.X;
|
||||
AutoSizeAxes = Axes.Y;
|
||||
|
||||
InternalChildren = new Drawable[]
|
||||
{
|
||||
new FillFlowContainer
|
||||
{
|
||||
RelativeSizeAxes = Axes.X,
|
||||
AutoSizeAxes = Axes.Y,
|
||||
Direction = FillDirection.Vertical,
|
||||
Spacing = new Vector2(20),
|
||||
Children = new Drawable[]
|
||||
{
|
||||
textBox = new LabelledTextBox
|
||||
{
|
||||
Label = labelText,
|
||||
SelectAllOnFocus = true,
|
||||
},
|
||||
slider = new SettingsSlider<T>
|
||||
{
|
||||
TransferValueOnCommit = true,
|
||||
RelativeSizeAxes = Axes.X,
|
||||
}
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
textBox.OnCommit += textCommitted;
|
||||
textBox.Current.BindValueChanged(textChanged);
|
||||
|
||||
Current.BindValueChanged(updateTextBoxFromSlider, true);
|
||||
}
|
||||
|
||||
public bool TakeFocus() => GetContainingFocusManager()?.ChangeFocus(textBox) == true;
|
||||
|
||||
private bool updatingFromTextBox;
|
||||
|
||||
private void textChanged(ValueChangedEvent<string> change)
|
||||
{
|
||||
if (!instantaneous) return;
|
||||
|
||||
tryUpdateSliderFromTextBox();
|
||||
}
|
||||
|
||||
private void textCommitted(TextBox t, bool isNew)
|
||||
{
|
||||
tryUpdateSliderFromTextBox();
|
||||
|
||||
// If the attempted update above failed, restore text box to match the slider.
|
||||
Current.TriggerChange();
|
||||
}
|
||||
|
||||
private void tryUpdateSliderFromTextBox()
|
||||
{
|
||||
updatingFromTextBox = true;
|
||||
|
||||
try
|
||||
{
|
||||
switch (slider.Current)
|
||||
{
|
||||
case Bindable<int> bindableInt:
|
||||
bindableInt.Value = int.Parse(textBox.Current.Value);
|
||||
break;
|
||||
|
||||
case Bindable<double> bindableDouble:
|
||||
bindableDouble.Value = double.Parse(textBox.Current.Value);
|
||||
break;
|
||||
|
||||
default:
|
||||
slider.Current.Parse(textBox.Current.Value, CultureInfo.CurrentCulture);
|
||||
break;
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignore parsing failures.
|
||||
// sane state will eventually be restored by a commit (either explicit, or implicit via focus loss).
|
||||
}
|
||||
|
||||
updatingFromTextBox = false;
|
||||
}
|
||||
|
||||
private void updateTextBoxFromSlider(ValueChangedEvent<T> _)
|
||||
{
|
||||
if (updatingFromTextBox) return;
|
||||
|
||||
decimal decimalValue = decimal.CreateTruncating(slider.Current.Value);
|
||||
textBox.Text = decimalValue.ToString($@"N{FormatUtils.FindPrecision(decimalValue)}");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -19,53 +19,39 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
public partial class SwitchButton : Checkbox
|
||||
{
|
||||
private const float border_thickness = 4.5f;
|
||||
private const float padding = 1.25f;
|
||||
public const float WIDTH = 56;
|
||||
|
||||
private readonly Box fill;
|
||||
private readonly Container nubContainer;
|
||||
private readonly Drawable nub;
|
||||
private readonly CircularContainer content;
|
||||
private readonly Container content;
|
||||
|
||||
[Resolved]
|
||||
private OverlayColourProvider colourProvider { get; set; } = null!;
|
||||
|
||||
public bool ExpandOnCurrent { get; init; } = true;
|
||||
|
||||
private Sample? sampleChecked;
|
||||
private Sample? sampleUnchecked;
|
||||
|
||||
public SwitchButton()
|
||||
{
|
||||
Size = new Vector2(45, 20);
|
||||
Size = new Vector2(WIDTH, 16);
|
||||
|
||||
InternalChild = content = new CircularContainer
|
||||
{
|
||||
Anchor = Anchor.Centre,
|
||||
Origin = Anchor.Centre,
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
BorderColour = Color4.White,
|
||||
BorderThickness = border_thickness,
|
||||
BorderThickness = 3.2f,
|
||||
Masking = true,
|
||||
CornerExponent = 2.5f,
|
||||
Children = new Drawable[]
|
||||
{
|
||||
fill = new Box
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
AlwaysPresent = true,
|
||||
Alpha = 0
|
||||
},
|
||||
new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Padding = new MarginPadding(border_thickness + padding),
|
||||
Child = nubContainer = new Container
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
Child = nub = new Circle
|
||||
{
|
||||
RelativeSizeAxes = Axes.Both,
|
||||
FillMode = FillMode.Fit,
|
||||
Masking = true,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -81,71 +67,65 @@ namespace osu.Game.Graphics.UserInterfaceV2
|
||||
{
|
||||
base.LoadComplete();
|
||||
|
||||
Current.BindDisabledChanged(_ => updateColours());
|
||||
Current.BindDisabledChanged(_ => updateState());
|
||||
Current.BindValueChanged(_ => updateState(), true);
|
||||
|
||||
FinishTransforms(true);
|
||||
}
|
||||
|
||||
private void updateState()
|
||||
{
|
||||
nub.MoveToX(Current.Value ? nubContainer.DrawWidth - nub.DrawWidth : 0, 200, Easing.OutQuint);
|
||||
fill.FadeTo(Current.Value ? 1 : 0, 250, Easing.OutQuint);
|
||||
|
||||
updateColours();
|
||||
}
|
||||
|
||||
protected override bool OnHover(HoverEvent e)
|
||||
{
|
||||
updateColours();
|
||||
updateState();
|
||||
return base.OnHover(e);
|
||||
}
|
||||
|
||||
protected override void OnHoverLost(HoverLostEvent e)
|
||||
{
|
||||
updateColours();
|
||||
updateState();
|
||||
base.OnHoverLost(e);
|
||||
}
|
||||
|
||||
protected override void OnUserChange(bool value)
|
||||
{
|
||||
base.OnUserChange(value);
|
||||
PlaySample(value);
|
||||
}
|
||||
|
||||
public void PlaySample(bool value)
|
||||
{
|
||||
if (value)
|
||||
sampleChecked?.Play();
|
||||
else
|
||||
sampleUnchecked?.Play();
|
||||
}
|
||||
|
||||
private void updateColours()
|
||||
private void updateState()
|
||||
{
|
||||
ColourInfo borderColour;
|
||||
ColourInfo switchColour;
|
||||
Color4 fillColour = colourProvider.Background5.Opacity(0);
|
||||
Color4 borderColour = colourProvider.Light4;
|
||||
|
||||
if (IsHovered)
|
||||
borderColour = colourProvider.Highlight1;
|
||||
else if (Current.Value)
|
||||
borderColour = colourProvider.Highlight1.Darken(0.1f);
|
||||
|
||||
if (Current.Value)
|
||||
fillColour = borderColour;
|
||||
|
||||
if (Current.Disabled)
|
||||
{
|
||||
borderColour = colourProvider.Dark2;
|
||||
switchColour = colourProvider.Dark1;
|
||||
fill.Colour = colourProvider.Dark5;
|
||||
fillColour = fillColour.Darken(0.4f);
|
||||
borderColour = borderColour.Darken(0.4f);
|
||||
}
|
||||
|
||||
fill.FadeColour(fillColour, 250, Easing.OutQuint);
|
||||
|
||||
content.TransformTo(nameof(BorderColour), (ColourInfo)borderColour, 250, Easing.OutQuint);
|
||||
|
||||
if (ExpandOnCurrent && Current.Value)
|
||||
content.ResizeWidthTo(1f, 200, Easing.OutElasticQuarter);
|
||||
else
|
||||
{
|
||||
bool hover = IsHovered && !Current.Disabled;
|
||||
|
||||
borderColour = hover ? colourProvider.Highlight1.Opacity(0.5f) : colourProvider.Highlight1.Opacity(0.3f);
|
||||
switchColour = hover ? colourProvider.Highlight1 : colourProvider.Light4;
|
||||
|
||||
if (!Current.Value)
|
||||
{
|
||||
borderColour = borderColour.MultiplyAlpha(0.8f);
|
||||
switchColour = switchColour.MultiplyAlpha(0.8f);
|
||||
}
|
||||
|
||||
fill.Colour = colourProvider.Background6;
|
||||
}
|
||||
|
||||
nubContainer.FadeColour(switchColour, 250, Easing.OutQuint);
|
||||
content.TransformTo(nameof(BorderColour), borderColour, 250, Easing.OutQuint);
|
||||
content.ResizeWidthTo(0.75f, 120, Easing.OutExpo);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,9 @@ namespace osu.Game.Localisation
|
||||
private const string prefix = @"osu.Game.Resources.Localisation.BindingSettings";
|
||||
|
||||
/// <summary>
|
||||
/// "Shortcut and gameplay bindings"
|
||||
/// "Shortcuts and gameplay bindings"
|
||||
/// </summary>
|
||||
public static LocalisableString ShortcutAndGameplayBindings => new TranslatableString(getKey(@"shortcut_and_gameplay_bindings"), @"Shortcut and gameplay bindings");
|
||||
public static LocalisableString ShortcutAndGameplayBindings => new TranslatableString(getKey(@"shortcut_and_gameplay_bindings"), @"Shortcuts and gameplay bindings");
|
||||
|
||||
/// <summary>
|
||||
/// "Configure"
|
||||
|
||||
@@ -120,9 +120,9 @@ namespace osu.Game.Localisation
|
||||
public static LocalisableString ModsHeader => new TranslatableString(getKey(@"mods_header"), @"Mods");
|
||||
|
||||
/// <summary>
|
||||
/// "Increase visibility of first object when visual impairment mods are enabled"
|
||||
/// "Increase first object visibility on visual impairment mods"
|
||||
/// </summary>
|
||||
public static LocalisableString IncreaseFirstObjectVisibility => new TranslatableString(getKey(@"increase_first_object_visibility"), @"Increase visibility of first object when visual impairment mods are enabled");
|
||||
public static LocalisableString IncreaseFirstObjectVisibility => new TranslatableString(getKey(@"increase_first_object_visibility"), @"Increase first object visibility on visual impairment mods");
|
||||
|
||||
/// <summary>
|
||||
/// "Hide during gameplay"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user