1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-26 10:10:19 +08:00

Compare commits

...

349 Commits

426 changed files with 9670 additions and 4296 deletions
@@ -0,0 +1,20 @@
name: Copy labels from linked issues
on:
pull_request:
types: [opened]
permissions:
issues: write
pull-requests: write
contents: read
jobs:
copy-labels:
runs-on: ubuntu-latest
name: Copy labels from linked issues
steps:
- name: Copy labels
uses: michalvankodev/copy-issue-labels@v1.3.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
+1 -3
View File
@@ -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
View File
@@ -10,7 +10,7 @@
<EmbedAssembliesIntoApk>true</EmbedAssembliesIntoApk>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="ppy.osu.Framework.Android" Version="2026.108.0" />
<PackageReference Include="ppy.osu.Framework.Android" Version="2026.303.0" />
</ItemGroup>
<PropertyGroup>
<!-- Fody does not handle Android build well, and warns when unchanged.
+7 -3
View File
@@ -146,9 +146,13 @@ namespace osu.Desktop
{
base.SetHost(host);
var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico");
if (iconStream != null)
host.Window.SetIconFromStream(iconStream);
// Apple operating systems use a better icon provided via external assets.
if (!RuntimeInfo.IsApple)
{
var iconStream = Assembly.GetExecutingAssembly().GetManifestResourceStream(GetType(), "lazer.ico");
if (iconStream != null)
host.Window.SetIconFromStream(iconStream);
}
host.Window.Title = Name;
}
@@ -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]
+1 -3
View File
@@ -35,11 +35,9 @@
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
</array>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/AppIcon.appiconset</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
</dict>
</plist>
</plist>
+6 -1
View File
@@ -176,15 +176,20 @@ namespace osu.Game.Rulesets.Catch
public override Drawable CreateIcon() => new SpriteIcon { Icon = OsuIcon.RulesetCatch };
protected override IEnumerable<HitResult> GetValidHitResults()
public override IEnumerable<HitResult> GetValidHitResults()
{
return new[]
{
HitResult.Great,
HitResult.Miss,
HitResult.LargeTickHit,
HitResult.LargeTickMiss,
HitResult.SmallTickHit,
HitResult.SmallTickMiss,
HitResult.LargeBonus,
HitResult.IgnoreHit,
HitResult.IgnoreMiss,
};
}
+1 -3
View File
@@ -35,11 +35,9 @@
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
</array>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/AppIcon.appiconset</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
</dict>
</plist>
</plist>
+5 -3
View File
@@ -383,7 +383,7 @@ namespace osu.Game.Rulesets.Mania
return (PlayfieldType)Enum.GetValues(typeof(PlayfieldType)).Cast<int>().OrderDescending().First(v => variant >= v);
}
protected override IEnumerable<HitResult> GetValidHitResults()
public override IEnumerable<HitResult> GetValidHitResults()
{
return new[]
{
@@ -392,9 +392,11 @@ namespace osu.Game.Rulesets.Mania
HitResult.Good,
HitResult.Ok,
HitResult.Meh,
HitResult.Miss,
// HitResult.SmallBonus is used for awarding perfect bonus score but is not included here as
// it would be a bit redundant to show this to the user.
HitResult.IgnoreHit,
HitResult.ComboBreak,
HitResult.IgnoreMiss,
};
}
@@ -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);
}
}
}
+1 -1
View File
@@ -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);
}
+1 -3
View File
@@ -35,11 +35,9 @@
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
</array>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/AppIcon.appiconset</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
</dict>
</plist>
</plist>
@@ -3,10 +3,13 @@
using System.Linq;
using NUnit.Framework;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Tests.Beatmaps;
@@ -30,6 +33,16 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
PathType.LINEAR,
new Vector2(100, 0),
new Vector2(100, 100)
),
createPathSegment(
PathType.PERFECT_CURVE,
new Vector2(100.009f, -50.0009f),
new Vector2(200.0089f, -100)
),
createPathSegment(
PathType.PERFECT_CURVE,
new Vector2(25, -50),
new Vector2(100, 75)
)
};
@@ -48,9 +61,13 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
[TestCase(0, 250)]
[TestCase(0, 200)]
[TestCase(1, 120)]
[TestCase(1, 80)]
public void TestSliderReversal(int pathIndex, double length)
[TestCase(1, 120, false, false)]
[TestCase(1, 80, false, false)]
[TestCase(2, 250)]
[TestCase(2, 190)]
[TestCase(3, 250)]
[TestCase(3, 190)]
public void TestSliderReversal(int pathIndex, double length, bool assertEqualDistances = true, bool assertSliderReduction = true)
{
var controlPoints = paths[pathIndex];
@@ -90,6 +107,215 @@ namespace osu.Game.Rulesets.Osu.Tests.Editor
InputManager.ReleaseKey(Key.LControl);
});
if (pathIndex == 2)
{
AddRepeatStep("Reverse slider again", () =>
{
InputManager.PressKey(Key.LControl);
InputManager.Key(Key.G);
InputManager.ReleaseKey(Key.LControl);
}, 2);
}
if (assertEqualDistances)
{
AddAssert("Middle control point has the same distance from start to end", () =>
{
var pathControlPoints = selectedSlider.Path.ControlPoints;
float middleToStart = Vector2.Distance(pathControlPoints[^2].Position, pathControlPoints[0].Position);
float middleToEnd = Vector2.Distance(pathControlPoints[^2].Position, pathControlPoints[^1].Position);
return Precision.AlmostEquals(middleToStart, middleToEnd, 1f);
});
}
AddAssert("Middle control point is not at start or end", () =>
Vector2.Distance(selectedSlider.Path.ControlPoints[^2].Position, oldStartPos) > 1 &&
Vector2.Distance(selectedSlider.Path.ControlPoints[^2].Position, oldEndPos) > 1
);
AddAssert("Slider has correct length", () =>
Precision.AlmostEquals(selectedSlider.Path.Distance, oldDistance));
AddAssert("Slider has correct start position", () =>
Vector2.Distance(selectedSlider.Position, oldEndPos) < 1);
AddAssert("Slider has correct end position", () =>
Vector2.Distance(selectedSlider.EndPosition, oldStartPos) < 1);
AddAssert("Control points have correct types", () =>
{
var newControlPointTypes = selectedSlider.Path.ControlPoints.Select(p => p.Type).ToArray();
return oldControlPointTypes.Take(newControlPointTypes.Length).SequenceEqual(newControlPointTypes);
});
if (assertSliderReduction)
{
AddStep("Move to marker", () =>
{
var marker = this.ChildrenOfType<SliderEndDragMarker>().Single();
var markerPos = (marker.ScreenSpaceDrawQuad.TopRight + marker.ScreenSpaceDrawQuad.BottomRight) / 2;
// sometimes the cursor may miss the marker's hitbox so we
// add a little offset here to be sure it lands in a clickable position.
var position = new Vector2(markerPos.X + 2f, markerPos.Y);
InputManager.MoveMouseTo(position);
});
AddStep("Click", () => InputManager.PressButton(MouseButton.Left));
AddStep("Reduce slider", () =>
{
var middleControlPoint = this.ChildrenOfType<PathControlPointPiece<Slider>>().ToArray()[^2];
InputManager.MoveMouseTo(middleControlPoint);
});
AddStep("Release click", () => InputManager.ReleaseButton(MouseButton.Left));
AddStep("Save half slider info", () =>
{
oldStartPos = selectedSlider.Position;
oldEndPos = selectedSlider.EndPosition;
oldDistance = selectedSlider.Path.Distance;
});
AddStep("Reverse slider", () =>
{
InputManager.PressKey(Key.LControl);
InputManager.Key(Key.G);
InputManager.ReleaseKey(Key.LControl);
});
AddAssert("Middle control point has the same distance from start to end", () =>
{
var pathControlPoints = selectedSlider.Path.ControlPoints;
float middleToStart = Vector2.Distance(pathControlPoints[^2].Position, pathControlPoints[0].Position);
float middleToEnd = Vector2.Distance(pathControlPoints[^2].Position, pathControlPoints[^1].Position);
return Precision.AlmostEquals(middleToStart, middleToEnd, 1f);
});
AddAssert("Middle control point is not at start or end", () =>
Vector2.Distance(selectedSlider.Path.ControlPoints[^2].Position, oldStartPos) > 1 &&
Vector2.Distance(selectedSlider.Path.ControlPoints[^2].Position, oldEndPos) > 1
);
AddAssert("Slider has correct length", () =>
Precision.AlmostEquals(selectedSlider.Path.Distance, oldDistance));
AddAssert("Slider has correct start position", () =>
Vector2.Distance(selectedSlider.Position, oldEndPos) < 1);
AddAssert("Slider has correct end position", () =>
Vector2.Distance(selectedSlider.EndPosition, oldStartPos) < 1);
AddAssert("Control points have correct types", () =>
{
var newControlPointTypes = selectedSlider.Path.ControlPoints.Select(p => p.Type).ToArray();
return oldControlPointTypes.Take(newControlPointTypes.Length).SequenceEqual(newControlPointTypes);
});
}
}
[Test]
public void TestSegmentedSliderReversal()
{
PathControlPoint[] segmentedSliderPath =
[
new PathControlPoint
{
Position = new Vector2(0, 0),
Type = PathType.PERFECT_CURVE
},
new PathControlPoint
{
Position = new Vector2(100, 150),
},
new PathControlPoint
{
Position = new Vector2(75, -50),
Type = PathType.PERFECT_CURVE
},
new PathControlPoint
{
Position = new Vector2(225, -75),
},
new PathControlPoint
{
Position = new Vector2(350, 50),
Type = PathType.PERFECT_CURVE
},
new PathControlPoint
{
Position = new Vector2(500, -75),
},
new PathControlPoint
{
Position = new Vector2(350, -120),
},
];
Vector2 oldStartPos = default;
Vector2 oldEndPos = default;
double oldDistance = default;
var oldControlPointTypes = segmentedSliderPath.Select(p => p.Type);
AddStep("Add slider", () =>
{
var slider = new Slider
{
Position = new Vector2(0, 200),
Path = new SliderPath(segmentedSliderPath)
{
ExpectedDistance = { Value = 1314 }
}
};
EditorBeatmap.Add(slider);
oldStartPos = slider.Position;
oldEndPos = slider.EndPosition;
oldDistance = slider.Path.Distance;
});
AddStep("Select slider", () =>
{
var slider = (Slider)EditorBeatmap.HitObjects[0];
EditorBeatmap.SelectedHitObjects.Add(slider);
});
AddRepeatStep("Reverse slider", () =>
{
InputManager.PressKey(Key.LControl);
InputManager.Key(Key.G);
InputManager.ReleaseKey(Key.LControl);
}, 3);
AddAssert("First arc's control is not at the slider's middle", () =>
Vector2.Distance(selectedSlider.Path.ControlPoints[^2].Position, selectedSlider.Path.PositionAt(0.5)) > 1
);
AddAssert("Last arc's control is not at the slider's middle", () =>
Vector2.Distance(selectedSlider.Path.ControlPoints[1].Position, selectedSlider.Path.PositionAt(0.5)) > 1
);
AddAssert("First arc centered middle control point", () =>
{
var pathControlPoints = selectedSlider.Path.ControlPoints;
float middleToStart = Vector2.Distance(pathControlPoints[1].Position, pathControlPoints[0].Position);
float middleToEnd = Vector2.Distance(pathControlPoints[1].Position, pathControlPoints[2].Position);
return Precision.AlmostEquals(middleToStart, middleToEnd, 1f);
});
AddAssert("Last arc centered middle control point", () =>
{
var pathControlPoints = selectedSlider.Path.ControlPoints;
float middleToStart = Vector2.Distance(pathControlPoints[^2].Position, pathControlPoints[^3].Position);
float middleToEnd = Vector2.Distance(pathControlPoints[^2].Position, pathControlPoints[^1].Position);
return Precision.AlmostEquals(middleToStart, middleToEnd, 1f);
});
AddAssert("Slider has correct length", () =>
Precision.AlmostEquals(selectedSlider.Path.Distance, oldDistance));
@@ -0,0 +1,88 @@
// 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.Game.Beatmaps;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Tests.Beatmaps;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests.Mods
{
public partial class TestSceneOsuModEasy : OsuModTestScene
{
protected override bool AllowFail => true;
[Test]
public void TestMultipleApplication()
{
bool reapplied = false;
CreateModTest(new ModTestData
{
Mods = [new OsuModEasy { Retries = { Value = 1 } }],
Autoplay = false,
CreateBeatmap = () =>
{
// do stuff to speed up fails
var b = new TestBeatmap(new OsuRuleset().RulesetInfo)
{
Difficulty = { DrainRate = 10 }
};
foreach (var ho in b.HitObjects)
ho.StartTime /= 4;
return b;
},
PassCondition = () =>
{
if (((ModEasyTestPlayer)Player).FailuresSuppressed > 0 && !reapplied)
{
try
{
foreach (var mod in Player.GameplayState.Mods.OfType<IApplicableToDifficulty>())
mod.ApplyToDifficulty(new BeatmapDifficulty());
foreach (var mod in Player.GameplayState.Mods.OfType<IApplicableToPlayer>())
mod.ApplyToPlayer(Player);
}
catch
{
// don't care if this fails. in fact a failure here is probably better than the alternative.
}
finally
{
reapplied = true;
}
}
return Player.GameplayState.HasFailed && ((ModEasyTestPlayer)Player).FailuresSuppressed <= 1;
}
});
}
protected override TestPlayer CreateModPlayer(Ruleset ruleset) => new ModEasyTestPlayer(CurrentTestData, AllowFail);
private partial class ModEasyTestPlayer : ModTestPlayer
{
public int FailuresSuppressed { get; private set; }
public ModEasyTestPlayer(ModTestData data, bool allowFail)
: base(data, allowFail)
{
}
protected override bool CheckModsAllowFailure()
{
bool failureAllowed = GameplayState.Mods.OfType<IApplicableFailOverride>().All(m => m.PerformFail());
if (!failureAllowed)
FailuresSuppressed++;
return failureAllowed;
}
}
}
}
@@ -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
});
}
}
}
@@ -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;
}
@@ -7,6 +7,7 @@ using System.Diagnostics;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Utils;
@@ -37,13 +38,16 @@ namespace osu.Game.Rulesets.Osu.Edit
[Resolved]
private IEditorChangeHandler? changeHandler { get; set; }
[Resolved]
private EditorBeatmap editorBeatmap { get; set; } = null!;
[Resolved(CanBeNull = true)]
private IDistanceSnapProvider? snapProvider { get; set; }
private BindableList<HitObject> selectedItems { get; } = new BindableList<HitObject>();
[BackgroundDependencyLoader]
private void load(EditorBeatmap editorBeatmap)
private void load()
{
selectedItems.BindTo(editorBeatmap.SelectedHitObjects);
}
@@ -53,15 +57,22 @@ namespace osu.Game.Rulesets.Osu.Edit
base.LoadComplete();
selectedItems.CollectionChanged += (_, __) => updateState();
editorBeatmap.HitObjectUpdated += hitObjectUpdated;
updateState();
}
private void hitObjectUpdated(HitObject hitObject)
{
if (selectedMovableObjects.Contains(hitObject))
updateState();
}
private void updateState()
{
var quad = GeometryUtils.GetSurroundingQuad(selectedMovableObjects);
CanScaleX.Value = quad.Width > 0;
CanScaleY.Value = quad.Height > 0;
CanScaleX.Value = Precision.DefinitelyBigger(quad.Width, 0);
CanScaleY.Value = Precision.DefinitelyBigger(quad.Height, 0);
CanScaleDiagonally.Value = CanScaleX.Value && CanScaleY.Value;
CanScaleFromPlayfieldOrigin.Value = selectedMovableObjects.Any();
IsScalingSlider.Value = selectedMovableObjects.Count() == 1 && selectedMovableObjects.First() is Slider;
@@ -339,5 +350,13 @@ namespace osu.Game.Rulesets.Osu.Edit
PathControlPointTypes = (hitObject as IHasPath)?.Path.ControlPoints.Select(p => p.Type).ToArray();
}
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (editorBeatmap.IsNotNull())
editorBeatmap.HitObjectUpdated -= hitObjectUpdated;
}
}
}
@@ -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
{
+9 -1
View File
@@ -39,7 +39,7 @@ namespace osu.Game.Rulesets.Osu.Mods
[SettingSource(
"Max size at combo",
"The combo count at which the cursor reaches its maximum size",
SettingControlType = typeof(SettingsSlider<int, RoundedSliderBar<int>>)
SettingControlType = typeof(SettingsSlider<int, MaxSizeComboSlider>)
)]
public BindableInt MaxSizeComboCount { get; } = new BindableInt(50)
{
@@ -85,4 +85,12 @@ namespace osu.Game.Rulesets.Osu.Mods
cursor.ModScaleAdjust.Value = (float)Interpolation.Lerp(cursor.ModScaleAdjust.Value, currentSize, Math.Clamp(cursor.Time.Elapsed / TRANSITION_DURATION, 0, 1));
}
}
public partial class MaxSizeComboSlider : RoundedSliderBar<int>
{
public MaxSizeComboSlider()
{
KeyboardStep = 1;
}
}
}
@@ -1,7 +1,9 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Bindables;
using osu.Framework.Localisation;
using osu.Game.Beatmaps;
@@ -71,6 +73,8 @@ namespace osu.Game.Rulesets.Osu.Mods
}
}
public override Type[] IncompatibleMods => base.IncompatibleMods.Append(typeof(OsuModTargetPractice)).ToArray();
protected override void ApplySettings(BeatmapDifficulty difficulty)
{
base.ApplySettings(difficulty);
@@ -27,8 +27,10 @@ namespace osu.Game.Rulesets.Osu.Mods
public override LocalisableString Description => "Burn the notes into your memory.";
//Alters the transforms of the approach circles, breaking the effects of these mods.
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModApproachDifferent), typeof(OsuModTransform), typeof(OsuModDepth) }).ToArray();
/// <remarks>
/// Incompatible with all mods that directly modify or indirectly depend on <see cref="OsuHitObject.TimePreempt"/>, or alter the behaviour of approach circles.
/// </remarks>
public override Type[] IncompatibleMods => base.IncompatibleMods.Concat(new[] { typeof(OsuModApproachDifferent), typeof(OsuModTransform), typeof(OsuModDepth), typeof(OsuModHidden) }).ToArray();
public override ModType Type => ModType.Fun;
@@ -57,7 +59,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>())
{
+1 -1
View File
@@ -26,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override LocalisableString Description => @"Play with no approach circles and fading circles/sliders.";
public override double ScoreMultiplier => UsesDefaultConfiguration ? 1.06 : 1;
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModDepth) };
public override Type[] IncompatibleMods => new[] { typeof(IRequiresApproachCircles), typeof(OsuModSpinIn), typeof(OsuModDepth), typeof(OsuModFreezeFrame) };
public const double FADE_IN_DURATION_MULTIPLIER = 0.4;
public const double FADE_OUT_DURATION_MULTIPLIER = 0.3;
@@ -48,7 +48,8 @@ namespace osu.Game.Rulesets.Osu.Mods
typeof(OsuModSpunOut),
typeof(OsuModStrictTracking),
typeof(OsuModSuddenDeath),
typeof(OsuModDepth)
typeof(OsuModDepth),
typeof(OsuModDifficultyAdjust),
}).ToArray();
[SettingSource("Seed", "Use a custom seed instead of a random one", SettingControlType = typeof(SettingsNumberBox))]
@@ -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;
+7 -3
View File
@@ -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(),
@@ -278,19 +277,24 @@ namespace osu.Game.Rulesets.Osu
public override IRulesetConfigManager CreateConfig(SettingsStore? settings) => new OsuRulesetConfigManager(settings, RulesetInfo);
protected override IEnumerable<HitResult> GetValidHitResults()
public override IEnumerable<HitResult> GetValidHitResults()
{
return new[]
{
HitResult.Great,
HitResult.Ok,
HitResult.Meh,
HitResult.Miss,
HitResult.LargeTickHit,
HitResult.LargeTickMiss,
HitResult.SmallTickHit,
HitResult.SmallTickMiss,
HitResult.SliderTailHit,
HitResult.SmallBonus,
HitResult.LargeBonus,
HitResult.IgnoreHit,
HitResult.IgnoreMiss,
};
}
@@ -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),
},
}),
};
}
}
+1 -3
View File
@@ -35,11 +35,9 @@
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
</array>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/AppIcon.appiconset</string>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>CADisableMinimumFrameDurationOnPhone</key>
<true/>
</dict>
</plist>
</plist>
+4 -1
View File
@@ -222,15 +222,18 @@ namespace osu.Game.Rulesets.Taiko
public override RulesetSettingsSubsection CreateSettings() => new TaikoSettingsSubsection(this);
protected override IEnumerable<HitResult> GetValidHitResults()
public override IEnumerable<HitResult> GetValidHitResults()
{
return new[]
{
HitResult.Great,
HitResult.Ok,
HitResult.Miss,
HitResult.SmallBonus,
HitResult.LargeBonus,
HitResult.IgnoreHit,
HitResult.IgnoreMiss,
};
}
@@ -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)
}
})
};
}
}
+1 -1
View File
@@ -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));
+25 -2
View File
@@ -35,8 +35,31 @@
<string>UIInterfaceOrientationLandscapeRight</string>
<string>UIInterfaceOrientationLandscapeLeft</string>
</array>
<key>XSAppIconAssets</key>
<string>Assets.xcassets/AppIcon.appiconset</string>
<key>CFBundleIcons~ipad</key>
<dict>
<key>CFBundlePrimaryIcon</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>AppIcon60x60</string>
</array>
<key>CFBundleIconName</key>
<string>AppIcon</string>
</dict>
</dict>
<key>CFBundleIcons</key>
<dict>
<key>CFBundlePrimaryIcon</key>
<dict>
<key>CFBundleIconFiles</key>
<array>
<string>AppIcon60x60</string>
<string>AppIcon76x76</string>
</array>
<key>CFBundleIconName</key>
<string>AppIcon</string>
</dict>
</dict>
<key>UIApplicationSupportsIndirectInputEvents</key>
<true/>
<key>CADisableMinimumFrameDurationOnPhone</key>
@@ -4,6 +4,7 @@
#nullable disable
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
@@ -15,6 +16,7 @@ using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Tests.Beatmaps.IO;
using osu.Game.Tests.Visual;
@@ -34,6 +36,12 @@ namespace osu.Game.Tests.Beatmaps
private IBindable<StarDifficulty> starDifficultyBindable;
[Resolved]
private BeatmapManager beatmapManager { get; set; }
[Resolved]
private BeatmapDifficultyCache actualDifficultyCache { get; set; }
[BackgroundDependencyLoader]
private void load(OsuGameBase osu)
{
@@ -55,6 +63,36 @@ namespace osu.Game.Tests.Beatmaps
AddUntilStep($"star difficulty -> {BASE_STARS}", () => starDifficultyBindable.Value.Stars == BASE_STARS);
}
[Test]
public void TestInvalidationFlow()
{
BeatmapInfo postEditBeatmapInfo = null;
BeatmapInfo preEditBeatmapInfo = null;
IBindable<StarDifficulty> bindableDifficulty = null;
AddStep("get bindable stars", () =>
{
preEditBeatmapInfo = importedSet.Beatmaps.First();
bindableDifficulty = actualDifficultyCache.GetBindableDifficulty(preEditBeatmapInfo);
});
AddUntilStep("wait for stars retrieved", () => bindableDifficulty.Value.Stars, () => Is.GreaterThan(0));
AddStep("remove all hitobjects", () =>
{
var working = beatmapManager.GetWorkingBeatmap(preEditBeatmapInfo);
((IList<HitObject>)working.Beatmap.HitObjects).Clear();
beatmapManager.Save(working.BeatmapInfo, working.Beatmap);
postEditBeatmapInfo = working.BeatmapInfo;
});
AddAssert("stars is now zero", () => actualDifficultyCache.GetDifficultyAsync(postEditBeatmapInfo).GetResultSafely()!.Value.Stars, () => Is.Zero);
AddUntilStep("bindable stars is now zero", () => bindableDifficulty.Value.Stars, () => Is.Zero);
}
[Test]
public void TestStarDifficultyChangesOnModSettings()
{
@@ -76,6 +114,30 @@ namespace osu.Game.Tests.Beatmaps
AddUntilStep($"star difficulty -> {BASE_STARS + 1.75}", () => starDifficultyBindable.Value.Stars == BASE_STARS + 1.75);
}
[Test]
public void TestStarDifficultyChangesOnModSettingsCorrectlyTrackAcrossReferenceChanges()
{
OsuModDoubleTime dt = null;
AddStep("set computation function", () => difficultyCache.ComputeDifficulty = lookup =>
{
var modRateAdjust = (ModRateAdjust)lookup.OrderedMods.SingleOrDefault(mod => mod is ModRateAdjust);
return new StarDifficulty(BASE_STARS + modRateAdjust?.SpeedChange.Value ?? 0, 0);
});
AddStep("change selected mod to DT", () => SelectedMods.Value = new[] { dt = new OsuModDoubleTime { SpeedChange = { Value = 1.5 } } });
AddUntilStep($"star difficulty -> {BASE_STARS + 1.5}", () => starDifficultyBindable.Value.Stars == BASE_STARS + 1.5);
AddStep("change DT speed to 1.25", () => dt.SpeedChange.Value = 1.25);
AddUntilStep($"star difficulty -> {BASE_STARS + 1.25}", () => starDifficultyBindable.Value.Stars == BASE_STARS + 1.25);
AddStep("reconstruct DT mod with same settings", () => SelectedMods.Value = new[] { dt = (OsuModDoubleTime)dt.DeepClone() });
AddUntilStep($"star difficulty -> {BASE_STARS + 1.25}", () => starDifficultyBindable.Value.Stars == BASE_STARS + 1.25);
AddStep("change DT speed to 1.25", () => dt.SpeedChange.Value = 2);
AddUntilStep($"star difficulty -> {BASE_STARS + 2}", () => starDifficultyBindable.Value.Stars == BASE_STARS + 2);
}
[Test]
public void TestStarDifficultyAdjustHashCodeConflict()
{
@@ -122,8 +184,10 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestKeyDoesntEqualWithDifferentModSettings()
{
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.1 } } });
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.9 } } });
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 },
new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.1 } } });
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 },
new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.9 } } });
Assert.That(key1, Is.Not.EqualTo(key2));
Assert.That(key1.GetHashCode(), Is.Not.EqualTo(key2.GetHashCode()));
@@ -132,8 +196,10 @@ namespace osu.Game.Tests.Beatmaps
[Test]
public void TestKeyEqualWithMatchingModSettings()
{
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 }, new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
var key1 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 },
new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
var key2 = new BeatmapDifficultyCache.DifficultyCacheLookup(new BeatmapInfo { ID = guid }, new RulesetInfo { OnlineID = 0 },
new Mod[] { new OsuModDoubleTime { SpeedChange = { Value = 1.25 } } });
Assert.That(key1, Is.EqualTo(key2));
Assert.That(key1.GetHashCode(), Is.EqualTo(key2.GetHashCode()));
@@ -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,6 +7,7 @@ using System.Linq;
using NUnit.Framework;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Difficulty;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
@@ -116,6 +117,69 @@ namespace osu.Game.Tests.Database
});
}
[Test]
public void TestFakedRulesetIdIsDetected()
{
RunTestWithRealm((realm, storage) =>
{
LoadTestRuleset.HasImplementations = true;
LoadTestRuleset.Version = Ruleset.CURRENT_RULESET_API_VERSION;
var ruleset = new LoadTestRuleset();
string rulesetShortName = ruleset.RulesetInfo.ShortName;
realm.Write(r => r.Add(new RulesetInfo(rulesetShortName, ruleset.RulesetInfo.Name, ruleset.RulesetInfo.InstantiationInfo, 0)
{
Available = true,
}));
Assert.That(realm.Run(r => r.Find<RulesetInfo>(rulesetShortName)!.Available), Is.True);
// Availability is updated on construction of a RealmRulesetStore
using var _ = new RealmRulesetStore(realm, storage);
Assert.That(realm.Run(r => r.Find<RulesetInfo>(rulesetShortName)!.Available), Is.False);
});
}
[Test]
public void TestMultipleRulesetWithSameOnlineIdsAreDetected()
{
RunTestWithRealm((realm, storage) =>
{
LoadTestRuleset.HasImplementations = true;
LoadTestRuleset.Version = Ruleset.CURRENT_RULESET_API_VERSION;
LoadTestRuleset.OnlineID = 2;
var first = new LoadTestRuleset();
var second = new CatchRuleset();
realm.Write(r => r.Add(new RulesetInfo(first.ShortName, first.RulesetInfo.Name, first.RulesetInfo.InstantiationInfo, first.RulesetInfo.OnlineID)
{
Available = true,
}));
realm.Write(r => r.Add(new RulesetInfo(second.ShortName, second.RulesetInfo.Name, second.RulesetInfo.InstantiationInfo, second.RulesetInfo.OnlineID)
{
Available = true,
}));
Assert.That(realm.Run(r => r.Find<RulesetInfo>(first.ShortName)!.Available), Is.True);
Assert.That(realm.Run(r => r.Find<RulesetInfo>(second.ShortName)!.Available), Is.True);
// Availability is updated on construction of a RealmRulesetStore
using var _ = new RealmRulesetStore(realm, storage);
Assert.That(realm.Run(r => r.Find<RulesetInfo>(first.ShortName)!.Available), Is.False);
Assert.That(realm.Run(r => r.Find<RulesetInfo>(second.ShortName)!.Available), Is.False);
realm.Write(r => r.Remove(r.Find<RulesetInfo>(first.ShortName)!));
using var __ = new RealmRulesetStore(realm, storage);
Assert.That(realm.Run(r => r.Find<RulesetInfo>(second.ShortName)!.Available), Is.True);
});
}
private class LoadTestRuleset : Ruleset
{
public override string RulesetAPIVersionSupported => Version;
@@ -124,6 +188,13 @@ namespace osu.Game.Tests.Database
public static string Version { get; set; } = CURRENT_RULESET_API_VERSION;
public static int OnlineID { get; set; } = -1;
public LoadTestRuleset()
{
RulesetInfo.OnlineID = OnlineID;
}
public override IEnumerable<Mod> GetModsFor(ModType type)
{
if (!HasImplementations)
@@ -526,7 +526,7 @@ namespace osu.Game.Tests.Rulesets.Scoring
// ReSharper disable once MemberHidesStaticFromOuterClass
private class TestRuleset : Ruleset
{
protected override IEnumerable<HitResult> GetValidHitResults() => new[] { HitResult.Great };
public override IEnumerable<HitResult> GetValidHitResults() => new[] { HitResult.Great };
public override IEnumerable<Mod> GetModsFor(ModType type) => throw new NotImplementedException();
@@ -1,45 +1,64 @@
// 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.Graphics;
using osu.Framework.Graphics.Containers;
using System;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Testing;
using osu.Game.Overlays;
using osu.Game.Screens;
using osu.Game.Screens.Edit.Submission;
using osu.Game.Screens.Footer;
namespace osu.Game.Tests.Visual.Editing
{
public partial class TestSceneBeatmapSubmissionOverlay : OsuTestScene
public partial class TestSceneBeatmapSubmissionOverlay : ScreenTestScene
{
private ScreenFooter footer = null!;
private TestBeatmapSubmissionOverlayScreen screen = null!;
[Cached]
private readonly BeatmapSubmissionSettings beatmapSubmissionSettings = new BeatmapSubmissionSettings();
[SetUpSteps]
public void SetUpSteps()
public override void SetUpSteps()
{
AddStep("add overlay", () =>
{
var receptor = new ScreenFooter.BackReceptor();
footer = new ScreenFooter(receptor);
base.SetUpSteps();
Child = new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both,
CachedDependencies = new[]
{
(typeof(ScreenFooter), (object)footer),
(typeof(BeatmapSubmissionSettings), new BeatmapSubmissionSettings()),
},
Children = new Drawable[]
{
receptor,
new BeatmapSubmissionOverlay
{
State = { Value = Visibility.Visible, },
},
footer,
}
};
});
AddStep("push screen", () => LoadScreen(screen = new TestBeatmapSubmissionOverlayScreen()));
AddUntilStep("wait until screen is loaded", () => screen.IsLoaded, () => Is.True);
AddStep("show overlay", () => screen.Overlay.Show());
}
private partial class TestBeatmapSubmissionOverlayScreen : OsuScreen
{
public override bool ShowFooter => true;
public BeatmapSubmissionOverlay Overlay = null!;
private IDisposable? overlayRegistration;
[Resolved]
private IOverlayManager? overlayManager { get; set; }
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
[BackgroundDependencyLoader]
private void load()
{
LoadComponent(Overlay = new BeatmapSubmissionOverlay());
}
protected override void LoadComplete()
{
base.LoadComplete();
overlayRegistration = overlayManager?.RegisterBlockingOverlay(Overlay);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
overlayRegistration?.Dispose();
}
}
}
}
@@ -132,6 +132,7 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName);
AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 }));
AddStep("add effect point", () => EditorBeatmap.ControlPointInfo.Add(500, new EffectControlPoint { KiaiMode = true }));
AddStep("add bookmarks", () => EditorBeatmap.Bookmarks.AddRange([500, 1000]));
AddStep("add hitobjects", () => EditorBeatmap.AddRange(new[]
{
new HitCircle
@@ -185,6 +186,7 @@ namespace osu.Game.Tests.Visual.Editing
var effectPoint = EditorBeatmap.ControlPointInfo.EffectPoints.Single();
return effectPoint.Time == 500 && effectPoint.KiaiMode && effectPoint.ScrollSpeedBindable.IsDefault;
});
AddAssert("created difficulty has bookmarks", () => EditorBeatmap.Bookmarks.Count == 2);
AddAssert("created difficulty has no objects", () => EditorBeatmap.HitObjects.Count == 0);
AddAssert("status is modified", () => EditorBeatmap.BeatmapInfo.Status == BeatmapOnlineStatus.LocallyModified);
@@ -223,6 +225,7 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = previousDifficultyName = Guid.NewGuid().ToString());
AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 }));
AddStep("add bookmarks", () => EditorBeatmap.Bookmarks.AddRange([500, 1000]));
AddStep("add effect points", () =>
{
EditorBeatmap.ControlPointInfo.Add(250, new EffectControlPoint { KiaiMode = false, ScrollSpeed = 0.05 });
@@ -253,6 +256,8 @@ namespace osu.Game.Tests.Visual.Editing
return timingPoint.Time == 0 && timingPoint.BeatLength == 1000;
});
AddAssert("created difficulty has bookmarks", () => EditorBeatmap.Bookmarks.Count == 2);
AddAssert("created difficulty has effect points", () =>
{
return EditorBeatmap.ControlPointInfo.EffectPoints.SequenceEqual(new[]
@@ -284,6 +289,7 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("set unique difficulty name", () => EditorBeatmap.BeatmapInfo.DifficultyName = firstDifficultyName);
AddStep("add timing point", () => EditorBeatmap.ControlPointInfo.Add(0, new TimingControlPoint { BeatLength = 1000 }));
AddStep("add bookmarks", () => EditorBeatmap.Bookmarks.AddRange([500, 1000]));
AddStep("add effect points", () =>
{
EditorBeatmap.ControlPointInfo.Add(250, new EffectControlPoint { KiaiMode = false, ScrollSpeed = 0.05 });
@@ -311,6 +317,8 @@ namespace osu.Game.Tests.Visual.Editing
return timingPoint.Time == 0 && timingPoint.BeatLength == 1000;
});
AddAssert("created difficulty has bookmarks", () => EditorBeatmap.Bookmarks.Count == 2);
AddAssert("created difficulty has effect points", () =>
{
// since this difficulty is on another ruleset, scroll speed specifications are completely reset,
@@ -344,6 +352,7 @@ namespace osu.Game.Tests.Visual.Editing
StartTime = 1000
}
}));
AddStep("add bookmarks", () => EditorBeatmap.Bookmarks.AddRange([500, 1000]));
AddStep("set approach rate", () => EditorBeatmap.Difficulty.ApproachRate = 4);
AddStep("set combo colours", () =>
{
@@ -394,6 +403,7 @@ namespace osu.Game.Tests.Visual.Editing
return timingPoint.Time == 0 && timingPoint.BeatLength == 1000;
});
AddAssert("created difficulty has objects", () => EditorBeatmap.HitObjects.Count == 2);
AddAssert("created difficulty has bookmarks", () => EditorBeatmap.Bookmarks.Count == 2);
AddAssert("approach rate correctly copied", () => EditorBeatmap.Difficulty.ApproachRate == 4);
AddAssert("combo colours correctly copied", () => EditorBeatmap.BeatmapSkin.AsNonNull().ComboColours.Count == 2);
@@ -19,6 +19,7 @@ using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Screens.Edit.Components.TernaryButtons;
using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using osu.Game.Screens.Edit.Timing;
using osu.Game.Tests.Beatmaps;
@@ -112,6 +113,77 @@ namespace osu.Game.Tests.Visual.Editing
hitObjectHasSampleBank(1, HitSampleInfo.BANK_DRUM);
}
[Test]
public void TestAutoAdditionsBankMatchesNormalBankWhenChangedViaPopover()
{
clickSamplePiece(0);
setBankViaPopover(HitSampleInfo.BANK_SOFT);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_SOFT);
toggleAdditionViaPopover(1);
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_SOFT);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT);
setBankViaPopover(HitSampleInfo.BANK_DRUM);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
setAdditionBankViaPopover(HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_NORMAL);
setAdditionBankViaPopover(EditorSelectionHandler.HIT_BANK_AUTO);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
}
[Test]
public void TestAutoAdditionsBankMatchesNormalBankWhenChangedViaHotkeys()
{
AddStep("select first object", () => EditorBeatmap.SelectedHitObjects.Add(EditorBeatmap.HitObjects[0]));
AddStep("set soft normal bank", () =>
{
InputManager.PressKey(Key.ShiftLeft);
InputManager.Key(Key.E);
InputManager.ReleaseKey(Key.ShiftLeft);
});
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_SOFT);
AddStep("toggle finish", () => InputManager.Key(Key.E));
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_SOFT);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_SOFT);
AddStep("set drum normal bank", () =>
{
InputManager.PressKey(Key.ShiftLeft);
InputManager.Key(Key.R);
InputManager.ReleaseKey(Key.ShiftLeft);
});
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
AddStep("set normal addition bank", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.W);
InputManager.ReleaseKey(Key.AltLeft);
});
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_NORMAL);
AddStep("set auto addition bank", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.Q);
InputManager.ReleaseKey(Key.AltLeft);
});
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
}
[Test]
public void TestUndo()
{
@@ -346,7 +418,7 @@ namespace osu.Game.Tests.Visual.Editing
{
for (int i = 0; i < h.Samples.Count; i++)
{
h.Samples[i] = h.Samples[i].With(newBank: HitSampleInfo.BANK_SOFT);
h.Samples[i] = h.Samples[i].With(newBank: HitSampleInfo.BANK_SOFT, newEditorAutoBank: false);
}
}
});
@@ -354,7 +426,7 @@ namespace osu.Game.Tests.Visual.Editing
AddStep("add whistle addition", () =>
{
foreach (var h in EditorBeatmap.HitObjects)
h.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, HitSampleInfo.BANK_SOFT));
h.Samples.Add(new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, HitSampleInfo.BANK_SOFT, editorAutoBank: false));
});
AddStep("select both objects", () => EditorBeatmap.SelectedHitObjects.AddRange(EditorBeatmap.HitObjects));
@@ -523,6 +595,172 @@ namespace osu.Game.Tests.Visual.Editing
() => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name != HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(expected));
}
[Test]
public void TestNonAutoBankHotkeysDuringPlacementPersistAfterPlacement()
{
AddStep("Clear all objects", () => EditorBeatmap.Clear());
AddStep("Enter placement mode", () => InputManager.Key(Key.Number2));
AddStep("Move mouse to centre", () => InputManager.MoveMouseTo(Editor.ChildrenOfType<HitObjectComposer>().First().ScreenSpaceDrawQuad.Centre));
AddStep("Move to 3000", () => EditorClock.Seek(3000));
AddStep("Press drum bank shortcut", () =>
{
InputManager.PressKey(Key.ShiftLeft);
InputManager.Key(Key.R);
InputManager.ReleaseKey(Key.ShiftLeft);
});
AddAssert($"Placement sample is {HitSampleInfo.BANK_DRUM}",
() => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name == HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(HitSampleInfo.BANK_DRUM));
AddStep("Press normal addition bank shortcut", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.W);
InputManager.ReleaseKey(Key.AltLeft);
});
AddStep("Press finish sample shortcut", () =>
{
InputManager.Key(Key.E);
});
AddAssert($"Placement sample addition is {HitSampleInfo.BANK_NORMAL}",
() => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name != HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(HitSampleInfo.BANK_NORMAL));
AddStep("Finish placement", () => InputManager.Click(MouseButton.Left));
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasAutoNormalBankFlag(0, false);
hitObjectHasAutoAdditionBankFlag(0, false);
clickSamplePiece(0);
samplePopoverIsOpen();
samplePopoverHasSingleAdditionBank(HitSampleInfo.BANK_NORMAL);
}
[Test]
public void TestAutoAdditionBankHotkeyDuringPlacementPersistsAfterPlacement()
{
AddStep("Clear all objects", () => EditorBeatmap.Clear());
AddStep("Enter placement mode", () => InputManager.Key(Key.Number2));
AddStep("Move mouse to centre", () => InputManager.MoveMouseTo(Editor.ChildrenOfType<HitObjectComposer>().First().ScreenSpaceDrawQuad.Centre));
AddStep("Move to 3000", () => EditorClock.Seek(3000));
AddStep("Press drum bank shortcut", () =>
{
InputManager.PressKey(Key.ShiftLeft);
InputManager.Key(Key.R);
InputManager.ReleaseKey(Key.ShiftLeft);
});
AddAssert($"Placement sample is {HitSampleInfo.BANK_DRUM}",
() => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name == HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(HitSampleInfo.BANK_DRUM));
AddStep("Press normal addition bank shortcut", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.W);
InputManager.ReleaseKey(Key.AltLeft);
});
AddStep("Press finish sample shortcut", () =>
{
InputManager.Key(Key.E);
});
AddStep("Press auto addition bank shortcut", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.Q);
InputManager.ReleaseKey(Key.AltLeft);
});
AddAssert($"Placement sample addition is {HitSampleInfo.BANK_DRUM}",
() => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name != HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(HitSampleInfo.BANK_DRUM));
AddStep("Finish placement", () => InputManager.Click(MouseButton.Left));
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_DRUM);
hitObjectHasAutoNormalBankFlag(0, false);
hitObjectHasAutoAdditionBankFlag(0, true);
clickSamplePiece(0);
samplePopoverIsOpen();
samplePopoverHasSingleAdditionBank(EditorSelectionHandler.HIT_BANK_AUTO);
}
[Test]
public void TestFullAutoBankHotkeyDuringPlacementPersistsAfterPlacement()
{
AddStep("Clear all objects", () => EditorBeatmap.Clear());
AddStep("Enter placement mode", () => InputManager.Key(Key.Number2));
AddStep("Move mouse to centre", () => InputManager.MoveMouseTo(Editor.ChildrenOfType<HitObjectComposer>().First().ScreenSpaceDrawQuad.Centre));
AddStep("Move to 3000", () => EditorClock.Seek(3000));
AddStep("Press auto normal bank shortcut", () =>
{
InputManager.PressKey(Key.ShiftLeft);
InputManager.Key(Key.Q);
InputManager.ReleaseKey(Key.ShiftLeft);
});
AddAssert($"Placement sample is {HitSampleInfo.BANK_NORMAL}",
() => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name == HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(HitSampleInfo.BANK_NORMAL));
AddStep("Press finish sample shortcut", () =>
{
InputManager.Key(Key.E);
});
AddStep("Press auto addition bank shortcut", () =>
{
InputManager.PressKey(Key.AltLeft);
InputManager.Key(Key.Q);
InputManager.ReleaseKey(Key.AltLeft);
});
AddAssert($"Placement sample addition is {HitSampleInfo.BANK_NORMAL}",
() => EditorBeatmap.PlacementObject.Value.Samples.First(s => s.Name != HitSampleInfo.HIT_NORMAL).Bank, () => Is.EqualTo(HitSampleInfo.BANK_NORMAL));
AddStep("Finish placement", () => InputManager.Click(MouseButton.Left));
hitObjectHasSamples(0, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH);
hitObjectHasSampleNormalBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleAdditionBank(0, HitSampleInfo.BANK_NORMAL);
hitObjectHasAutoNormalBankFlag(0, false); // it's the first object - nothing to inherit bank from
hitObjectHasAutoAdditionBankFlag(0, true);
clickSamplePiece(0);
samplePopoverIsOpen();
samplePopoverHasSingleBank(HitSampleInfo.BANK_NORMAL);
samplePopoverHasSingleAdditionBank(EditorSelectionHandler.HIT_BANK_AUTO);
dismissPopover();
AddStep("Move to 5000", () => EditorClock.Seek(5000));
AddStep("Enter placement mode", () => InputManager.Key(Key.Number2));
AddStep("Move mouse to centre", () => InputManager.MoveMouseTo(Editor.ChildrenOfType<HitObjectComposer>().First().ScreenSpaceDrawQuad.Centre));
AddStep("Finish placement", () => InputManager.Click(MouseButton.Left));
hitObjectHasSamples(1, HitSampleInfo.HIT_NORMAL, HitSampleInfo.HIT_FINISH); // finish is still implied, continuing from first placement
hitObjectHasSampleNormalBank(1, HitSampleInfo.BANK_NORMAL);
hitObjectHasSampleAdditionBank(1, HitSampleInfo.BANK_NORMAL);
hitObjectHasAutoNormalBankFlag(1, true);
hitObjectHasAutoAdditionBankFlag(1, true);
clickSamplePiece(1);
samplePopoverIsOpen();
samplePopoverHasSingleBank(HitSampleInfo.BANK_NORMAL);
samplePopoverHasSingleAdditionBank(EditorSelectionHandler.HIT_BANK_AUTO);
}
[Test]
public void PopoverForMultipleSelectionChangesAllSamples()
{
@@ -599,19 +837,19 @@ namespace osu.Game.Tests.Visual.Editing
Path = new SliderPath(new[] { new PathControlPoint(Vector2.Zero), new PathControlPoint(new Vector2(250, 0)) }),
Samples =
{
new HitSampleInfo(HitSampleInfo.HIT_NORMAL)
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, editorAutoBank: false)
},
NodeSamples = new List<IList<HitSampleInfo>>
{
new List<HitSampleInfo>
{
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_DRUM),
new HitSampleInfo(HitSampleInfo.HIT_CLAP, bank: HitSampleInfo.BANK_DRUM),
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_DRUM, editorAutoBank: false),
new HitSampleInfo(HitSampleInfo.HIT_CLAP, bank: HitSampleInfo.BANK_DRUM, editorAutoBank: false),
},
new List<HitSampleInfo>
{
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_SOFT),
new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, bank: HitSampleInfo.BANK_SOFT),
new HitSampleInfo(HitSampleInfo.HIT_NORMAL, bank: HitSampleInfo.BANK_SOFT, editorAutoBank: false),
new HitSampleInfo(HitSampleInfo.HIT_WHISTLE, bank: HitSampleInfo.BANK_SOFT, editorAutoBank: false),
},
}
});
@@ -880,6 +1118,14 @@ namespace osu.Game.Tests.Visual.Editing
return dropdown?.Current.Value == "(multiple)";
});
private void samplePopoverHasSingleAdditionBank(string bank) => AddUntilStep($"sample popover has bank {bank}", () =>
{
var popover = this.ChildrenOfType<SamplePointPiece.SampleEditPopover>().SingleOrDefault();
var dropdown = popover?.ChildrenOfType<LabelledDropdown<string>>().ElementAt(1);
return dropdown?.Current.Value == bank;
});
private void dismissPopover()
{
AddStep("dismiss popover", () => InputManager.Key(Key.Escape));
@@ -953,6 +1199,18 @@ namespace osu.Game.Tests.Visual.Editing
return h.Samples.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.Bank == bank);
});
private void hitObjectHasAutoNormalBankFlag(int objectIndex, bool autoBank) => AddAssert($"{objectIndex.ToOrdinalWords()} has auto normal bank {(autoBank ? "on" : "off")}", () =>
{
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex);
return h.Samples.Where(o => o.Name == HitSampleInfo.HIT_NORMAL).All(o => o.EditorAutoBank == autoBank);
});
private void hitObjectHasAutoAdditionBankFlag(int objectIndex, bool autoBank) => AddAssert($"{objectIndex.ToOrdinalWords()} has auto addition bank {(autoBank ? "on" : "off")}", () =>
{
var h = EditorBeatmap.HitObjects.ElementAt(objectIndex);
return h.Samples.Where(o => o.Name != HitSampleInfo.HIT_NORMAL).All(o => o.EditorAutoBank == autoBank);
});
private void hitObjectNodeHasSamples(int objectIndex, int nodeIndex, params string[] samples) => AddAssert(
$"{objectIndex.ToOrdinalWords()} object {nodeIndex.ToOrdinalWords()} node has samples {string.Join(',', samples)}", () =>
{
@@ -272,8 +272,8 @@ namespace osu.Game.Tests.Visual.Editing
AddAssert("circle has 2 samples", () => EditorBeatmap.HitObjects[1].Samples, () => Has.Count.EqualTo(2));
AddAssert("normal sample has soft bank", () => EditorBeatmap.HitObjects[1].Samples.Single(s => s.Name == HitSampleInfo.HIT_NORMAL).Bank,
() => Is.EqualTo(HitSampleInfo.BANK_SOFT));
AddAssert("clap sample has drum bank", () => EditorBeatmap.HitObjects[1].Samples.Single(s => s.Name == HitSampleInfo.HIT_CLAP).Bank,
() => Is.EqualTo(HitSampleInfo.BANK_DRUM));
AddAssert("clap sample has soft bank", () => EditorBeatmap.HitObjects[1].Samples.Single(s => s.Name == HitSampleInfo.HIT_CLAP).Bank,
() => Is.EqualTo(HitSampleInfo.BANK_SOFT));
AddAssert("circle inherited volume", () => EditorBeatmap.HitObjects[1].Samples.All(s => s.Volume == 70));
AddStep("seek to 1000", () => EditorClock.Seek(1000)); // previous object is the one at time 500, which has no additions
@@ -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++)
{
@@ -1,6 +1,8 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
@@ -15,6 +17,7 @@ using osu.Game.Database;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Lounge;
@@ -22,6 +25,7 @@ using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
using osu.Game.Screens.Play;
using osu.Game.Tests.Resources;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
{
@@ -35,7 +39,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
protected IScreen CurrentScreen => multiplayerComponents.CurrentScreen;
protected IScreen CurrentSubScreen => multiplayerComponents.MultiplayerScreen.CurrentSubScreen;
private BeatmapManager beatmaps = null!;
protected BeatmapManager Beatmaps { get; private set; } = null!;
private BeatmapSetInfo importedSet = null!;
private RulesetStore rulesets = null!;
@@ -49,7 +54,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
BeatmapStore beatmapStore;
Dependencies.Cache(rulesets = new RealmRulesetStore(Realm));
Dependencies.Cache(beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.Cache(Beatmaps = new BeatmapManager(LocalStorage, Realm, null, audio, Resources, host, Beatmap.Default));
Dependencies.CacheAs(beatmapStore = new RealmDetachedBeatmapStore());
Dependencies.Cache(Realm);
@@ -62,13 +67,13 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("import beatmap", () =>
{
beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
Beatmaps.Import(TestResources.GetQuickTestBeatmapForImport()).WaitSafely();
Realm.Write(r =>
{
foreach (var beatmapInfo in r.All<BeatmapInfo>())
beatmapInfo.OnlineMD5Hash = beatmapInfo.MD5Hash;
});
importedSet = beatmaps.GetAllUsableBeatmapSets().First();
importedSet = Beatmaps.GetAllUsableBeatmapSets().First();
InitialBeatmap = importedSet.Beatmaps.First(b => b.Ruleset.OnlineID == 0);
OtherBeatmap = importedSet.Beatmaps.Last(b => b.Ruleset.OnlineID == 0);
});
@@ -118,6 +123,30 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("exit player", () => multiplayerComponents.MultiplayerScreen.MakeCurrent());
}
protected void AddBeatmapFromSongSelect(Func<BeatmapInfo> beatmap, RulesetInfo? ruleset = null, IReadOnlyList<Mod>? mods = null)
{
Screens.SelectV2.SongSelect? songSelect = null;
AddStep("click add button", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerMatchSubScreen.AddItemButton>().Single());
InputManager.Click(MouseButton.Left);
});
AddUntilStep("wait for song select", () => (songSelect = CurrentSubScreen as Screens.SelectV2.SongSelect) != null);
AddUntilStep("wait for loaded", () => songSelect.IsCurrentScreen() && !songSelect.AsNonNull().IsFiltering);
if (ruleset != null)
AddStep($"set {ruleset.Name} ruleset", () => songSelect.AsNonNull().Ruleset.Value = ruleset);
if (mods != null)
AddStep($"set mods to {string.Join(",", mods.Select(m => m.Acronym))}", () => songSelect.AsNonNull().Mods.Value = mods);
AddStep("select other beatmap", () => songSelect.AsNonNull().Beatmap.Value = Beatmaps.GetWorkingBeatmap(beatmap()));
AddStep("confirm selection", () => InputManager.Key(Key.Enter));
AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
@@ -1,24 +1,16 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.OnlinePlay.Multiplayer.Match;
using osu.Game.Screens.Play;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
{
@@ -45,10 +37,10 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestItemAddedToTheEndOfQueue()
{
addItem(() => OtherBeatmap);
AddBeatmapFromSongSelect(() => OtherBeatmap);
AddUntilStep("playlist has 2 items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2);
addItem(() => InitialBeatmap);
AddBeatmapFromSongSelect(() => InitialBeatmap);
AddUntilStep("playlist has 3 items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 3);
AddUntilStep("first item still selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID);
@@ -57,8 +49,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestNextItemSelectedAfterGameplayFinish()
{
addItem(() => OtherBeatmap);
addItem(() => InitialBeatmap);
AddBeatmapFromSongSelect(() => OtherBeatmap);
AddBeatmapFromSongSelect(() => InitialBeatmap);
RunGameplay();
@@ -74,8 +66,8 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestItemsNotClearedWhenSwitchToHostOnlyMode()
{
addItem(() => OtherBeatmap);
addItem(() => InitialBeatmap);
AddBeatmapFromSongSelect(() => OtherBeatmap);
AddBeatmapFromSongSelect(() => InitialBeatmap);
// Move to the "other" beatmap.
RunGameplay();
@@ -89,14 +81,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestCorrectItemSelectedAfterNewItemAdded()
{
addItem(() => OtherBeatmap);
AddBeatmapFromSongSelect(() => OtherBeatmap);
AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID);
}
[Test]
public void TestCorrectRulesetSelectedAfterNewItemAdded()
{
addItem(() => OtherBeatmap, new CatchRuleset().RulesetInfo);
AddBeatmapFromSongSelect(() => OtherBeatmap, new CatchRuleset().RulesetInfo);
AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID);
AddUntilStep("wait for idle", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Idle);
@@ -113,7 +105,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestCorrectModsSelectedAfterNewItemAdded()
{
addItem(() => OtherBeatmap, mods: new Mod[] { new OsuModDoubleTime() });
AddBeatmapFromSongSelect(() => OtherBeatmap, mods: new Mod[] { new OsuModDoubleTime() });
AddUntilStep("selected beatmap is initial beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == InitialBeatmap.OnlineID);
AddUntilStep("wait for idle", () => MultiplayerClient.LocalUser?.State == MultiplayerUserState.Idle);
@@ -126,28 +118,5 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("mods are correct", () => !((Player)CurrentScreen).Mods.Value.Any());
AddStep("exit player", () => CurrentScreen.Exit());
}
private void addItem(Func<BeatmapInfo> beatmap, RulesetInfo? ruleset = null, IReadOnlyList<Mod>? mods = null)
{
Screens.Select.SongSelect? songSelect = null;
AddStep("click add button", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerMatchSubScreen.AddItemButton>().Single());
InputManager.Click(MouseButton.Left);
});
AddUntilStep("wait for song select", () => (songSelect = CurrentSubScreen as Screens.Select.SongSelect) != null);
AddUntilStep("wait for loaded", () => songSelect.AsNonNull().BeatmapSetsLoaded);
if (ruleset != null)
AddStep($"set {ruleset.Name} ruleset", () => songSelect.AsNonNull().Ruleset.Value = ruleset);
if (mods != null)
AddStep($"set mods to {string.Join(",", mods.Select(m => m.Acronym))}", () => songSelect.AsNonNull().Mods.Value = mods);
AddStep("select other beatmap", () => songSelect.AsNonNull().FinaliseSelection(beatmap()));
AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen);
}
}
}
@@ -7,14 +7,13 @@ using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Framework.Testing;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens;
using osu.Game.Screens.Footer;
using osu.Game.Screens.OnlinePlay;
using osu.Game.Utils;
@@ -22,12 +21,13 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
{
public partial class TestSceneFreeModSelectOverlay : MultiplayerTestScene
public partial class TestSceneFreeModSelectOverlay : ScreenTestScene
{
private FreeModSelectOverlay freeModSelectOverlay = null!;
private FooterButtonFreeMods footerButtonFreeMods = null!;
private ScreenFooter footer = null!;
private TestFreeModSelectOverlayScreen screen = null!;
private readonly Bindable<Dictionary<ModType, IReadOnlyList<Mod>>> availableMods = new Bindable<Dictionary<ModType, IReadOnlyList<Mod>>>();
private readonly Bindable<IReadOnlyList<Mod>> freeMods = new Bindable<IReadOnlyList<Mod>>([]);
private FreeModSelectOverlay freeModSelectOverlay => screen.Overlay;
[BackgroundDependencyLoader]
private void load(OsuGameBase osuGameBase)
@@ -35,6 +35,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
availableMods.BindTo(osuGameBase.AvailableMods);
}
[SetUpSteps]
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("reset selected mods", () => freeMods.Value = []);
}
[Test]
public void TestFreeModSelect()
{
@@ -44,11 +52,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
() => this.ChildrenOfType<ModPanel>()
.Where(panel => panel.IsPresent)
.All(panel => panel.Mod.HasImplementation && panel.Mod.UserPlayable));
AddToggleStep("toggle visibility", visible =>
{
freeModSelectOverlay.State.Value = visible ? Visibility.Visible : Visibility.Hidden;
});
}
[Test]
@@ -72,18 +75,16 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("select all button enabled", () => this.ChildrenOfType<SelectAllModsButton>().Single().Enabled.Value);
AddStep("click select all button", navigateAndClick<SelectAllModsButton>);
AddStep("click select all button", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<SelectAllModsButton>().Single());
InputManager.Click(MouseButton.Left);
});
AddAssert("select all button disabled", () => !this.ChildrenOfType<SelectAllModsButton>().Single().Enabled.Value);
AddStep("change search term", () => freeModSelectOverlay.SearchTerm = "e");
AddAssert("select all button enabled", () => this.ChildrenOfType<SelectAllModsButton>().Single().Enabled.Value);
void navigateAndClick<T>() where T : Drawable
{
InputManager.MoveMouseTo(this.ChildrenOfType<T>().Single());
InputManager.Click(MouseButton.Left);
}
}
[Test]
@@ -124,55 +125,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("select all button enabled", () => this.ChildrenOfType<SelectAllModsButton>().Single().Enabled.Value);
}
[Test]
public void TestSelectAllViaFooterButtonThenDeselectFromOverlay()
{
createFreeModSelect();
AddAssert("overlay select all button enabled", () => this.ChildrenOfType<SelectAllModsButton>().Single().Enabled.Value);
AddAssert("footer button displays off", () => footerButtonFreeMods.ChildrenOfType<IHasText>().Any(t => t.Text == "off"));
AddStep("click footer select all button", () =>
{
InputManager.MoveMouseTo(footerButtonFreeMods);
InputManager.Click(MouseButton.Left);
});
AddUntilStep("all mods selected", assertAllAvailableModsSelected);
AddAssert("footer button displays all", () => footerButtonFreeMods.ChildrenOfType<IHasText>().Any(t => t.Text == "all"));
AddStep("click deselect all button", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<DeselectAllModsButton>().Single());
InputManager.Click(MouseButton.Left);
});
AddUntilStep("all mods deselected", () => !freeModSelectOverlay.SelectedMods.Value.Any());
AddAssert("footer button displays off", () => footerButtonFreeMods.ChildrenOfType<IHasText>().Any(t => t.Text == "off"));
}
private void createFreeModSelect()
{
AddStep("create free mod select screen", () => Child = new DependencyProvidingContainer
AddStep("create free mod select screen", () => LoadScreen(screen = new TestFreeModSelectOverlayScreen
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
freeModSelectOverlay = new FreeModSelectOverlay
{
State = { Value = Visibility.Visible }
},
footerButtonFreeMods = new FooterButtonFreeMods(freeModSelectOverlay)
{
Anchor = Anchor.BottomRight,
Origin = Anchor.BottomRight,
Y = -ScreenFooter.HEIGHT,
FreeMods = { BindTarget = freeModSelectOverlay.SelectedMods },
},
footer = new ScreenFooter(),
},
CachedDependencies = new (Type, object)[] { (typeof(ScreenFooter), footer) },
});
FreeMods = { BindTarget = freeMods },
}));
AddUntilStep("wait until screen is loaded", () => screen.IsLoaded, () => Is.True);
AddStep("show overlay", () => freeModSelectOverlay.Show());
AddUntilStep("all column content loaded",
() => freeModSelectOverlay.ChildrenOfType<ModColumn>().Any()
&& freeModSelectOverlay.ChildrenOfType<ModColumn>().All(column => column.IsLoaded && column.ItemsLoaded));
@@ -197,5 +157,50 @@ namespace osu.Game.Tests.Visual.Multiplayer
return true;
}
private partial class TestFreeModSelectOverlayScreen : OsuScreen
{
public override bool ShowFooter => true;
public FreeModSelectOverlay Overlay = null!;
private IDisposable? overlayRegistration;
public readonly Bindable<IReadOnlyList<Mod>> FreeMods = new Bindable<IReadOnlyList<Mod>>([]);
[Resolved]
private IOverlayManager? overlayManager { get; set; }
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
[BackgroundDependencyLoader]
private void load()
{
LoadComponent(Overlay = new FreeModSelectOverlay
{
SelectedMods = { BindTarget = FreeMods }
});
}
protected override void LoadComplete()
{
base.LoadComplete();
overlayRegistration = overlayManager?.RegisterBlockingOverlay(Overlay);
}
public override IReadOnlyList<ScreenFooterButton> CreateFooterButtons() =>
[
new FooterButtonFreeMods(Overlay)
{
FreeMods = { BindTarget = FreeMods },
},
];
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
overlayRegistration?.Dispose();
}
}
}
}
@@ -5,6 +5,8 @@ using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Extensions;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
@@ -36,6 +38,14 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("second playlist item selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[1].ID);
}
[Test]
public void TestItemStillSelectedAfterChangeToSameBeatmap()
{
selectNewItem(() => InitialBeatmap);
AddUntilStep("playlist item still selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID);
}
[Test]
public void TestSettingsUpdatedWhenChangingQueueMode()
{
@@ -47,14 +57,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("api room updated", () => MultiplayerClient.ClientAPIRoom?.QueueMode == QueueMode.AllPlayers);
}
[Test]
public void TestItemStillSelectedAfterChangeToSameBeatmap()
{
selectNewItem(() => InitialBeatmap);
AddUntilStep("playlist item still selected", () => MultiplayerClient.ClientRoom?.Settings.PlaylistItemId == MultiplayerClient.ClientAPIRoom?.Playlist[0].ID);
}
[Test]
public void TestItemStillSelectedAfterChangeToOtherBeatmap()
{
@@ -80,13 +82,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
[Test]
public void TestAddItemsAsHost()
{
addItem(() => OtherBeatmap);
AddBeatmapFromSongSelect(() => OtherBeatmap);
AddUntilStep("playlist contains two items", () => MultiplayerClient.ClientAPIRoom?.Playlist.Count == 2);
}
private void selectNewItem(Func<BeatmapInfo> beatmap)
{
Screens.SelectV2.SongSelect? songSelect = null;
AddUntilStep("wait for playlist panels to load", () =>
{
var queueList = this.ChildrenOfType<MultiplayerQueueList>().Single();
@@ -99,26 +103,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
InputManager.Click(MouseButton.Left);
});
AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded);
AddUntilStep("wait for song select", () => (songSelect = CurrentSubScreen as Screens.SelectV2.SongSelect) != null);
AddUntilStep("wait for loaded", () => songSelect.IsCurrentScreen() && !songSelect.AsNonNull().IsFiltering);
BeatmapInfo otherBeatmap = null!;
AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(otherBeatmap = beatmap()));
AddStep("select other beatmap", () => songSelect.AsNonNull().Beatmap.Value = Beatmaps.GetWorkingBeatmap(otherBeatmap = beatmap()));
AddStep("confirm selection", () => InputManager.Key(Key.Enter));
AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen);
AddUntilStep("selected item is new beatmap", () => Beatmap.Value.BeatmapInfo.OnlineID == otherBeatmap.OnlineID);
}
private void addItem(Func<BeatmapInfo> beatmap)
{
AddStep("click add button", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<MultiplayerMatchSubScreen.AddItemButton>().Single());
InputManager.Click(MouseButton.Left);
});
AddUntilStep("wait for song select", () => CurrentSubScreen is Screens.Select.SongSelect select && select.BeatmapSetsLoaded);
AddStep("select other beatmap", () => ((Screens.Select.SongSelect)CurrentSubScreen).FinaliseSelection(beatmap()));
AddUntilStep("wait for return to match", () => CurrentSubScreen is MultiplayerMatchSubScreen);
}
}
}
@@ -228,7 +228,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
// edit playlist item
AddStep("Press select", () => InputManager.Key(Key.Enter));
AddUntilStep("wait for song select", () => InputManager.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault()?.BeatmapSetsLoaded == true);
waitForSongSelect();
// select beatmap
AddStep("Press select", () => InputManager.Key(Key.Enter));
@@ -451,7 +451,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
((MultiplayerMatchSubScreen)currentSubScreen).ShowSongSelect(item);
});
AddUntilStep("wait for song select", () => this.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault()?.BeatmapSetsLoaded == true);
waitForSongSelect();
AddUntilStep("Beatmap matches current item", () => Beatmap.Value.BeatmapInfo.OnlineID == multiplayerClient.ClientRoom?.Playlist.First().BeatmapID);
@@ -492,7 +492,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
((MultiplayerMatchSubScreen)currentSubScreen).ShowSongSelect(item);
});
AddUntilStep("wait for song select", () => this.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault()?.BeatmapSetsLoaded == true);
waitForSongSelect();
AddUntilStep("Ruleset matches current item", () => Ruleset.Value.OnlineID == multiplayerClient.ClientRoom?.Playlist.First().RulesetID);
@@ -533,7 +533,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
((MultiplayerMatchSubScreen)currentSubScreen).ShowSongSelect(item);
});
AddUntilStep("wait for song select", () => this.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault()?.BeatmapSetsLoaded == true);
waitForSongSelect();
AddUntilStep("Mods match current item",
() => SelectedMods.Value.Select(m => m.Acronym).SequenceEqual(multiplayerClient.ClientRoom.AsNonNull().Playlist.First().RequiredMods.Select(m => m.Acronym)));
@@ -1051,7 +1051,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddStep("press edit on second item", () => this.ChildrenOfType<DrawableRoomPlaylistItem>().Single(i => i.Item.RulesetID == 1)
.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistEditButton>().Single().TriggerClick());
AddUntilStep("wait for song select", () => InputManager.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault()?.BeatmapSetsLoaded == true);
waitForSongSelect();
AddAssert("ruleset is taiko", () => Ruleset.Value.OnlineID == 1);
AddStep("start match", () => multiplayerClient.StartMatch().WaitSafely());
@@ -1249,6 +1249,15 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddUntilStep("wait for join", () => multiplayerClient.RoomJoined);
}
private void waitForSongSelect()
{
AddUntilStep("wait for song select", () =>
{
var songSelect = InputManager.ChildrenOfType<MultiplayerMatchSongSelect>().FirstOrDefault();
return songSelect != null && songSelect.IsCurrentScreen() && !songSelect.IsFiltering;
});
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
@@ -16,6 +16,7 @@ using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Database;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Overlays.Mods;
using osu.Game.Rulesets;
@@ -26,8 +27,8 @@ using osu.Game.Rulesets.Taiko;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Multiplayer;
using osu.Game.Screens.Select;
using osu.Game.Tests.Resources;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
{
@@ -64,7 +65,11 @@ namespace osu.Game.Tests.Visual.Multiplayer
{
base.SetUpSteps();
AddStep("create room", () => room = CreateDefaultRoom());
AddStep("create room", () =>
{
Ruleset.Value = new OsuRuleset().RulesetInfo;
room = CreateDefaultRoom();
});
AddStep("join room", () => JoinRoom(room));
WaitForJoined();
}
@@ -80,7 +85,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
LoadScreen(songSelect = new TestMultiplayerMatchSongSelect(room));
});
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded);
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && !songSelect.IsFiltering);
}
[Test]
@@ -101,19 +106,21 @@ namespace osu.Game.Tests.Visual.Multiplayer
setUp();
AddStep("change ruleset", () => Ruleset.Value = new TaikoRuleset().RulesetInfo);
AddUntilStep("wait for filtering", () => !songSelect.IsFiltering);
AddStep("select beatmap",
() => songSelect.Carousel.SelectBeatmap(selectedBeatmap = beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == new TaikoRuleset().LegacyID)));
() => songSelect.SelectBeatmap(selectedBeatmap = beatmaps.First(beatmap => beatmap.Ruleset.OnlineID == new TaikoRuleset().LegacyID)));
AddUntilStep("wait for selection", () => Beatmap.Value.BeatmapInfo.Equals(selectedBeatmap));
AddUntilStep("wait for ongoing operation to complete", () => !OnlinePlayDependencies.OngoingOperationTracker.InProgress.Value);
AddStep("set mods", () => SelectedMods.Value = new[] { new TaikoModDoubleTime() });
AddStep("confirm selection", () => songSelect.FinaliseSelection());
AddStep("confirm selection", () => InputManager.Key(Key.Enter));
AddUntilStep("song select exited", () => !songSelect.IsCurrentScreen());
AddAssert("beatmap not changed", () => Beatmap.Value.BeatmapInfo.Equals(selectedBeatmap));
AddAssert("beatmap not changed", () => Beatmap.Value.BeatmapInfo, () => Is.EqualTo((selectedBeatmap)));
AddAssert("ruleset not changed", () => Ruleset.Value.Equals(new TaikoRuleset().RulesetInfo));
AddAssert("mods not changed", () => SelectedMods.Value.Single() is TaikoModDoubleTime);
}
@@ -133,10 +140,42 @@ namespace osu.Game.Tests.Visual.Multiplayer
// A previous test's mod overlay could still be fading out.
AddUntilStep("wait for only one freemod overlay", () => this.ChildrenOfType<FreeModSelectOverlay>().Count() == 1);
AddStep("open free mod overlay", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<FooterButtonFreeMods>().Single());
InputManager.Click(MouseButton.Left);
});
assertFreeModNotShown(allowedMod);
assertFreeModNotShown(requiredMod);
}
[Test]
public void TestFreeModsDisplayedOnEnter()
{
AddStep("set room freemods", () =>
{
var editedItem = MultiplayerClient.ClientRoom!.CurrentPlaylistItem.Clone();
editedItem.AllowedMods =
[
new APIMod(new OsuModHardRock()),
];
MultiplayerClient.EditPlaylistItem(editedItem);
});
setUp();
AddStep("open free mod overlay", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<FooterButtonFreeMods>().Single());
InputManager.Click(MouseButton.Left);
});
assertFreeModShown(typeof(OsuModHardRock));
}
[Test]
public void TestChangeRulesetImmediatelyAfterLoadComplete()
{
@@ -154,16 +193,27 @@ namespace osu.Game.Tests.Visual.Multiplayer
songSelect.OnLoadComplete += _ => Ruleset.Value = new TaikoRuleset().RulesetInfo;
LoadScreen(songSelect);
});
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && songSelect.BeatmapSetsLoaded);
AddStep("confirm selection", () => songSelect.FinaliseSelection());
AddUntilStep("wait for present", () => songSelect.IsCurrentScreen() && !songSelect.IsFiltering);
AddStep("confirm selection", () => InputManager.Key(Key.Enter));
AddAssert("beatmap is taiko", () => Beatmap.Value.BeatmapInfo.Ruleset.OnlineID, () => Is.EqualTo(1));
AddAssert("ruleset is taiko", () => Ruleset.Value.OnlineID, () => Is.EqualTo(1));
}
private void assertFreeModShown(Type type)
{
AddUntilStep($"{type.ReadableName()} displayed in freemod overlay",
() => this.ChildrenOfType<FreeModSelectOverlay>()
.Single()
.ChildrenOfType<ModPanel>()
.Where(panel => panel.Visible)
.Any(b => b.Mod.GetType() == type));
}
private void assertFreeModNotShown(Type type)
{
AddAssert($"{type.ReadableName()} not displayed in freemod overlay",
AddUntilStep($"{type.ReadableName()} not displayed in freemod overlay",
() => this.ChildrenOfType<FreeModSelectOverlay>()
.Single()
.ChildrenOfType<ModPanel>()
@@ -185,12 +235,12 @@ namespace osu.Game.Tests.Visual.Multiplayer
public new Bindable<IReadOnlyList<Mod>> FreeMods => base.FreeMods;
public new BeatmapCarousel Carousel => base.Carousel;
public TestMultiplayerMatchSongSelect(Room room, PlaylistItem? itemToEdit = null)
: base(room, itemToEdit)
{
}
public void SelectBeatmap(BeatmapInfo beatmap) => SelectAndRun(beatmap, () => { });
}
}
}
@@ -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;
@@ -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,13 +161,10 @@ 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<FooterButtonFreeMods>().Single());
InputManager.Click(MouseButton.Left);
});
AddAssert("mod select not visible", () => this.ChildrenOfType<FreeModSelectOverlay>().Single().State.Value, () => Is.EqualTo(Visibility.Hidden));
@@ -185,7 +177,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
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<FooterButtonFreeMods>().Single());
InputManager.Click(MouseButton.Left);
});
AddAssert("mod select visible", () => this.ChildrenOfType<FreeModSelectOverlay>().Single().State.Value, () => Is.EqualTo(Visibility.Visible));
@@ -201,8 +193,6 @@ namespace osu.Game.Tests.Visual.Multiplayer
private partial class TestPlaylistsSongSelect : PlaylistsSongSelect
{
public new MatchBeatmapDetailArea BeatmapDetails => (MatchBeatmapDetailArea)base.BeatmapDetails;
public new IBindable<bool> Freestyle => base.Freestyle;
public TestPlaylistsSongSelect(Room room)
@@ -8,9 +8,13 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Screens;
using osu.Game.Screens.Footer;
@@ -23,10 +27,13 @@ namespace osu.Game.Tests.Visual.Navigation
[Test]
public void TestFooterButtonsOnScreenTransitions()
{
PushAndConfirm(() => new TestScreenOne());
PushAndConfirm(() => new TestScreen
{
CreateButtons = () => [new ScreenFooterButton { Text = "Button One" }]
});
AddUntilStep("button one shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button One"));
PushAndConfirm(() => new TestScreenTwo());
PushAndConfirm(() => new TestScreen { CreateButtons = () => [new ScreenFooterButton { Text = "Button Two" }] });
AddUntilStep("button two shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button Two"));
AddStep("exit screen", () => Game.ScreenStack.Exit());
@@ -40,7 +47,7 @@ namespace osu.Game.Tests.Visual.Navigation
AddAssert("footer hidden", () => screenFooter.State.Value, () => Is.EqualTo(Visibility.Hidden));
AddAssert("old back button shown", () => Game.BackButton.State.Value, () => Is.EqualTo(Visibility.Visible));
PushAndConfirm(() => new TestScreen(true));
PushAndConfirm(() => new TestScreen());
AddAssert("footer shown", () => screenFooter.State.Value, () => Is.EqualTo(Visibility.Visible));
AddAssert("old back button hidden", () => Game.BackButton.State.Value, () => Is.EqualTo(Visibility.Hidden));
@@ -69,10 +76,16 @@ namespace osu.Game.Tests.Visual.Navigation
AddAssert("footer hidden", () => screenFooter.State.Value, () => Is.EqualTo(Visibility.Hidden));
AddAssert("old back button shown", () => Game.BackButton.State.Value, () => Is.EqualTo(Visibility.Visible));
pushSubScreenAndConfirm(() => screen, () => new TestScreenOne());
pushSubScreenAndConfirm(() => screen, () => new TestScreen
{
CreateButtons = () => [new ScreenFooterButton { Text = "Button One" }]
});
AddUntilStep("button one shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button One"));
pushSubScreenAndConfirm(() => screen, () => new TestScreenTwo());
pushSubScreenAndConfirm(() => screen, () => new TestScreen
{
CreateButtons = () => [new ScreenFooterButton { Text = "Button Two" }]
});
AddUntilStep("button two shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button Two"));
AddStep("exit sub screen", () => screen.ExitSubScreen());
@@ -92,10 +105,16 @@ namespace osu.Game.Tests.Visual.Navigation
TestScreenWithSubScreen screen = null!;
PushAndConfirm(() => screen = new TestScreenWithSubScreen());
pushSubScreenAndConfirm(() => screen, () => new TestScreenOne());
pushSubScreenAndConfirm(() => screen, () => new TestScreen
{
CreateButtons = () => [new ScreenFooterButton { Text = "Button One" }]
});
AddUntilStep("button one shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button One"));
PushAndConfirm(() => new TestScreenTwo());
PushAndConfirm(() => new TestScreen
{
CreateButtons = () => [new ScreenFooterButton { Text = "Button Two" }]
});
AddUntilStep("button two shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button Two"));
AddStep("exit parent screen", () => Game.ScreenStack.Exit());
@@ -111,14 +130,23 @@ namespace osu.Game.Tests.Visual.Navigation
TestScreenWithSubScreen screen = null!;
PushAndConfirm(() => screen = new TestScreenWithSubScreen());
pushSubScreenAndConfirm(() => screen, () => new TestScreenOne());
pushSubScreenAndConfirm(() => screen, () => new TestScreen
{
CreateButtons = () => [new ScreenFooterButton { Text = "Button One" }]
});
AddUntilStep("button one shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button One"));
PushAndConfirm(() => new TestScreenOne());
PushAndConfirm(() => new TestScreen
{
CreateButtons = () => [new ScreenFooterButton { Text = "Button One" }]
});
AddUntilStep("button one shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button One"));
// Can't use the helper method because the screen never loads
AddStep("Push new sub screen", () => screen.PushSubScreen(new TestScreenTwo()));
AddStep("Push new sub screen", () => screen.PushSubScreen(new TestScreen
{
CreateButtons = () => [new ScreenFooterButton { Text = "Button Two" }]
}));
AddWaitStep("wait for potential screen load", 5);
AddUntilStep("button one still shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button One"));
@@ -126,6 +154,83 @@ namespace osu.Game.Tests.Visual.Navigation
AddUntilStep("button two shown", () => screenFooter.ChildrenOfType<ScreenFooterButton>().First().Text.ToString(), () => Is.EqualTo("Button Two"));
}
/// <summary>
/// Tests clicking the back button while an overlay is open.
/// </summary>
[Test]
public void TestBackButtonWhenOverlayOpen()
{
TestScreen screen = null!;
PushAndConfirm(() =>
{
ShearedOverlayContainer overlay = new TestShearedOverlayContainer();
return screen = new TestScreen
{
Overlay = overlay,
CreateButtons = () =>
[
new ScreenFooterButton(overlay)
{
AccentColour = Dependencies.Get<OsuColour>().Orange1,
Icon = FontAwesome.Solid.Toolbox,
Text = "One",
},
new ScreenFooterButton { Text = "Two", Action = () => { } },
new ScreenFooterButton { Text = "Three", Action = () => { } },
],
};
});
AddStep("show overlay", () => screen.Overlay.Show());
AddAssert("overlay shown", () => screen.Overlay.State.Value, () => Is.EqualTo(Visibility.Visible));
AddStep("press back", () => screenFooter.ChildrenOfType<ScreenBackButton>().Single().TriggerClick());
AddAssert("overlay hidden", () => screen.Overlay.State.Value, () => Is.EqualTo(Visibility.Hidden));
AddAssert("screen still shown", () => screen.IsCurrentScreen(), () => Is.True);
}
/// <summary>
/// Tests clicking the back button on an overlay with `BackButtonPressed` being overridden.
/// </summary>
[Test]
public void TestBackButtonWithCustomBackButtonPressed()
{
TestScreen screen = null!;
TestShearedOverlayContainer overlay = null!;
PushAndConfirm(() =>
{
return screen = new TestScreen
{
Overlay = overlay = new TestShearedOverlayContainer(),
CreateButtons = () =>
[
new ScreenFooterButton(overlay)
{
AccentColour = Dependencies.Get<OsuColour>().Orange1,
Icon = FontAwesome.Solid.Toolbox,
Text = "One",
},
new ScreenFooterButton { Text = "Two", Action = () => { } },
new ScreenFooterButton { Text = "Three", Action = () => { } },
],
};
});
AddStep("show overlay", () => screen.Overlay.Show());
AddAssert("overlay shown", () => screen.Overlay.State.Value, () => Is.EqualTo(Visibility.Visible));
AddStep("set block count", () => overlay.BackButtonCount = 1);
AddStep("press back", () => screenFooter.ChildrenOfType<ScreenBackButton>().Single().TriggerClick());
AddAssert("overlay still shown", () => screen.Overlay.State.Value, () => Is.EqualTo(Visibility.Visible));
AddStep("press back again", () => screenFooter.ChildrenOfType<ScreenBackButton>().Single().TriggerClick());
AddAssert("overlay hidden", () => screen.Overlay.State.Value, () => Is.EqualTo(Visibility.Hidden));
AddAssert("screen still shown", () => screen.IsCurrentScreen(), () => Is.True);
}
private void pushSubScreenAndConfirm(Func<TestScreenWithSubScreen> target, Func<Screen> newScreen)
{
Screen screen = null!;
@@ -142,39 +247,45 @@ namespace osu.Game.Tests.Visual.Navigation
&& (previousScreen == null || previousScreen.GetChildScreen() == screen));
}
private partial class TestScreenOne : OsuScreen
{
public override bool ShowFooter => true;
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
public override IReadOnlyList<ScreenFooterButton> CreateFooterButtons() => new[]
{
new ScreenFooterButton { Text = "Button One" },
};
}
private partial class TestScreenTwo : OsuScreen
{
public override bool ShowFooter => true;
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
public override IReadOnlyList<ScreenFooterButton> CreateFooterButtons() => new[]
{
new ScreenFooterButton { Text = "Button Two" },
};
}
private partial class TestScreen : OsuScreen
{
public override bool ShowFooter { get; }
public TestScreen(bool footer)
public Func<IReadOnlyList<ScreenFooterButton>> CreateButtons = Array.Empty<ScreenFooterButton>;
public ShearedOverlayContainer Overlay = new TestShearedOverlayContainer();
private IDisposable? overlayRegistration;
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
[Resolved]
private IOverlayManager? overlayManager { get; set; }
public TestScreen(bool showFooter = true)
{
ShowFooter = footer;
ShowFooter = showFooter;
}
[BackgroundDependencyLoader]
private void load()
{
LoadComponent(Overlay);
}
protected override void LoadComplete()
{
base.LoadComplete();
overlayRegistration = overlayManager?.RegisterBlockingOverlay(Overlay);
}
public override IReadOnlyList<ScreenFooterButton> CreateFooterButtons() => CreateButtons.Invoke();
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
overlayRegistration?.Dispose();
}
}
@@ -196,5 +307,66 @@ namespace osu.Game.Tests.Visual.Navigation
public void ExitSubScreen() => SubScreenStack.Exit();
}
private partial class TestShearedOverlayContainer : ShearedOverlayContainer
{
public TestShearedOverlayContainer()
: base(OverlayColourScheme.Orange)
{
}
[BackgroundDependencyLoader]
private void load()
{
Header.Title = "Test overlay";
Header.Description = "An overlay that is made purely for testing purposes.";
}
public int BackButtonCount;
public override bool OnBackButton()
{
if (BackButtonCount > 0)
{
BackButtonCount--;
return true;
}
return false;
}
public override VisibilityContainer CreateFooterContent() => new TestFooterContent();
public partial class TestFooterContent : VisibilityContainer
{
[BackgroundDependencyLoader]
private void load()
{
AutoSizeAxes = Axes.Both;
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Children = new[]
{
new ShearedButton(200) { Text = "Action #1", Action = () => { } },
new ShearedButton(140) { Text = "Action #2", Action = () => { } },
}
};
}
protected override void PopIn()
{
this.MoveToY(0, 400, Easing.OutQuint)
.FadeIn(400, Easing.OutQuint);
}
protected override void PopOut()
{
this.MoveToY(-20f, 200, Easing.OutQuint)
.FadeOut(200, Easing.OutQuint);
}
}
}
}
}
@@ -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 PlaylistsSongSelect 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 PlaylistsSongSelect 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();
@@ -72,6 +72,10 @@ namespace osu.Game.Tests.Visual.Online
Preview = @"https://b.ppy.sh/preview/12345.mp3",
PlayCount = 123,
FavouriteCount = 456,
NominationStatus = new BeatmapSetNominationStatus
{
Current = 2,
},
Submitted = DateTime.Now,
Ranked = DateTime.Now,
BPM = 111,
@@ -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.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Graphics.Cursor;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Users.Drawables;
using osuTK;
namespace osu.Game.Tests.Visual.Online
{
public partial class TestSceneClickableTeamFlag : OsuManualInputManagerTestScene
{
[SetUpSteps]
public void SetUp()
{
AddStep("create flags", () =>
{
Child = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Spacing = new Vector2(10f),
Children = new[]
{
new ClickableTeamFlag(
new APITeam
{
Id = 1,
Name = "Collective Wangs",
ShortName = "WANG",
}, showTooltipOnHover: false) { Width = 300, Height = 150 },
new ClickableTeamFlag(
new APITeam
{
Id = 2,
Name = "mom?",
ShortName = "MOM",
FlagUrl = "https://assets.ppy.sh/teams/flag/1/b46fb10dbfd8a35dc50e6c00296c0dc6172dffc3ed3d3a4b379277ba498399fe.png",
}, showTooltipOnHover: true) { Width = 300, Height = 150 },
},
};
});
}
[Test]
public void TestHover()
{
AddStep("hover flag with no tooltip", () => InputManager.MoveMouseTo(this.ChildrenOfType<ClickableTeamFlag>().ElementAt(0)));
AddWaitStep("wait", 3);
AddAssert("tooltip is not visible", () => this.ChildrenOfType<OsuTooltipContainer.OsuTooltip>().FirstOrDefault()?.State.Value, () => Is.EqualTo(Visibility.Hidden));
AddStep("hover flag with tooltip", () => InputManager.MoveMouseTo(this.ChildrenOfType<ClickableTeamFlag>().ElementAt(1)));
AddUntilStep("wait for tooltip to show", () => this.ChildrenOfType<OsuTooltipContainer.OsuTooltip>().FirstOrDefault()?.State.Value, () => Is.EqualTo(Visibility.Visible));
}
}
}
@@ -3,18 +3,16 @@
using System;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Testing;
using osu.Game.Database;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Metadata;
using osu.Game.Online.Spectator;
using osu.Game.Overlays;
using osu.Game.Overlays.Dashboard;
using osu.Game.Screens.OnlinePlay.Match.Components;
using osu.Game.Overlays.Dashboard.CurrentlyOnline;
using osu.Game.Tests.Visual.Metadata;
using osu.Game.Tests.Visual.Spectator;
using osu.Game.Users;
@@ -23,6 +21,26 @@ namespace osu.Game.Tests.Visual.Online
{
public partial class TestSceneCurrentlyOnlineDisplay : OsuTestScene
{
private static readonly string[] usernames =
{
"fieryrage",
"Kerensa",
"MillhioreF",
"Player01",
"smoogipoo",
"Ephemeral",
"BTMC",
"Cilvery",
"m980",
"HappyStick",
"LittleEndu",
"frenzibyte",
"Zallius",
"BanchoBot",
"rocketminer210",
"pishifat"
};
private readonly APIUser streamingUser = new APIUser { Id = 2, Username = "Test user" };
private TestSpectatorClient spectatorClient = null!;
@@ -36,11 +54,34 @@ namespace osu.Game.Tests.Visual.Online
{
spectatorClient = new TestSpectatorClient();
metadataClient = new TestMetadataClient();
var lookupCache = new TestUserLookupCache();
((DummyAPIAccess)API).HandleRequest = req =>
{
switch (req)
{
case LookupUsersRequest lookupUsersRequest:
var users = lookupUsersRequest.UserIds.Select(id =>
{
// tests against failed lookups
if (id == 13)
return null;
return new APIUser
{
Id = id,
Username = usernames[id % usernames.Length],
};
}).ToList();
lookupUsersRequest.TriggerSuccess(new GetUsersResponse { Users = users });
return true;
default:
return false;
}
};
Children = new Drawable[]
{
lookupCache,
spectatorClient,
metadataClient,
new DependencyProvidingContainer
@@ -50,13 +91,9 @@ namespace osu.Game.Tests.Visual.Online
{
(typeof(SpectatorClient), spectatorClient),
(typeof(MetadataClient), metadataClient),
(typeof(UserLookupCache), lookupCache),
(typeof(OverlayColourProvider), new OverlayColourProvider(OverlayColourScheme.Purple)),
},
Child = currentlyOnline = new CurrentlyOnlineDisplay
{
RelativeSizeAxes = Axes.Both,
}
Child = currentlyOnline = new CurrentlyOnlineDisplay()
},
};
});
@@ -69,17 +106,18 @@ namespace osu.Game.Tests.Visual.Online
AddStep("Begin watching user presence", () => token = metadataClient.BeginWatchingUserPresence());
AddStep("Add online user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() }));
AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType<UserGridPanel>().FirstOrDefault()?.User.Id == 2);
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.False);
AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType<OnlineUserPanel>().FirstOrDefault()?.User.Id == 2);
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType<OnlineUserPanel>().First().CanSpectate.Value, () => Is.False);
AddStep("User began playing", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.InSoloGame() }));
AddAssert("Spectate button enabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.True);
AddAssert("Spectate button enabled", () => currentlyOnline.ChildrenOfType<OnlineUserPanel>().First().CanSpectate.Value, () => Is.True);
AddStep("User finished playing", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() }));
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.False);
AddStep("User finished playing",
() => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() }));
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType<OnlineUserPanel>().First().CanSpectate.Value, () => Is.False);
AddStep("Remove playing user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, null));
AddUntilStep("Panel no longer present", () => !currentlyOnline.ChildrenOfType<UserGridPanel>().Any());
AddUntilStep("Panel no longer present", () => !currentlyOnline.ChildrenOfType<OnlineUserPanel>().Any());
AddStep("End watching user presence", () => token.Dispose());
}
@@ -90,49 +128,14 @@ namespace osu.Game.Tests.Visual.Online
AddStep("Begin watching user presence", () => token = metadataClient.BeginWatchingUserPresence());
AddStep("Add online user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.InSoloGame() }));
AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType<UserGridPanel>().FirstOrDefault()?.User.Id == streamingUser.Id);
AddAssert("Spectate button enabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.True);
AddUntilStep("Panel loaded", () => currentlyOnline.ChildrenOfType<OnlineUserPanel>().FirstOrDefault()?.User.Id == streamingUser.Id);
AddAssert("Spectate button enabled", () => currentlyOnline.ChildrenOfType<OnlineUserPanel>().First().CanSpectate.Value, () => Is.True);
AddStep("User finished playing", () => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() }));
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType<PurpleRoundedButton>().First().Enabled.Value, () => Is.False);
AddStep("User finished playing",
() => metadataClient.UserPresenceUpdated(streamingUser.Id, new UserPresence { Status = UserStatus.Online, Activity = new UserActivity.ChoosingBeatmap() }));
AddAssert("Spectate button disabled", () => currentlyOnline.ChildrenOfType<OnlineUserPanel>().First().CanSpectate.Value, () => Is.False);
AddStep("Remove playing user", () => metadataClient.UserPresenceUpdated(streamingUser.Id, null));
AddStep("End watching user presence", () => token.Dispose());
}
internal partial class TestUserLookupCache : UserLookupCache
{
private static readonly string[] usernames =
{
"fieryrage",
"Kerensa",
"MillhioreF",
"Player01",
"smoogipoo",
"Ephemeral",
"BTMC",
"Cilvery",
"m980",
"HappyStick",
"LittleEndu",
"frenzibyte",
"Zallius",
"BanchoBot",
"rocketminer210",
"pishifat"
};
protected override Task<APIUser?> ComputeValueAsync(int lookup, CancellationToken token = default)
{
// tests against failed lookups
if (lookup == 13)
return Task.FromResult<APIUser?>(null);
return Task.FromResult<APIUser?>(new APIUser
{
Id = lookup,
Username = usernames[lookup % usernames.Length],
});
}
}
}
}
@@ -0,0 +1,111 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Metadata;
using osu.Game.Overlays;
using osu.Game.Overlays.Dashboard.CurrentlyOnline;
using osu.Game.Rulesets;
using osu.Game.Tests.Visual.Metadata;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Tests.Visual.Online
{
[TestFixture]
public partial class TestSceneOnlineUserPanel : OsuTestScene
{
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Purple);
[Resolved]
private IRulesetStore rulesetStore { get; set; } = null!;
private TestMetadataClient metadataClient = null!;
private OnlineUserListPanel panel = null!;
[SetUp]
public void SetUp() => Schedule(() =>
{
Child = new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both,
CachedDependencies =
[
(typeof(MetadataClient), metadataClient = new TestMetadataClient())
],
Children = new Drawable[]
{
metadataClient,
new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
Spacing = new Vector2(10f),
Children = new Drawable[]
{
new OnlineUserGridPanel(new APIUser
{
Username = @"flyte",
Id = 3103765,
CountryCode = CountryCode.JP,
CoverUrl = @"https://assets.ppy.sh/user-cover-presets/1/df28696b58541a9e67f6755918951d542d93bdf1da41720fcca2fd2c1ea8cf51.jpeg",
WasRecentlyOnline = true
}),
new OnlineUserGridPanel(new APIUser
{
Username = @"peppy",
Id = 2,
CountryCode = CountryCode.AU,
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg",
IsSupporter = true,
SupportLevel = 3,
}),
new OnlineUserListPanel(new APIUser
{
Username = @"flyte",
Id = 3103765,
CountryCode = CountryCode.JP,
CoverUrl = @"https://assets.ppy.sh/user-cover-presets/1/df28696b58541a9e67f6755918951d542d93bdf1da41720fcca2fd2c1ea8cf51.jpeg",
WasRecentlyOnline = true
}),
panel = new OnlineUserListPanel(new APIUser
{
Username = @"peppy",
Id = 2,
CountryCode = CountryCode.AU,
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8195163/4a8e2ad5a02a2642b631438cfa6c6bd7e2f9db289be881cb27df18331f64144c.jpeg",
LastVisit = DateTimeOffset.Now
}),
}
}
}
};
metadataClient.BeginWatchingUserPresence();
});
[Test]
public void TestUserActivity()
{
AddStep("idle", () => setPresence(UserStatus.Online, null));
AddStep("in game", () => setPresence(UserStatus.Online, new UserActivity.InSoloGame(new BeatmapInfo(), rulesetStore.GetRuleset(0)!)));
}
private void setPresence(UserStatus status, UserActivity? activity, int? userId = null)
{
if (status == UserStatus.Offline)
metadataClient.UserPresenceUpdated(userId ?? panel.User.OnlineID, null);
else
metadataClient.UserPresenceUpdated(userId ?? panel.User.OnlineID, new UserPresence { Status = status, Activity = activity });
}
}
}
@@ -0,0 +1,43 @@
// 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.Game.Online.API.Requests.Responses;
using osu.Game.Users.Drawables;
namespace osu.Game.Tests.Visual.Online
{
[TestFixture]
public partial class TestSceneUpdateableTeamFlag : OsuTestScene
{
[Test]
public void TestHideOnNull()
{
UpdateableTeamFlag flag = null!;
AddStep("create flag with team", () => Child = flag = new UpdateableTeamFlag(createTeam(), hideOnNull: true) { Width = 300, Height = 150 });
AddAssert("flag is present", () => flag.IsPresent, () => Is.True);
AddStep("set team to null", () => flag.Team = null);
AddAssert("flag is not present", () => flag.IsPresent, () => Is.False);
}
[Test]
public void DontHideOnNull()
{
UpdateableTeamFlag flag = null!;
AddStep("create flag with team", () => Child = flag = new UpdateableTeamFlag(createTeam(), hideOnNull: false) { Width = 300, Height = 150 });
AddAssert("flag is present", () => flag.IsPresent, () => Is.True);
AddStep("set team to null", () => flag.Team = null);
AddAssert("flag is present", () => flag.IsPresent, () => Is.True);
}
private static APITeam createTeam() => new APITeam
{
Id = 2,
Name = "mom?",
ShortName = "MOM",
FlagUrl = @"https://assets.ppy.sh/teams/flag/1/b46fb10dbfd8a35dc50e6c00296c0dc6172dffc3ed3d3a4b379277ba498399fe.png",
};
}
}
@@ -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 FooterButtonFreeMods button;
public TestSceneFooterButtonFreeModsV2()
{
ModSelectOverlay modSelectOverlay;
Add(modSelectOverlay = new TestModSelectOverlay());
Add(button = new FooterButtonFreeMods(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 FooterButtonFreestyle
{
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 PlaylistsSongSelect.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();
});
}
}
}
@@ -13,11 +13,11 @@ using osu.Framework.Bindables;
using osu.Framework.Extensions;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Platform;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Database;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Online.Rooms;
using osu.Game.Rulesets;
@@ -34,6 +34,7 @@ using osu.Game.Screens.OnlinePlay;
using osu.Game.Screens.OnlinePlay.Playlists;
using osu.Game.Tests.Resources;
using osu.Game.Tests.Visual.OnlinePlay;
using osuTK.Input;
namespace osu.Game.Tests.Visual.Playlists
{
@@ -215,6 +216,85 @@ namespace osu.Game.Tests.Visual.Playlists
AddUntilStep("second beatmap selected", () => Beatmap.Value.BeatmapInfo.Equals(importedSet.Beatmaps[0]));
}
[Test]
public void TestFreestyleSelectAbort()
{
Room room = null!;
AddStep("add room", () =>
{
room = new Room
{
RoomID = 1,
Playlist =
[
new PlaylistItem(importedSet.Beatmaps[0])
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
Freestyle = true
},
]
};
API.Perform(new CreateRoomRequest(room));
});
TestPlaylistsScreen playlistsScreen = null!;
AddStep("load screen", () => LoadScreen(playlistsScreen = new TestPlaylistsScreen(new TestPlaylistsRoomSubScreen(room))));
AddUntilStep("wait for playlist room screen", () => playlistsScreen.Stack.CurrentScreen is PlaylistsRoomSubScreen roomSubScreen && roomSubScreen.IsLoaded);
AddUntilStep("original beatmap", () => Beatmap.Value.BeatmapInfo.Equals(importedSet.Beatmaps[0]));
AddStep("enter freestyle select", () => playlistsScreen.Stack.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistEditButton>().Single(b => b.IsPresent).TriggerClick());
AddUntilStep("wait for select screen", () => playlistsScreen.Stack.CurrentScreen is PlaylistsRoomFreestyleSelect selectScreen && selectScreen.CarouselItemsPresented);
AddStep("select next beatmap", () => InputManager.Key(Key.Down));
AddStep("abort", () => playlistsScreen.Stack.CurrentScreen.Exit());
AddUntilStep("beatmap not changed", () => Beatmap.Value.BeatmapInfo.Equals(importedSet.Beatmaps[0]));
}
[Test]
public void TestFreestyleSelect()
{
Room room = null!;
AddStep("add room", () =>
{
room = new Room
{
RoomID = 1,
Playlist =
[
new PlaylistItem(importedSet.Beatmaps[0])
{
RulesetID = new OsuRuleset().RulesetInfo.OnlineID,
Freestyle = true
},
]
};
API.Perform(new CreateRoomRequest(room));
});
TestPlaylistsScreen playlistsScreen = null!;
AddStep("load screen", () => LoadScreen(playlistsScreen = new TestPlaylistsScreen(new TestPlaylistsRoomSubScreen(room))));
AddUntilStep("wait for playlist room screen", () => playlistsScreen.Stack.CurrentScreen is PlaylistsRoomSubScreen roomSubScreen && roomSubScreen.IsLoaded);
AddUntilStep("original beatmap", () => Beatmap.Value.BeatmapInfo.Equals(importedSet.Beatmaps[0]));
AddStep("enter freestyle select", () => playlistsScreen.Stack.ChildrenOfType<DrawableRoomPlaylistItem.PlaylistEditButton>().Single(b => b.IsPresent).TriggerClick());
AddUntilStep("wait for select screen", () => playlistsScreen.Stack.CurrentScreen is PlaylistsRoomFreestyleSelect selectScreen && selectScreen.CarouselItemsPresented);
AddStep("select next beatmap", () => InputManager.Key(Key.Down));
AddStep("select (beatmap)", () => InputManager.Key(Key.Enter));
AddStep("select (exit screen)", () => InputManager.Key(Key.Enter));
AddUntilStep("beatmap changed", () => Beatmap.Value.BeatmapInfo.Equals(importedSet.Beatmaps[1]));
}
/// <summary>
/// Tests that the ruleset style is reset when the selected item is changed and it's no longer valid.
/// </summary>
@@ -591,30 +671,16 @@ namespace osu.Game.Tests.Visual.Playlists
private partial class TestPlaylistsScreen : OsuScreen
{
public readonly OnlinePlaySubScreenStack Stack;
public TestPlaylistsScreen(PlaylistsRoomSubScreen screen)
{
OnlinePlaySubScreenStack stack;
InternalChildren = new Drawable[]
InternalChild = Stack = new OnlinePlaySubScreenStack
{
stack = new OnlinePlaySubScreenStack
{
RelativeSizeAxes = Axes.Both
},
new BackButton
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
State = { Value = Visibility.Visible },
Action = () =>
{
if (stack.CurrentScreen is not PlaylistsRoomSubScreen)
stack.Exit();
}
}
RelativeSizeAxes = Axes.Both
};
stack.Push(screen);
Stack.Push(screen);
}
}
@@ -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;
@@ -70,6 +71,15 @@ namespace osu.Game.Tests.Visual.Settings
checkBinding("Increase volume", "Shift");
}
[Test]
public void TestRulesetBindingSingleModifier()
{
scrollToAndStartBinding("Left button");
AddStep("press left shift", () => InputManager.Key(Key.ShiftLeft));
AddStep("release left shift", () => InputManager.ReleaseKey(Key.ShiftLeft));
checkBinding("Left button", "LShift");
}
[Test]
public void TestBindingSingleKeyWithModifier()
{
@@ -202,16 +212,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 +242,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 +251,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 +404,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 +460,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()
{
@@ -7,8 +7,10 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Graphics.Containers;
using osu.Framework.Testing;
using osu.Game.Beatmaps;
using osu.Game.Extensions;
using osu.Game.Graphics.Sprites;
using osu.Game.Models;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Screens.SelectV2;
@@ -53,8 +55,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
working.Metadata.Source = string.Empty;
onlineLookupResult.Value = online;
Beatmap.Value = working;
onlineLookupResult.Value = online;
});
AddStep("no success rate", () =>
{
@@ -63,8 +65,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
online.Result!.Beatmaps.Single().PlayCount = 0;
online.Result!.Beatmaps.Single().PassCount = 0;
onlineLookupResult.Value = online;
Beatmap.Value = working;
onlineLookupResult.Value = online;
});
AddStep("no user ratings", () =>
{
@@ -72,8 +74,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
online.Result!.Ratings = Array.Empty<int>();
onlineLookupResult.Value = online;
Beatmap.Value = working;
onlineLookupResult.Value = online;
});
AddStep("no fail times", () =>
{
@@ -81,8 +83,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
online.Result!.Beatmaps.Single().FailTimes = null;
onlineLookupResult.Value = online;
Beatmap.Value = working;
onlineLookupResult.Value = online;
});
AddStep("no metrics", () =>
{
@@ -91,8 +93,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
online.Result!.Ratings = Array.Empty<int>();
online.Result!.Beatmaps.Single().FailTimes = null;
onlineLookupResult.Value = online;
Beatmap.Value = working;
onlineLookupResult.Value = online;
});
AddStep("local beatmap", () =>
{
@@ -100,8 +102,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
working.BeatmapInfo.OnlineID = 0;
onlineLookupResult.Value = null;
Beatmap.Value = working;
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.Completed(null);
});
}
@@ -119,8 +121,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
online.Result!.Language = new BeatmapSetOnlineLanguage { Id = 12, Name = "Verrrrryyyy llooonngggggg language" };
online.Result!.Beatmaps.Single().TopTags = Enumerable.Repeat(online.Result!.Beatmaps.Single().TopTags, 3).SelectMany(t => t!).ToArray();
onlineLookupResult.Value = online;
Beatmap.Value = working;
onlineLookupResult.Value = online;
});
}
@@ -137,20 +139,28 @@ namespace osu.Game.Tests.Visual.SongSelectV2
working.BeatmapInfo.ResetOnlineInfo();
onlineLookupResult.Value = lookupResult;
Beatmap.Value = working;
onlineLookupResult.Value = lookupResult;
});
AddUntilStep("rating wedge hidden", () => !wedge.RatingsVisible);
AddUntilStep("fail time wedge hidden", () => !wedge.FailRetryVisible);
// just check for text everywhere on the wedge as the classes are private and generic
AddAssert("genre is still visible", () => wedge.ChildrenOfType<OsuSpriteText>().Any(t => t.Text == "Pop"));
AddAssert("language is still visible", () => wedge.ChildrenOfType<OsuSpriteText>().Any(t => t.Text == "English"));
AddStep("local beatmap", () =>
{
var (working, _) = createTestBeatmap();
onlineLookupResult.Value = null;
Beatmap.Value = working;
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.Completed(null);
});
AddAssert("rating wedge still hidden", () => !wedge.RatingsVisible);
AddAssert("fail time wedge still hidden", () => !wedge.FailRetryVisible);
AddAssert("genre is cleared", () => wedge.ChildrenOfType<OsuSpriteText>().All(t => t.Text != "Pop"));
AddAssert("language is cleared", () => wedge.ChildrenOfType<OsuSpriteText>().All(t => t.Text != "English"));
}
[Test]
@@ -166,8 +176,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
online.Result!.RelatedTags = null;
working.BeatmapSetInfo.Beatmaps.Single().Metadata.UserTags.Clear();
onlineLookupResult.Value = online;
Beatmap.Value = working;
onlineLookupResult.Value = online;
});
}
@@ -178,9 +188,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
{
var (working, online) = createTestBeatmap();
Beatmap.Value = working;
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.InProgress();
Scheduler.AddDelayed(() => onlineLookupResult.Value = online, 500);
Beatmap.Value = working;
});
AddWaitStep("wait", 5);
@@ -192,9 +202,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
online.Result!.RelatedTags[1].Name = "another/tag";
online.Result!.RelatedTags[2].Name = "some/tag";
Beatmap.Value = working;
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.InProgress();
Scheduler.AddDelayed(() => onlineLookupResult.Value = online, 500);
Beatmap.Value = working;
});
AddWaitStep("wait", 5);
@@ -206,9 +216,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
online.Result!.RelatedTags = null;
working.BeatmapSetInfo.Beatmaps.Single().Metadata.UserTags.Clear();
Beatmap.Value = working;
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.InProgress();
Scheduler.AddDelayed(() => onlineLookupResult.Value = online, 500);
Beatmap.Value = working;
});
AddWaitStep("wait", 5);
@@ -220,9 +230,9 @@ namespace osu.Game.Tests.Visual.SongSelectV2
online.Result!.RelatedTags = null;
working.BeatmapSetInfo.Beatmaps.Single().Metadata.UserTags.Clear();
Beatmap.Value = working;
onlineLookupResult.Value = Screens.SelectV2.SongSelect.BeatmapSetLookupResult.InProgress();
Scheduler.AddDelayed(() => onlineLookupResult.Value = online, 500);
Beatmap.Value = working;
});
AddWaitStep("wait", 5);
}
@@ -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));
}
@@ -295,7 +295,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddUntilStep($"displayed bpm is {target}", () =>
{
var label = titleWedge.ChildrenOfType<BeatmapTitleWedge.Statistic>().Single(l => l.TooltipText == BeatmapsetsStrings.ShowStatsBpm);
return label.Text == target;
return label.Text.ToString() == target;
});
}
@@ -1,301 +0,0 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Testing;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Screens.Footer;
using osu.Game.Screens.SelectV2;
namespace osu.Game.Tests.Visual.SongSelectV2
{
public partial class TestSceneScreenFooter : OsuManualInputManagerTestScene
{
private DependencyProvidingContainer contentContainer = null!;
private ScreenFooter screenFooter = null!;
private UserModSelectOverlay modOverlay = null!;
[SetUp]
public void SetUp() => Schedule(() =>
{
screenFooter = new ScreenFooter();
Child = contentContainer = new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both,
CachedDependencies = new (Type, object)[]
{
(typeof(ScreenFooter), screenFooter)
},
Children = new Drawable[]
{
modOverlay = new UserModSelectOverlay { ShowPresets = true },
new PopoverContainer
{
RelativeSizeAxes = Axes.Both,
Depth = float.MinValue,
Child = screenFooter,
},
},
};
screenFooter.SetButtons(new ScreenFooterButton[]
{
new FooterButtonMods(modOverlay) { Current = SelectedMods },
new FooterButtonRandom(),
new FooterButtonOptions(),
});
});
[SetUpSteps]
public void SetUpSteps()
{
AddStep("show footer", () => screenFooter.Show());
}
/// <summary>
/// Transition when moving from a screen with no buttons to a screen with buttons.
/// </summary>
[Test]
public void TestButtonsIn()
{
}
/// <summary>
/// Transition when moving from a screen with buttons to a screen with no buttons.
/// </summary>
[Test]
public void TestButtonsOut()
{
AddStep("clear buttons", () => screenFooter.SetButtons(Array.Empty<ScreenFooterButton>()));
}
/// <summary>
/// Transition when moving from a screen with buttons to a screen with buttons.
/// </summary>
[Test]
public void TestReplaceButtons()
{
AddStep("replace buttons", () => screenFooter.SetButtons(new[]
{
new ScreenFooterButton { Text = "One", Action = () => { } },
new ScreenFooterButton { Text = "Two", Action = () => { } },
new ScreenFooterButton { Text = "Three", Action = () => { } },
}));
}
[Test]
public void TestExternalOverlayContent()
{
TestShearedOverlayContainer externalOverlay = null!;
AddStep("add overlay", () => contentContainer.Add(externalOverlay = new TestShearedOverlayContainer()));
AddStep("set buttons", () => screenFooter.SetButtons(new[]
{
new ScreenFooterButton(externalOverlay)
{
AccentColour = Dependencies.Get<OsuColour>().Orange1,
Icon = FontAwesome.Solid.Toolbox,
Text = "One",
},
new ScreenFooterButton { Text = "Two", Action = () => { } },
new ScreenFooterButton { Text = "Three", Action = () => { } },
}));
AddWaitStep("wait for transition", 3);
AddStep("show overlay", () => externalOverlay.Show());
contentDisplayed();
AddUntilStep("other buttons hidden", () => screenFooter.ChildrenOfType<ScreenFooterButton>().Skip(1).All(b => b.Child.Parent!.Y > 0));
AddStep("hide overlay", () => externalOverlay.Hide());
contentHidden();
AddUntilStep("other buttons returned", () => screenFooter.ChildrenOfType<ScreenFooterButton>().Skip(1).All(b => b.ChildrenOfType<Container>().First().Y == 0));
}
[Test]
public void TestTemporarilyShowFooter()
{
TestShearedOverlayContainer externalOverlay = null!;
AddStep("hide footer", () => screenFooter.Hide());
AddStep("remove buttons", () => screenFooter.SetButtons(Array.Empty<ScreenFooterButton>()));
AddStep("add external overlay", () => contentContainer.Add(externalOverlay = new TestShearedOverlayContainer()));
AddStep("show external overlay", () => externalOverlay.Show());
AddAssert("footer shown", () => screenFooter.State.Value == Visibility.Visible);
contentDisplayed();
AddStep("hide external overlay", () => externalOverlay.Hide());
AddAssert("footer hidden", () => screenFooter.State.Value == Visibility.Hidden);
contentHidden();
AddStep("show footer", () => screenFooter.Show());
AddAssert("content still hidden from footer", () => screenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().SingleOrDefault()?.IsPresent != true);
AddStep("show external overlay", () => externalOverlay.Show());
AddAssert("footer still visible", () => screenFooter.State.Value == Visibility.Visible);
AddStep("hide external overlay", () => externalOverlay.Hide());
AddAssert("footer still visible", () => screenFooter.State.Value == Visibility.Visible);
AddStep("hide footer", () => screenFooter.Hide());
AddStep("show external overlay", () => externalOverlay.Show());
}
[Test]
public void TestBackButton()
{
TestShearedOverlayContainer externalOverlay = null!;
AddStep("hide footer", () => screenFooter.Hide());
AddStep("remove buttons", () => screenFooter.SetButtons(Array.Empty<ScreenFooterButton>()));
AddStep("add external overlay", () => contentContainer.Add(externalOverlay = new TestShearedOverlayContainer()));
AddStep("show external overlay", () => externalOverlay.Show());
AddAssert("footer shown", () => screenFooter.State.Value == Visibility.Visible);
AddStep("press back", () => this.ChildrenOfType<ScreenBackButton>().Single().TriggerClick());
AddAssert("overlay hidden", () => externalOverlay.State.Value == Visibility.Hidden);
AddAssert("footer hidden", () => screenFooter.State.Value == Visibility.Hidden);
AddStep("show external overlay", () => externalOverlay.Show());
AddStep("set block count", () => externalOverlay.BackButtonCount = 1);
AddStep("press back", () => this.ChildrenOfType<ScreenBackButton>().Single().TriggerClick());
AddAssert("overlay still visible", () => externalOverlay.State.Value == Visibility.Visible);
AddAssert("footer still shown", () => screenFooter.State.Value == Visibility.Visible);
AddStep("press back again", () => this.ChildrenOfType<ScreenBackButton>().Single().TriggerClick());
AddAssert("overlay hidden", () => externalOverlay.State.Value == Visibility.Hidden);
AddAssert("footer hidden", () => screenFooter.State.Value == Visibility.Hidden);
}
[Test]
public void TestLoadOverlayAfterFooterIsDisplayed()
{
TestShearedOverlayContainer externalOverlay = null!;
AddStep("show mod overlay", () => modOverlay.Show());
AddUntilStep("mod footer content shown", () => this.ChildrenOfType<ModSelectFooterContent>().SingleOrDefault()?.IsPresent, () => Is.True);
AddStep("add external overlay", () => contentContainer.Add(externalOverlay = new TestShearedOverlayContainer()));
AddUntilStep("wait for load", () => externalOverlay.IsLoaded);
AddAssert("mod footer content still shown", () => this.ChildrenOfType<ModSelectFooterContent>().SingleOrDefault()?.IsPresent, () => Is.True);
AddAssert("external overlay content not shown", () => this.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().SingleOrDefault()?.IsPresent, () => Is.Not.True);
AddStep("hide mod overlay", () => modOverlay.Hide());
AddUntilStep("mod footer content hidden", () => this.ChildrenOfType<ModSelectFooterContent>().SingleOrDefault()?.IsPresent, () => Is.Not.True);
AddAssert("external overlay content still not shown", () => this.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().SingleOrDefault()?.IsPresent, () => Is.Not.True);
}
[Test]
public void TestButtonResizedAfterFooterIsDisplayed()
{
TestShearedOverlayContainer externalOverlay = null!;
AddStep("add overlay", () => contentContainer.Add(externalOverlay = new TestShearedOverlayContainer()));
AddStep("set buttons", () => screenFooter.SetButtons(new[]
{
new ScreenFooterButton(externalOverlay)
{
AccentColour = Dependencies.Get<OsuColour>().Orange1,
Icon = FontAwesome.Solid.Toolbox,
Text = "One",
},
new ScreenFooterButton { Text = "Two", Action = () => { } },
new ScreenFooterButton { Text = "Three", Action = () => { } },
}));
AddWaitStep("wait for transition", 3);
AddStep("show overlay", () => externalOverlay.Show());
contentDisplayed();
AddUntilStep("other buttons hidden", () => screenFooter.ChildrenOfType<ScreenFooterButton>().Skip(1).All(b => b.Child.Parent!.Y > 0));
AddStep("resize active button", () => this.ChildrenOfType<ScreenFooterButton>().First().ResizeWidthTo(240, 300, Easing.OutQuint));
AddStep("resize active button back", () => this.ChildrenOfType<ScreenFooterButton>().First().ResizeWidthTo(116, 300, Easing.OutQuint));
AddStep("hide overlay", () => externalOverlay.Hide());
contentHidden();
AddUntilStep("other buttons returned", () => screenFooter.ChildrenOfType<ScreenFooterButton>().Skip(1).All(b => b.ChildrenOfType<Container>().First().Y == 0));
}
private void contentHidden()
{
AddUntilStep("content hidden from footer", () => screenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().SingleOrDefault()?.IsPresent != true);
}
private void contentDisplayed()
{
AddUntilStep("content displayed in footer", () => screenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().Single().IsPresent);
}
private partial class TestShearedOverlayContainer : ShearedOverlayContainer
{
public TestShearedOverlayContainer()
: base(OverlayColourScheme.Orange)
{
}
[BackgroundDependencyLoader]
private void load()
{
Header.Title = "Test overlay";
Header.Description = "An overlay that is made purely for testing purposes.";
}
public int BackButtonCount;
public override bool OnBackButton()
{
if (BackButtonCount > 0)
{
BackButtonCount--;
return true;
}
return false;
}
public override VisibilityContainer CreateFooterContent() => new TestFooterContent();
public partial class TestFooterContent : VisibilityContainer
{
[BackgroundDependencyLoader]
private void load()
{
AutoSizeAxes = Axes.Both;
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Children = new[]
{
new ShearedButton(200) { Text = "Action #1", Action = () => { } },
new ShearedButton(140) { Text = "Action #2", Action = () => { } },
}
};
}
protected override void PopIn()
{
this.MoveToY(0, 400, Easing.OutQuint)
.FadeIn(400, Easing.OutQuint);
}
protected override void PopOut()
{
this.MoveToY(-20f, 200, Easing.OutQuint)
.FadeOut(200, Easing.OutQuint);
}
}
}
}
}
@@ -12,6 +12,7 @@ using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Chat;
using osu.Game.Rulesets.Catch;
using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Mods;
@@ -266,6 +267,8 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddUntilStep("wait for placeholder visible", () => getPlaceholder()?.State.Value == Visibility.Visible);
AddAssert("still has selection", () => Beatmap.IsDefault, () => Is.False);
AddStep("reset star difficulty filter", () => Config.SetValue(OsuSetting.DisplayStarsMinimum, 0.0));
}
[Test]
@@ -365,13 +368,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
SortBy(SortMode.Difficulty);
checkMatchedBeatmaps(6);
AddUntilStep("wait for spread indicator", () => this.ChildrenOfType<PanelBeatmapStandalone.SpreadDisplay>().Any(d => d.Enabled.Value));
AddStep("click spread indicator", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<PanelBeatmapStandalone.SpreadDisplay>().Single(d => d.Enabled.Value));
InputManager.Click(MouseButton.Left);
});
WaitForFiltering();
scopeBeatmap(false);
checkMatchedBeatmaps(3);
AddStep("press Escape", () => InputManager.Key(Key.Escape));
@@ -389,8 +386,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
SortBy(SortMode.Artist);
checkMatchedBeatmaps(6);
AddStep("click spread indicator", () => this.ChildrenOfType<PanelBeatmapSet.SpreadDisplay>().Single(d => d.Enabled.Value).TriggerClick());
WaitForFiltering();
scopeBeatmap(true);
checkMatchedBeatmaps(3);
AddStep("press Escape", () => InputManager.Key(Key.Escape));
@@ -412,8 +408,7 @@ namespace osu.Game.Tests.Visual.SongSelectV2
WaitForFiltering();
checkMatchedBeatmaps(3);
AddStep("click spread indicator", () => this.ChildrenOfType<PanelBeatmapSet.SpreadDisplay>().Single(d => d.Enabled.Value).TriggerClick());
WaitForFiltering();
scopeBeatmap(true);
checkMatchedBeatmaps(3);
AddStep("press Escape", () => InputManager.Key(Key.Escape));
@@ -422,6 +417,179 @@ namespace osu.Game.Tests.Visual.SongSelectV2
AddAssert("text filter not emptied", () => filterTextBox.Current.Value, () => Is.Not.Empty);
}
[TestCase(false)]
[TestCase(true)]
public void TestUnscopeRevertsToOriginalSelection(bool grouped)
{
ImportBeatmapForRuleset(0);
ImportBeatmapForRuleset(0);
LoadSongSelect();
SortBy(grouped ? SortMode.Title : SortMode.Difficulty);
checkMatchedBeatmaps(6);
AddStep("select normal difficulty", () => Beatmap.Value = Beatmaps.GetWorkingBeatmap(findBeatmap("Normal")));
AddUntilStep("selection changed", () => Beatmap.Value.BeatmapInfo, () => Is.EqualTo(findBeatmap("Normal")));
scopeBeatmap(grouped);
checkMatchedBeatmaps(3);
AddStep("select insane difficulty", () => Beatmap.Value = Beatmaps.GetWorkingBeatmap(findBeatmap("Insane")));
AddUntilStep("selection changed", () => Beatmap.Value.BeatmapInfo, () => Is.EqualTo(findBeatmap("Insane")));
AddStep("exit scoped view", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<FilterControl.ScopedBeatmapSetDisplay>().First());
InputManager.Click(MouseButton.Left);
});
WaitForFiltering();
checkMatchedBeatmaps(6);
AddAssert("normal difficulty is selected", () => Beatmap.Value.BeatmapInfo, () => Is.EqualTo(findBeatmap("Normal")));
AddStep("reset star difficulty filter", () => Config.SetValue(OsuSetting.DisplayStarsMaximum, 10.1));
}
[TestCase(false)]
[TestCase(true)]
public void TestUnscopeWhenSelectedBeatmapHiddenByFilters(bool grouped)
{
ImportBeatmapForRuleset(0);
ImportBeatmapForRuleset(0);
LoadSongSelect();
SortBy(grouped ? SortMode.Title : SortMode.Difficulty);
checkMatchedBeatmaps(6);
AddStep("set star difficulty filter", () => Config.SetValue(OsuSetting.DisplayStarsMaximum, findBeatmap("Hard").StarRating + 0.1));
WaitForFiltering();
AddStep("select hard difficulty", () => Beatmap.Value = Beatmaps.GetWorkingBeatmap(findBeatmap("Hard")));
AddUntilStep("selection changed", () => Beatmap.Value.BeatmapInfo, () => Is.EqualTo(findBeatmap("Hard")));
scopeBeatmap(grouped);
checkMatchedBeatmaps(3);
AddStep("select insane difficulty", () => Beatmap.Value = Beatmaps.GetWorkingBeatmap(findBeatmap("Insane")));
AddUntilStep("selection changed", () => Beatmap.Value.BeatmapInfo, () => Is.EqualTo(findBeatmap("Insane")));
AddStep("exit scoped view", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<FilterControl.ScopedBeatmapSetDisplay>().First());
InputManager.Click(MouseButton.Left);
});
WaitForFiltering();
AddAssert("hard difficulty is selected", () => Beatmap.Value.BeatmapInfo, () => Is.EqualTo(findBeatmap("Hard")));
AddStep("reset star difficulty filter", () => Config.SetValue(OsuSetting.DisplayStarsMaximum, 10.1));
}
[TestCase(false)]
[TestCase(true)]
public void TestUnscopeByChangingRuleset(bool grouped)
{
bool showConverts = Config.Get<bool>(OsuSetting.ShowConvertedBeatmaps);
AddStep("hide converts", () => Config.SetValue(OsuSetting.ShowConvertedBeatmaps, false));
ImportBeatmapForRuleset(0, 2);
LoadSongSelect();
SortBy(grouped ? SortMode.Title : SortMode.Difficulty);
checkMatchedBeatmaps(2);
scopeBeatmap(grouped);
checkMatchedBeatmaps(2);
AddStep("select insane difficulty", () => Beatmap.Value = Beatmaps.GetWorkingBeatmap(findBeatmap("Insane")));
AddUntilStep("selection changed", () => Beatmap.Value.BeatmapInfo, () => Is.EqualTo(findBeatmap("Insane")));
AddStep("change ruleset", () => Ruleset.Value = new CatchRuleset().RulesetInfo);
WaitForFiltering();
AddAssert("hard catch difficulty is selected", () => Beatmap.Value.BeatmapInfo, () => Is.EqualTo(findBeatmap("Hard")));
AddStep("revert convert setting", () => Config.SetValue(OsuSetting.ShowConvertedBeatmaps, showConverts));
}
[TestCase(false)]
[TestCase(true)]
public void TestUnscopeByShowingConverts(bool grouped)
{
bool showConverts = Config.Get<bool>(OsuSetting.ShowConvertedBeatmaps);
AddStep("hide converts", () => Config.SetValue(OsuSetting.ShowConvertedBeatmaps, false));
ImportBeatmapForRuleset(0);
ImportBeatmapForRuleset(0);
LoadSongSelect();
SortBy(grouped ? SortMode.Title : SortMode.Difficulty);
checkMatchedBeatmaps(6);
AddStep("set star difficulty filter", () => Config.SetValue(OsuSetting.DisplayStarsMaximum, Beatmap.Value.BeatmapSetInfo.Beatmaps.ElementAt(1).StarRating + 0.1));
WaitForFiltering();
AddStep("select hard difficulty", () => Beatmap.Value = Beatmaps.GetWorkingBeatmap(findBeatmap("Hard")));
AddUntilStep("selection changed", () => Beatmap.Value.BeatmapInfo, () => Is.EqualTo(findBeatmap("Hard")));
scopeBeatmap(grouped);
checkMatchedBeatmaps(3);
AddStep("select insane difficulty", () => Beatmap.Value = Beatmaps.GetWorkingBeatmap(findBeatmap("Insane")));
AddUntilStep("selection changed", () => Beatmap.Value.BeatmapInfo, () => Is.EqualTo(findBeatmap("Insane")));
AddStep("show converts", () => Config.SetValue(OsuSetting.ShowConvertedBeatmaps, true));
WaitForFiltering();
AddAssert("hard difficulty is selected", () => Beatmap.Value.BeatmapInfo, () => Is.EqualTo(findBeatmap("Hard")));
AddStep("revert convert setting", () => Config.SetValue(OsuSetting.ShowConvertedBeatmaps, showConverts));
AddStep("reset star difficulty filter", () => Config.SetValue(OsuSetting.DisplayStarsMaximum, 10.1));
}
[TestCase(false)]
[TestCase(true)]
public void TestUnscopeByChangingFilterText(bool grouped)
{
ImportBeatmapForRuleset(0);
ImportBeatmapForRuleset(0);
LoadSongSelect();
SortBy(grouped ? SortMode.Title : SortMode.Difficulty);
checkMatchedBeatmaps(6);
AddStep("select hard difficulty", () => Beatmap.Value = Beatmaps.GetWorkingBeatmap(findBeatmap("Hard")));
AddUntilStep("selection changed", () => Beatmap.Value.BeatmapInfo, () => Is.EqualTo(findBeatmap("Hard")));
scopeBeatmap(grouped);
checkMatchedBeatmaps(3);
AddStep("set filter text", () => filterTextBox.Current.Value = findBeatmap("Normal").DifficultyName);
WaitForFiltering();
AddAssert("normal difficulty is selected", () => Beatmap.Value.BeatmapInfo, () => Is.EqualTo(findBeatmap("Normal")));
}
private void scopeBeatmap(bool grouped)
{
if (grouped)
{
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());
}
else
{
AddUntilStep("wait for spread indicator", () => this.ChildrenOfType<PanelBeatmapStandalone.SpreadDisplay>().Any(d => d.Enabled.Value));
AddStep("click spread indicator", () => this.ChildrenOfType<PanelBeatmapStandalone.SpreadDisplay>().Single(d => d.Enabled.Value).TriggerClick());
}
WaitForFiltering();
}
private BeatmapInfo findBeatmap(string difficultySubstring) => Beatmap.Value.BeatmapSetInfo.Beatmaps.First(b => b.DifficultyName.Contains(difficultySubstring));
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);
}
@@ -12,7 +12,6 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Bindables;
using osu.Framework.Extensions.ObjectExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Screens;
using osu.Framework.Testing;
@@ -23,17 +22,16 @@ using osu.Game.Overlays;
using osu.Game.Overlays.FirstRunSetup;
using osu.Game.Overlays.Notifications;
using osu.Game.Screens;
using osu.Game.Screens.Footer;
using osu.Game.Tests.Beatmaps;
using osuTK;
using osuTK.Input;
namespace osu.Game.Tests.Visual.UserInterface
{
public partial class TestSceneFirstRunSetupOverlay : OsuManualInputManagerTestScene
public partial class TestSceneFirstRunSetupOverlay : ScreenTestScene
{
private FirstRunSetupOverlay overlay;
private ScreenFooter footer;
private TestFirstRunSetupOverlayScreen screen = null!;
private FirstRunSetupOverlay overlay => screen.Overlay;
private readonly Mock<TestPerformerFromScreenRunner> performer = new Mock<TestPerformerFromScreenRunner>();
@@ -53,8 +51,10 @@ namespace osu.Game.Tests.Visual.UserInterface
}
[SetUpSteps]
public void SetUpSteps()
public override void SetUpSteps()
{
base.SetUpSteps();
AddStep("setup dependencies", () =>
{
performer.Reset();
@@ -67,16 +67,16 @@ namespace osu.Game.Tests.Visual.UserInterface
.Callback((Notification n) => lastNotification = n);
});
createOverlay();
AddStep("reset first run", () => LocalConfig.SetValue(OsuSetting.ShowFirstRunSetup, true));
AddStep("show overlay", () => overlay.Show());
createScreen();
}
[Test]
public void TestBasic()
{
AddAssert("overlay visible", () => overlay.State.Value == Visibility.Visible);
AddAssert("footer visible", () => footer.State.Value == Visibility.Visible);
AddAssert("footer visible", () => ScreenFooter.State.Value == Visibility.Visible);
}
[Test]
@@ -92,7 +92,8 @@ namespace osu.Game.Tests.Visual.UserInterface
AddAssert("first run false", () => !LocalConfig.Get<bool>(OsuSetting.ShowFirstRunSetup));
createOverlay();
AddStep("exit screen", () => Stack.Exit());
createScreen();
AddWaitStep("wait some", 5);
@@ -146,7 +147,7 @@ namespace osu.Game.Tests.Visual.UserInterface
if (keyboard)
InputManager.Key(Key.Escape);
else
footer.BackButton.TriggerClick();
ScreenFooter.BackButton.TriggerClick();
}
return overlay.CurrentScreen is ScreenWelcome;
@@ -161,7 +162,7 @@ namespace osu.Game.Tests.Visual.UserInterface
}
else
{
AddStep("press back button", () => footer.BackButton.TriggerClick());
AddStep("press back button", () => ScreenFooter.BackButton.TriggerClick());
AddAssert("overlay dismissed", () => overlay.State.Value == Visibility.Hidden);
}
}
@@ -204,25 +205,45 @@ namespace osu.Game.Tests.Visual.UserInterface
AddAssert("is resumed", () => overlay.CurrentScreen is ScreenUIScale);
}
private void createOverlay()
private void createScreen()
{
AddStep("add overlay", () =>
{
var receptor = new ScreenFooter.BackReceptor();
footer = new ScreenFooter(receptor);
AddStep("push screen", () => LoadScreen(screen = new TestFirstRunSetupOverlayScreen()));
AddUntilStep("wait until screen is loaded", () => screen.IsLoaded, () => Is.True);
}
Child = new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both,
CachedDependencies = new[] { (typeof(ScreenFooter), (object)footer) },
Children = new Drawable[]
{
receptor,
overlay = new FirstRunSetupOverlay(),
footer,
}
};
});
private partial class TestFirstRunSetupOverlayScreen : OsuScreen
{
public override bool ShowFooter => true;
public FirstRunSetupOverlay Overlay = null!;
[CanBeNull]
private IDisposable overlayRegistration;
[CanBeNull]
[Resolved]
private IOverlayManager overlayManager { get; set; }
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
[BackgroundDependencyLoader]
private void load()
{
LoadComponent(Overlay = new FirstRunSetupOverlay());
}
protected override void LoadComplete()
{
base.LoadComplete();
overlayRegistration = overlayManager?.RegisterBlockingOverlay(Overlay);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
overlayRegistration?.Dispose();
}
}
// interface mocks break hot reload, mocking this stub implementation instead works around it.
@@ -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;
}
}
}
}
@@ -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);
}
@@ -7,21 +7,27 @@ using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Framework.Input.Events;
using osu.Framework.Utils;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.UserInterfaceV2;
using osu.Game.Input.Bindings;
using osuTK;
using osuTK.Graphics;
using osuTK.Input;
namespace osu.Game.Tests.Visual.UserInterface
{
public partial class TestSceneLoadingLayer : OsuTestScene
public partial class TestSceneLoadingLayer : OsuManualInputManagerTestScene
{
private TestLoadingLayer overlay;
private Container content;
private PressableButton pressableButton;
[SetUp]
public void SetUp() => Schedule(() =>
{
@@ -51,10 +57,9 @@ namespace osu.Game.Tests.Visual.UserInterface
{
new OsuSpriteText { Text = "Sample content" },
new RoundedButton { Text = "can't puush me", Width = 200, },
new RoundedButton { Text = "puush me", Width = 200, Action = () => { } },
pressableButton = new PressableButton { Text = "puush me", Width = 200 },
}
},
overlay = new TestLoadingLayer(true),
}
},
};
@@ -63,20 +68,62 @@ namespace osu.Game.Tests.Visual.UserInterface
[Test]
public void TestShowHide()
{
AddStep("create loading layer", () => content.Add(overlay = new TestLoadingLayer(true)));
AddAssert("not visible", () => !overlay.IsPresent);
AddStep("show", () => overlay.Show());
AddUntilStep("wait for content dim", () => overlay.Alpha > 0);
AddStep("hide", () => overlay.Hide());
AddUntilStep("wait for content restore", () => Precision.AlmostEquals(overlay.Alpha, 0));
}
[TestCase(true)]
[TestCase(false)]
public void TestBlockPositional(bool blockInput)
{
AddStep("create loading layer", () => content.Add(overlay = new TestLoadingLayer(true) { BlockPositionalInput = blockInput }));
AddStep("show", () => overlay.Show());
AddStep("click button", () =>
{
InputManager.MoveMouseTo(pressableButton);
InputManager.Click(MouseButton.Left);
});
AddAssert("check pressed", () => pressableButton.Pressed, () => Is.EqualTo(!blockInput));
}
[TestCase(true)]
[TestCase(false)]
public void TestBlockNonPositional(bool blockKeyboardInput)
{
AddStep("create loading layer", () => content.Add(overlay = new TestLoadingLayer(true) { BlockNonPositionalInput = blockKeyboardInput }));
AddStep("show", () => overlay.Show());
AddStep("press enter", () => InputManager.Key(Key.Enter));
AddAssert("check pressed", () => pressableButton.Pressed, () => Is.EqualTo(!blockKeyboardInput));
}
[TestCase(true)]
[TestCase(false)]
public void TestBlockNonPositionalGlobalAction(bool blockKeyboardInput)
{
AddStep("create loading layer", () => content.Add(overlay = new TestLoadingLayer(true) { BlockNonPositionalInput = blockKeyboardInput }));
AddStep("show", () => overlay.Show());
AddStep("press enter", () => InputManager.Key(Key.F8));
AddAssert("check pressed", () => pressableButton.Pressed, () => Is.EqualTo(!blockKeyboardInput));
}
[Test]
public void TestLargeArea()
{
AddStep("create loading layer", () => content.Add(overlay = new TestLoadingLayer(true)));
AddStep("show", () =>
{
content.RelativeSizeAxes = Axes.Both;
@@ -88,6 +135,42 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("hide", () => overlay.Hide());
}
public partial class PressableButton : RoundedButton, IKeyBindingHandler<GlobalAction>
{
public PressableButton()
{
Action = () => Pressed = true;
}
public bool Pressed { get; private set; }
protected override bool OnKeyDown(KeyDownEvent e)
{
if (e.Key == Key.Enter)
{
Pressed = true;
return true;
}
return base.OnKeyDown(e);
}
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{
if (e.Action == GlobalAction.ToggleChat)
{
Pressed = true;
return true;
}
return false;
}
public void OnReleased(KeyBindingReleaseEvent<GlobalAction> e)
{
}
}
private partial class TestLoadingLayer : LoadingLayer
{
public TestLoadingLayer(bool dimBackground = false, bool withBox = true)
@@ -80,6 +80,35 @@ 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,
});
AddUntilStep("3 panels visible", () => this.ChildrenOfType<ModPresetPanel>().Count() == 3);
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()
{
@@ -12,6 +12,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Framework.Localisation;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Framework.Utils;
using osu.Game.Configuration;
@@ -25,6 +26,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Screens;
using osu.Game.Screens.Footer;
using osu.Game.Tests.Mods;
using osuTK;
@@ -33,17 +35,19 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.UserInterface
{
[TestFixture]
public partial class TestSceneModSelectOverlay : OsuManualInputManagerTestScene
public partial class TestSceneModSelectOverlay : ScreenTestScene
{
protected override bool UseFreshStoragePerRun => true;
private RulesetStore rulesetStore = null!;
private TestModSelectOverlay modSelectOverlay = null!;
private TestModSelectOverlayScreen screen = null!;
[Resolved]
private OsuConfigManager configManager { get; set; } = null!;
private ModSelectOverlay modSelectOverlay => screen.Overlay;
[BackgroundDependencyLoader]
private void load()
{
@@ -52,9 +56,10 @@ namespace osu.Game.Tests.Visual.UserInterface
}
[SetUpSteps]
public void SetUpSteps()
public override void SetUpSteps()
{
AddStep("clear contents", Clear);
base.SetUpSteps();
AddStep("reset ruleset", () => Ruleset.Value = rulesetStore.GetRuleset(0));
AddStep("reset mods", () => SelectedMods.SetDefault());
AddStep("reset config", () => configManager.SetValue(OsuSetting.ModSelectTextSearchStartsActive, true));
@@ -97,29 +102,8 @@ namespace osu.Game.Tests.Visual.UserInterface
private void createScreen()
{
AddStep("create screen", () =>
{
var receptor = new ScreenFooter.BackReceptor();
var footer = new ScreenFooter(receptor);
Child = new DependencyProvidingContainer
{
RelativeSizeAxes = Axes.Both,
CachedDependencies = new[] { (typeof(ScreenFooter), (object)footer) },
Children = new Drawable[]
{
receptor,
modSelectOverlay = new TestModSelectOverlay
{
RelativeSizeAxes = Axes.Both,
State = { Value = Visibility.Visible },
Beatmap = { Value = Beatmap.Value },
SelectedMods = { BindTarget = SelectedMods },
},
footer,
}
};
});
AddStep("create screen", () => LoadScreen(screen = new TestModSelectOverlayScreen { SelectedMods = { BindTarget = SelectedMods } }));
AddUntilStep("wait until screen is loaded", () => screen.IsLoaded, () => Is.True);
waitForColumnLoad();
}
@@ -306,29 +290,30 @@ namespace osu.Game.Tests.Visual.UserInterface
[Test]
public void TestSettingsNotCrossPolluting()
{
TestScreenWithTwoOverlays screenWithTwoOverlays = null!;
Bindable<IReadOnlyList<Mod>> selectedMods2 = null!;
ModSelectOverlay modSelectOverlay2 = null!;
createScreen();
AddStep("push screen", () =>
{
selectedMods2 = new Bindable<IReadOnlyList<Mod>>(new Mod[] { new OsuModDifficultyAdjust() });
LoadScreen(screen = screenWithTwoOverlays = new TestScreenWithTwoOverlays
{
SelectedMods = { BindTarget = SelectedMods },
SelectedMods2 = { BindTarget = selectedMods2 },
});
});
AddStep("wait until screen is loaded", () => screenWithTwoOverlays.IsCurrentScreen());
waitForColumnLoad();
AddStep("select difficulty adjust via panel", () => getPanelForMod(typeof(OsuModDifficultyAdjust)).TriggerClick());
AddStep("set setting", () => modSelectOverlay.ChildrenOfType<RoundedSliderBar<float>>().First().Current.Value = 8);
AddStep("set setting", () => screenWithTwoOverlays.Overlay.ChildrenOfType<RoundedSliderBar<float>>().First().Current.Value = 8);
AddAssert("ensure setting is propagated", () => SelectedMods.Value.OfType<OsuModDifficultyAdjust>().Single().CircleSize.Value == 8);
AddStep("create second bindable", () => selectedMods2 = new Bindable<IReadOnlyList<Mod>>(new Mod[] { new OsuModDifficultyAdjust() }));
AddStep("create second overlay", () =>
{
Add(modSelectOverlay2 = new UserModSelectOverlay().With(d =>
{
d.Origin = Anchor.TopCentre;
d.Anchor = Anchor.TopCentre;
d.SelectedMods.BindTarget = selectedMods2;
}));
});
AddStep("show", () => modSelectOverlay2.Show());
AddStep("hide first overlay", () => screenWithTwoOverlays.Overlay.Hide());
AddStep("show second overlay", () => screenWithTwoOverlays.SecondOverlay.Show());
AddAssert("ensure first is unchanged", () => SelectedMods.Value.OfType<OsuModDifficultyAdjust>().Single().CircleSize.Value == 8);
AddAssert("ensure second is default", () => selectedMods2.Value.OfType<OsuModDifficultyAdjust>().Single().CircleSize.Value == null);
@@ -481,6 +466,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddStep("set customized mod externally", () => SelectedMods.Value = new[] { new OsuModDoubleTime { SpeedChange = { Value = 1.01 } } });
AddAssert("setting remains", () => (SelectedMods.Value.SingleOrDefault() as OsuModDoubleTime)?.SpeedChange.Value == 1.01);
AddStep("exit screen", () => Stack.Exit());
createScreen();
AddAssert("setting remains", () => (SelectedMods.Value.SingleOrDefault() as OsuModDoubleTime)?.SpeedChange.Value == 1.01);
}
@@ -797,16 +783,11 @@ namespace osu.Game.Tests.Visual.UserInterface
[Test]
public void TestColumnHidingOnIsValidChange()
{
AddStep("create screen", () => Child = modSelectOverlay = new TestModSelectOverlay
{
RelativeSizeAxes = Axes.Both,
State = { Value = Visibility.Visible },
SelectedMods = { BindTarget = SelectedMods },
IsValidMod = mod => mod.Type == ModType.DifficultyIncrease || mod.Type == ModType.Conversion
});
waitForColumnLoad();
createScreen();
changeRuleset(0);
AddStep("set filter for 2 columns", () => modSelectOverlay.IsValidMod = mod => mod.Type is ModType.DifficultyIncrease or ModType.Conversion);
AddAssert("two columns visible", () => this.ChildrenOfType<ModColumn>().Count(col => col.IsPresent) == 2);
AddStep("unset filter", () => modSelectOverlay.IsValidMod = _ => true);
@@ -816,9 +797,7 @@ namespace osu.Game.Tests.Visual.UserInterface
AddAssert("no columns visible", () => this.ChildrenOfType<ModColumn>().All(col => !col.IsPresent));
AddStep("hide", () => modSelectOverlay.Hide());
AddStep("set filter for 3 columns", () => modSelectOverlay.IsValidMod = mod => mod.Type == ModType.DifficultyReduction
|| mod.Type == ModType.Automation
|| mod.Type == ModType.Conversion);
AddStep("set filter for 3 columns", () => modSelectOverlay.IsValidMod = mod => mod.Type is ModType.DifficultyReduction or ModType.Automation or ModType.Conversion);
AddStep("show", () => modSelectOverlay.Show());
AddUntilStep("3 columns visible", () => this.ChildrenOfType<ModColumn>().Count(col => col.IsPresent) == 3);
@@ -830,13 +809,7 @@ namespace osu.Game.Tests.Visual.UserInterface
[Test]
public void TestColumnHidingOnTextFilterChange()
{
AddStep("create screen", () => Child = modSelectOverlay = new TestModSelectOverlay
{
RelativeSizeAxes = Axes.Both,
State = { Value = Visibility.Visible },
SelectedMods = { BindTarget = SelectedMods }
});
waitForColumnLoad();
createScreen();
changeRuleset(0);
AddAssert("all columns visible", () => this.ChildrenOfType<ModColumn>().All(col => col.IsPresent));
@@ -854,13 +827,7 @@ namespace osu.Game.Tests.Visual.UserInterface
[Test]
public void TestHidingOverlayClearsTextSearch()
{
AddStep("create screen", () => Child = modSelectOverlay = new TestModSelectOverlay
{
RelativeSizeAxes = Axes.Both,
State = { Value = Visibility.Visible },
SelectedMods = { BindTarget = SelectedMods }
});
waitForColumnLoad();
createScreen();
changeRuleset(0);
AddAssert("all columns visible", () => this.ChildrenOfType<ModColumn>().All(col => col.IsPresent));
@@ -1019,8 +986,8 @@ namespace osu.Game.Tests.Visual.UserInterface
{
selectedMods = new Bindable<IReadOnlyList<Mod>>([]);
modSelectOverlay.SelectedMods.UnbindFrom(SelectedMods);
modSelectOverlay.SelectedMods.BindTo(selectedMods);
screen.SelectedMods.UnbindFrom(SelectedMods);
screen.SelectedMods.BindTo(selectedMods);
});
AddStep("activate PF", () => selectedMods.Value = [new OsuModPerfect()]);
@@ -1066,11 +1033,79 @@ namespace osu.Game.Tests.Visual.UserInterface
rulesetStore.Dispose();
}
private partial class TestModSelectOverlay : UserModSelectOverlay
private partial class TestModSelectOverlayScreen : OsuScreen
{
public TestModSelectOverlay()
public readonly Bindable<IReadOnlyList<Mod>> SelectedMods = new Bindable<IReadOnlyList<Mod>>();
public override bool ShowFooter => true;
public ModSelectOverlay Overlay = null!;
private IDisposable? firstOverlayRegistration;
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
[Resolved]
protected IOverlayManager? OverlayManager { get; private set; }
[BackgroundDependencyLoader]
private void load()
{
ShowPresets = true;
LoadComponent(Overlay = new UserModSelectOverlay
{
RelativeSizeAxes = Axes.Both,
State = { Value = Visibility.Visible },
Beatmap = { Value = Beatmap.Value },
SelectedMods = { BindTarget = SelectedMods },
ShowPresets = true,
});
}
protected override void LoadComplete()
{
base.LoadComplete();
firstOverlayRegistration = OverlayManager?.RegisterBlockingOverlay(Overlay);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
firstOverlayRegistration?.Dispose();
}
}
private partial class TestScreenWithTwoOverlays : TestModSelectOverlayScreen
{
public readonly Bindable<IReadOnlyList<Mod>> SelectedMods2 = new Bindable<IReadOnlyList<Mod>>([]);
public ModSelectOverlay SecondOverlay = null!;
private IDisposable? secondOverlayRegistration;
[BackgroundDependencyLoader]
private void load()
{
LoadComponent(SecondOverlay = new UserModSelectOverlay
{
Origin = Anchor.TopCentre,
Anchor = Anchor.TopCentre,
SelectedMods = { BindTarget = SelectedMods2 },
});
}
protected override void LoadComplete()
{
base.LoadComplete();
secondOverlayRegistration = OverlayManager?.RegisterBlockingOverlay(SecondOverlay);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
secondOverlayRegistration?.Dispose();
}
}
@@ -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";
}
}
@@ -0,0 +1,365 @@
// Copyright (c) ppy Pty Ltd <contact@ppy.sh>. Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Screens;
using osu.Framework.Testing;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays;
using osu.Game.Overlays.Mods;
using osu.Game.Screens;
using osu.Game.Screens.Footer;
using osu.Game.Screens.SelectV2;
namespace osu.Game.Tests.Visual.UserInterface
{
public partial class TestSceneScreenFooter : ScreenTestScene
{
[Test]
public void TestButtonsIn()
{
AddStep("push empty screen", () => LoadScreen(new TestScreen()));
AddStep("push screen", () => LoadScreen(new TestScreen
{
CreateButtons = () => new[]
{
new ScreenFooterButton { Text = "Button 1", Action = () => { } },
new ScreenFooterButton { Text = "Button 2", Action = () => { } },
new ScreenFooterButton { Text = "Button 3", Action = () => { } },
},
}));
}
[Test]
public void TestButtonsOut()
{
AddStep("push empty screen", () => LoadScreen(new TestScreen()));
AddStep("push screen", () => LoadScreen(new TestScreen
{
CreateButtons = () => new[]
{
new ScreenFooterButton { Text = "Button 1", Action = () => { } },
new ScreenFooterButton { Text = "Button 2", Action = () => { } },
new ScreenFooterButton { Text = "Button 3", Action = () => { } },
},
}));
AddStep("exit screen", () => Stack.Exit());
}
[Test]
public void TestReplaceButtons()
{
AddStep("push first screen", () => LoadScreen(new TestScreen
{
CreateButtons = () => new[]
{
new ScreenFooterButton { Text = "Button 1", Action = () => { } },
new ScreenFooterButton { Text = "Button 2", Action = () => { } },
new ScreenFooterButton { Text = "Button 3", Action = () => { } },
},
}));
AddStep("push second screen", () => LoadScreen(new TestScreen
{
CreateButtons = () => new[]
{
new ScreenFooterButton { Text = "Button 4", Action = () => { } },
new ScreenFooterButton { Text = "Button 5", Action = () => { } },
new ScreenFooterButton { Text = "Button 6", Action = () => { } },
},
}));
}
[Test]
public void TestFooterVisibility()
{
TestScreen screen = null!;
TestScreen screenWithoutFooter = null!;
AddAssert("footer hidden", () => ScreenFooter.State.Value, () => Is.EqualTo(Visibility.Hidden));
AddStep("push screen", () => LoadScreen(screen = new TestScreen
{
CreateButtons = () => new[]
{
new ScreenFooterButton { Text = "Button 1", Action = () => { } },
new ScreenFooterButton { Text = "Button 2", Action = () => { } },
new ScreenFooterButton { Text = "Button 3", Action = () => { } },
},
}));
AddUntilStep("wait until screen is loaded", () => screen.IsCurrentScreen(), () => Is.True);
AddAssert("footer shown", () => ScreenFooter.State.Value, () => Is.EqualTo(Visibility.Visible));
AddStep("push screen with no footer", () => LoadScreen(screenWithoutFooter = new TestScreen(showFooter: false)));
AddUntilStep("wait until screen is loaded", () => screenWithoutFooter.IsCurrentScreen(), () => Is.True);
AddAssert("footer hidden", () => ScreenFooter.State.Value, () => Is.EqualTo(Visibility.Hidden));
AddStep("exit screen", () => Stack.Exit());
AddUntilStep("wait until screen is loaded", () => screen.IsCurrentScreen(), () => Is.True);
AddAssert("footer shown", () => ScreenFooter.State.Value, () => Is.EqualTo(Visibility.Visible));
}
[Test]
public void TestExternalOverlayContent()
{
TestScreen screen = null!;
AddStep("push screen", () =>
{
ShearedOverlayContainer overlay = new TestShearedOverlayContainer();
LoadScreen(screen = new TestScreen
{
Overlay = overlay,
CreateButtons = () => new[]
{
new ScreenFooterButton(overlay)
{
AccentColour = Dependencies.Get<OsuColour>().Orange1,
Icon = FontAwesome.Solid.Toolbox,
Text = "One",
},
new ScreenFooterButton { Text = "Two", Action = () => { } },
new ScreenFooterButton { Text = "Three", Action = () => { } },
},
});
});
AddUntilStep("wait until screen is loaded", () => screen.IsCurrentScreen(), () => Is.True);
AddStep("show overlay", () => screen.Overlay.Show());
contentDisplayed();
AddAssert("other buttons hidden", () => ScreenFooter.ChildrenOfType<ScreenFooterButton>().Skip(1).All(b => b.Child.Parent!.Y > 0));
AddStep("hide overlay", () => screen.Overlay.Hide());
contentHidden();
AddAssert("other buttons returned", () => ScreenFooter.ChildrenOfType<ScreenFooterButton>().Skip(1).All(b => b.ChildrenOfType<Container>().First().Y == 0));
}
[Test]
public void TestTemporarilyShowFooter()
{
TestScreen screen = null!;
AddStep("push screen", () => LoadScreen(screen = new TestScreen(showFooter: false)));
AddUntilStep("wait until screen is loaded", () => screen.IsCurrentScreen(), () => Is.True);
AddAssert("footer hidden", () => ScreenFooter.State.Value, () => Is.EqualTo(Visibility.Hidden));
AddStep("show overlay", () => screen.Overlay.Show());
AddAssert("footer shown", () => ScreenFooter.State.Value, () => Is.EqualTo(Visibility.Visible));
contentDisplayed();
AddStep("hide overlay", () => screen.Overlay.Hide());
AddAssert("footer hidden", () => ScreenFooter.State.Value, () => Is.EqualTo(Visibility.Hidden));
contentHidden();
}
[Test]
public void TestShowOverlayHidesOtherOverlays()
{
TestScreen screen = null!;
AddStep("push screen", () =>
{
ShearedOverlayContainer overlay = new TestShearedOverlayContainer();
ModSelectOverlay secondOverlay = new ModSelectOverlay();
LoadScreen(screen = new TestScreen
{
Overlay = overlay,
SecondOverlay = secondOverlay,
CreateButtons = () => new[]
{
new ScreenFooterButton(overlay)
{
AccentColour = Dependencies.Get<OsuColour>().Orange1,
Icon = FontAwesome.Solid.Toolbox,
Text = "One",
},
new FooterButtonMods(secondOverlay),
new ScreenFooterButton { Text = "Two", Action = () => { } },
new ScreenFooterButton { Text = "Three", Action = () => { } },
},
});
});
AddUntilStep("wait until screen is loaded", () => screen.IsCurrentScreen(), () => Is.True);
AddStep("show mods overlay", () => ScreenFooter.ChildrenOfType<FooterButtonMods>().First().TriggerClick());
AddUntilStep("wait until overlay is shown", () => screen.SecondOverlay.State.Value, () => Is.EqualTo(Visibility.Visible));
AddAssert("first button still visible", () => ScreenFooter.ChildrenOfType<ScreenFooterButton>().First(b => b.Text == "One").Y, () => Is.EqualTo(0));
AddStep("show test overlay", () => ScreenFooter.ChildrenOfType<ScreenFooterButton>().First(b => b.Text == "One").TriggerClick());
AddUntilStep("wait until overlay is shown", () => screen.Overlay.State.Value, () => Is.EqualTo(Visibility.Visible));
AddAssert("mod overlay is hidden", () => screen.SecondOverlay.State.Value, () => Is.EqualTo(Visibility.Hidden));
AddStep("hide test overlay", () => screen.Overlay.Hide());
contentHidden();
AddAssert("other buttons returned", () => ScreenFooter.ChildrenOfType<ScreenFooterButton>().Skip(1).All(b => b.ChildrenOfType<Container>().First().Y == 0));
}
[Test]
public void TestButtonResizedAfterFooterIsDisplayed()
{
TestScreen screen = null!;
const float initial_width = 116;
const float width_increase = 124;
float secondButtonX = 0;
float overlayContentX = 0;
AddStep("push screen", () =>
{
ShearedOverlayContainer overlay = new TestShearedOverlayContainer();
LoadScreen(screen = new TestScreen
{
Overlay = overlay,
CreateButtons = () => new[]
{
new ScreenFooterButton(overlay)
{
AccentColour = Dependencies.Get<OsuColour>().Orange1,
Icon = FontAwesome.Solid.Toolbox,
Text = "One",
},
new ScreenFooterButton { Text = "Two", Action = () => { } },
new ScreenFooterButton { Text = "Three", Action = () => { } },
},
});
});
AddUntilStep("wait until screen is loaded", () => screen.IsCurrentScreen(), () => Is.True);
AddStep("save second button position", () => secondButtonX = ScreenFooter.ChildrenOfType<ScreenFooterButton>().ElementAt(1).X);
AddStep("resize active button", () => ScreenFooter.ChildrenOfType<ScreenFooterButton>().First().ResizeWidthTo(initial_width + width_increase, 300, Easing.OutQuint));
AddUntilStep("second button moved", () => ScreenFooter.ChildrenOfType<ScreenFooterButton>().ElementAt(1).X, () => Is.EqualTo(secondButtonX + width_increase).Within(0.001));
AddStep("resize active button back", () => this.ChildrenOfType<ScreenFooterButton>().First().ResizeWidthTo(initial_width, 300, Easing.OutQuint));
AddUntilStep("second button moved back", () => ScreenFooter.ChildrenOfType<ScreenFooterButton>().ElementAt(1).X, () => Is.EqualTo(secondButtonX).Within(0.001));
AddStep("show overlay", () => screen.Overlay.Show());
contentDisplayed();
AddAssert("other buttons hidden", () => ScreenFooter.ChildrenOfType<ScreenFooterButton>().Skip(1).All(b => b.Child.Parent!.Y > 0));
AddStep("save overlay content position", () => overlayContentX = ScreenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().First().Parent!.Parent!.X);
AddStep("resize active button", () => ScreenFooter.ChildrenOfType<ScreenFooterButton>().First().ResizeWidthTo(initial_width + width_increase, 300, Easing.OutQuint));
AddUntilStep("overlay content moved", () => ScreenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().First().Parent!.Parent!.X, () => Is.EqualTo(overlayContentX + width_increase).Within(0.001));
AddStep("resize active button back", () => this.ChildrenOfType<ScreenFooterButton>().First().ResizeWidthTo(initial_width, 300, Easing.OutQuint));
AddUntilStep("overlay content moved back", () => ScreenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().First().Parent!.Parent!.X, () => Is.EqualTo(overlayContentX).Within(0.001));
AddStep("hide overlay", () => screen.Overlay.Hide());
contentHidden();
AddUntilStep("other buttons returned", () => ScreenFooter.ChildrenOfType<ScreenFooterButton>().Skip(1).All(b => b.ChildrenOfType<Container>().First().Y == 0));
}
private void contentHidden()
{
AddUntilStep("content hidden from footer", () => ScreenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().SingleOrDefault()?.IsPresent != true);
}
private void contentDisplayed()
{
AddUntilStep("content displayed in footer", () => ScreenFooter.ChildrenOfType<TestShearedOverlayContainer.TestFooterContent>().Single().IsPresent);
}
private partial class TestScreen : OsuScreen
{
public override bool ShowFooter { get; }
public Func<IReadOnlyList<ScreenFooterButton>> CreateButtons = Array.Empty<ScreenFooterButton>;
public ShearedOverlayContainer Overlay = new TestShearedOverlayContainer();
public ShearedOverlayContainer SecondOverlay = new TestShearedOverlayContainer();
private IDisposable? overlayRegistration;
private IDisposable? secondOverlayRegistration;
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Blue);
[Resolved]
private IOverlayManager? overlayManager { get; set; }
public TestScreen(bool showFooter = true)
{
ShowFooter = showFooter;
}
[BackgroundDependencyLoader]
private void load()
{
LoadComponent(Overlay);
LoadComponent(SecondOverlay);
}
protected override void LoadComplete()
{
base.LoadComplete();
overlayRegistration = overlayManager?.RegisterBlockingOverlay(Overlay);
secondOverlayRegistration = overlayManager?.RegisterBlockingOverlay(SecondOverlay);
}
public override IReadOnlyList<ScreenFooterButton> CreateFooterButtons() => CreateButtons.Invoke();
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
overlayRegistration?.Dispose();
secondOverlayRegistration?.Dispose();
}
}
private partial class TestShearedOverlayContainer : ShearedOverlayContainer
{
public TestShearedOverlayContainer()
: base(OverlayColourScheme.Orange)
{
}
[BackgroundDependencyLoader]
private void load()
{
Header.Title = "Test overlay";
Header.Description = "An overlay that is made purely for testing purposes.";
}
public override VisibilityContainer CreateFooterContent() => new TestFooterContent();
public partial class TestFooterContent : VisibilityContainer
{
[BackgroundDependencyLoader]
private void load()
{
AutoSizeAxes = Axes.Both;
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Children = new[]
{
new ShearedButton(200) { Text = "Action #1", Action = () => { } },
new ShearedButton(140) { Text = "Action #2", Action = () => { } },
}
};
}
protected override void PopIn()
{
this.MoveToY(0, 400, Easing.OutQuint)
.FadeIn(400, Easing.OutQuint);
}
protected override void PopOut()
{
this.MoveToY(-20f, 200, Easing.OutQuint)
.FadeOut(200, Easing.OutQuint);
}
}
}
}
}
@@ -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]
@@ -22,7 +22,7 @@ namespace osu.Game.Tests.Visual.UserInterface
OsuSpriteText sort;
OsuSpriteText displayStyle;
Add(toolbar = new UserListToolbar
Add(toolbar = new UserListToolbar(true)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@@ -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
{
+37 -5
View File
@@ -75,6 +75,13 @@ namespace osu.Game.Beatmaps
currentMods.BindValueChanged(mods =>
{
// A change in bindable here doesn't guarantee that mods have actually changed.
// However, we *do* want to make sure that the mod *references* are the same;
// `SequenceEqual()` without a comparer would fall back to `IEquatable`.
// Failing to ensure reference equality can cause setting change tracking to fail later.
if (mods.OldValue.SequenceEqual(mods.NewValue, ReferenceEqualityComparer.Instance))
return;
modSettingChangeTracker?.Dispose();
Scheduler.AddOnce(updateTrackedBindables);
@@ -82,15 +89,37 @@ namespace osu.Game.Beatmaps
modSettingChangeTracker = new ModSettingChangeTracker(mods.NewValue);
modSettingChangeTracker.SettingChanged += _ =>
{
debouncedModSettingsChange?.Cancel();
debouncedModSettingsChange = Scheduler.AddDelayed(updateTrackedBindables, 100);
lock (bindableUpdateLock)
{
debouncedModSettingsChange?.Cancel();
debouncedModSettingsChange = Scheduler.AddDelayed(updateTrackedBindables, 100);
}
};
}, true);
}
public void Invalidate(IBeatmapInfo beatmap)
/// <summary>
/// Notify this cache that a beatmap has been invalidated/updated.
/// </summary>
/// <param name="oldBeatmap">The old beatmap model.</param>
/// <param name="newBeatmap">The updated beatmap model.</param>
public void Invalidate(IBeatmapInfo oldBeatmap, IBeatmapInfo newBeatmap)
{
base.Invalidate(lookup => lookup.BeatmapInfo.Equals(beatmap));
base.Invalidate(lookup => lookup.BeatmapInfo.Equals(oldBeatmap));
lock (bindableUpdateLock)
{
bool trackedBindablesRefreshRequired = false;
foreach (var bsd in trackedBindables.Where(bsd => bsd.BeatmapInfo.Equals(oldBeatmap)))
{
bsd.BeatmapInfo = newBeatmap;
trackedBindablesRefreshRequired = true;
}
if (trackedBindablesRefreshRequired)
Scheduler.AddOnce(updateTrackedBindables);
}
}
/// <summary>
@@ -195,6 +224,9 @@ namespace osu.Game.Beatmaps
{
lock (bindableUpdateLock)
{
debouncedModSettingsChange?.Cancel();
debouncedModSettingsChange = null;
trackedUpdateCancellationSource.Cancel();
trackedUpdateCancellationSource = new CancellationTokenSource();
@@ -348,7 +380,7 @@ namespace osu.Game.Beatmaps
private class BindableStarDifficulty : Bindable<StarDifficulty>
{
public readonly IBeatmapInfo BeatmapInfo;
public IBeatmapInfo BeatmapInfo;
public readonly CancellationToken CancellationToken;
public BindableStarDifficulty(IBeatmapInfo beatmapInfo, CancellationToken cancellationToken)
+6 -2
View File
@@ -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);
@@ -154,7 +154,11 @@ namespace osu.Game.Beatmaps
{
DifficultyName = NamingUtils.GetNextBestName(targetBeatmapSet.Beatmaps.Select(b => b.DifficultyName), "New Difficulty")
};
var newBeatmap = new Beatmap { BeatmapInfo = newBeatmapInfo };
var newBeatmap = new Beatmap
{
BeatmapInfo = newBeatmapInfo,
Bookmarks = referenceWorkingBeatmap.Beatmap.Bookmarks.ToArray()
};
foreach (var timingPoint in referenceWorkingBeatmap.Beatmap.ControlPointInfo.TimingPoints)
newBeatmap.ControlPointInfo.Add(timingPoint.Time, timingPoint.DeepClone());
+3 -6
View File
@@ -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;
@@ -53,13 +52,11 @@ namespace osu.Game.Beatmaps
foreach (BeatmapInfo beatmap in beatmapSet.Beatmaps)
{
difficultyCache.Invalidate(beatmap);
var working = workingBeatmapCache.GetWorkingBeatmap(beatmap);
difficultyCache.Invalidate(beatmap, working.BeatmapInfo);
var ruleset = working.BeatmapInfo.Ruleset.CreateInstance();
Debug.Assert(ruleset != null);
var calculator = ruleset.CreateDifficultyCalculator(working);
beatmap.StarRating = calculator.Calculate().StarRating;
@@ -296,20 +296,20 @@ namespace osu.Game.Beatmaps.Drawables.Cards
return original;
}
statisticsContainer.Content[0][0] = withMargin(new FavouritesStatistic(BeatmapSet)
{
Current = FavouriteState,
});
statisticsContainer.Content[1][0] = withMargin(new PlayCountStatistic(BeatmapSet));
var hypesStatistic = HypesStatistic.CreateFor(BeatmapSet);
if (hypesStatistic != null)
statisticsContainer.Content[0][1] = withMargin(hypesStatistic);
statisticsContainer.Content[0][0] = withMargin(hypesStatistic);
var nominationsStatistic = NominationsStatistic.CreateFor(BeatmapSet);
if (nominationsStatistic != null)
statisticsContainer.Content[1][1] = withMargin(nominationsStatistic);
statisticsContainer.Content[1][0] = withMargin(nominationsStatistic);
statisticsContainer.Content[0][1] = withMargin(new PlayCountStatistic(BeatmapSet));
statisticsContainer.Content[1][1] = withMargin(new FavouritesStatistic(BeatmapSet)
{
Current = FavouriteState,
});
var dateStatistic = BeatmapCardDateStatistic.CreateFor(BeatmapSet);
if (dateStatistic != null)
@@ -278,8 +278,8 @@ namespace osu.Game.Beatmaps.Drawables.Cards
if (nominationsStatistic != null)
yield return nominationsStatistic;
yield return new FavouritesStatistic(BeatmapSet) { Current = FavouriteState };
yield return new PlayCountStatistic(BeatmapSet);
yield return new FavouritesStatistic(BeatmapSet) { Current = FavouriteState };
var dateStatistic = BeatmapCardDateStatistic.CreateFor(BeatmapSet);
if (dateStatistic != null)

Some files were not shown because too many files have changed in this diff Show More