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

Compare commits

...

251 Commits

371 changed files with 7802 additions and 3968 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 -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;
}
+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;
}
}
}
}
@@ -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;
}
}
}
+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;
+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, () => { });
}
}
}
@@ -27,7 +27,7 @@ using osuTK.Input;
namespace osu.Game.Tests.Visual.Multiplayer
{
public partial class TestScenePlaylistsSongSelectV2 : OnlinePlayTestScene
public partial class TestScenePlaylistsSongSelect : OnlinePlayTestScene
{
private RulesetStore rulesets = null!;
private BeatmapManager manager = null!;
@@ -164,20 +164,20 @@ namespace osu.Game.Tests.Visual.Multiplayer
AddAssert("freestyle enabled", () => songSelect.Freestyle.Value, () => Is.True);
AddStep("click icon in free mods button", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<FooterButtonFreeModsV2>().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));
AddStep("toggle freestyle off", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<FooterButtonFreestyleV2>().Single());
InputManager.MoveMouseTo(this.ChildrenOfType<FooterButtonFreestyle>().Single());
InputManager.Click(MouseButton.Left);
});
AddAssert("freestyle disabled", () => songSelect.Freestyle.Value, () => Is.False);
AddStep("click icon in free mods button", () =>
{
InputManager.MoveMouseTo(this.ChildrenOfType<FooterButtonFreeModsV2>().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));
@@ -191,7 +191,7 @@ namespace osu.Game.Tests.Visual.Multiplayer
rulesets.Dispose();
}
private partial class TestPlaylistsSongSelect : PlaylistsSongSelectV2
private partial class TestPlaylistsSongSelect : PlaylistsSongSelect
{
public new IBindable<bool> Freestyle => base.Freestyle;
@@ -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,7 +94,7 @@ namespace osu.Game.Tests.Visual.Navigation
AddStep("edit playlist", () => InputManager.Key(Key.Enter));
AddUntilStep("wait for song select", () => playlistScreen.CurrentSubScreen is PlaylistsSongSelectV2 songSelect && songSelect.IsLoaded && !songSelect.IsFiltering);
AddUntilStep("wait for song select", () => playlistScreen.CurrentSubScreen is PlaylistsSongSelect songSelect && songSelect.IsLoaded && !songSelect.IsFiltering);
AddUntilStep("wait for selection", () => !Game.Beatmap.IsDefault);
@@ -109,7 +109,7 @@ namespace osu.Game.Tests.Visual.Navigation
InputManager.Click(MouseButton.Left);
});
AddUntilStep("wait for song select", () => playlistScreen.CurrentSubScreen is PlaylistsSongSelectV2 songSelect && songSelect.IsLoaded && !songSelect.IsFiltering);
AddUntilStep("wait for song select", () => playlistScreen.CurrentSubScreen is PlaylistsSongSelect songSelect && songSelect.IsLoaded && !songSelect.IsFiltering);
AddStep("press home button", () =>
{
@@ -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" },
@@ -17,13 +17,13 @@ namespace osu.Game.Tests.Visual.Playlists
[Cached]
private OverlayColourProvider colourProvider = new OverlayColourProvider(OverlayColourScheme.Aquamarine);
private readonly FooterButtonFreeModsV2 button;
private readonly FooterButtonFreeMods button;
public TestSceneFooterButtonFreeModsV2()
{
ModSelectOverlay modSelectOverlay;
Add(modSelectOverlay = new TestModSelectOverlay());
Add(button = new FooterButtonFreeModsV2(modSelectOverlay)
Add(button = new FooterButtonFreeMods(modSelectOverlay)
{
Anchor = Anchor.Centre,
Origin = Anchor.CentreLeft,
@@ -15,7 +15,7 @@ namespace osu.Game.Tests.Visual.Playlists
public TestSceneFooterButtonFreestyleV2()
{
Add(new FooterButtonFreestyleV2
Add(new FooterButtonFreestyle
{
Anchor = Anchor.Centre,
Origin = Anchor.CentreLeft,
@@ -18,7 +18,7 @@ namespace osu.Game.Tests.Visual.Playlists
{
base.SetUpSteps();
AddStep("add tray", () => Child = new PlaylistsSongSelectV2.PlaylistTray(room = new Room())
AddStep("add tray", () => Child = new PlaylistsSongSelect.PlaylistTray(room = new Room())
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
@@ -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.
@@ -169,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]
@@ -212,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();
}
}
@@ -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);
}
}
}
}
}
@@ -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)
+5 -1
View File
@@ -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)
@@ -17,7 +17,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Statistics
{
this.dateTime = dateTime;
Icon = FontAwesome.Regular.CheckCircle;
Icon = FontAwesome.Solid.CheckCircle;
Text = dateTime.ToLocalisedMediumDate();
}
@@ -15,7 +15,7 @@ namespace osu.Game.Beatmaps.Drawables.Cards.Statistics
{
public PlayCountStatistic(IBeatmapSetOnlineInfo onlineInfo)
{
Icon = FontAwesome.Regular.PlayCircle;
Icon = FontAwesome.Solid.PlayCircle;
Text = onlineInfo.PlayCount.ToMetric(decimals: 1);
TooltipText = BeatmapsStrings.PanelPlaycount(onlineInfo.PlayCount.ToLocalisableString(@"N0"));
}
@@ -170,21 +170,21 @@ namespace osu.Game.Beatmaps.Formats
{
SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.StartTime + CONTROL_POINT_LENIENCY + 1)
?? SampleControlPoint.DEFAULT;
hitObject.Samples = hitObject.Samples.Select(o => sampleControlPoint.ApplyTo(o)).ToList();
hitObject.Samples = hitObject.Samples.Select(sampleControlPoint.ApplyTo).ToList();
for (int i = 0; i < hasRepeats.NodeSamples.Count; i++)
{
double time = hitObject.StartTime + i * hasRepeats.Duration / hasRepeats.SpanCount() + CONTROL_POINT_LENIENCY;
var nodeSamplePoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(time) ?? SampleControlPoint.DEFAULT;
hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(o => nodeSamplePoint.ApplyTo(o)).ToList();
hasRepeats.NodeSamples[i] = hasRepeats.NodeSamples[i].Select(nodeSamplePoint.ApplyTo).ToList();
}
}
else
{
SampleControlPoint sampleControlPoint = (beatmap.ControlPointInfo as LegacyControlPointInfo)?.SamplePointAt(hitObject.GetEndTime() + CONTROL_POINT_LENIENCY)
?? SampleControlPoint.DEFAULT;
hitObject.Samples = hitObject.Samples.Select(o => sampleControlPoint.ApplyTo(o)).ToList();
hitObject.Samples = hitObject.Samples.Select(sampleControlPoint.ApplyTo).ToList();
}
}
+11
View File
@@ -4,6 +4,7 @@
#nullable disable
using System;
using System.Diagnostics;
using System.IO;
using System.Linq;
using JetBrains.Annotations;
@@ -104,6 +105,10 @@ namespace osu.Game.Beatmaps
beatmapInfo = beatmapInfo.Detach();
// If this ever gets hit, a request has arrived with an outdated BeatmapInfo.
// An outdated BeatmapInfo may contain a reference to a previous version of the beatmap's files on disk.
Debug.Assert(confirmFileHashIsUpToDate(beatmapInfo), "working beatmap returned with outdated path");
workingCache.Add(working = new BeatmapManagerWorkingBeatmap(beatmapInfo, this));
// best effort; may be higher than expected.
@@ -113,6 +118,12 @@ namespace osu.Game.Beatmaps
}
}
private bool confirmFileHashIsUpToDate(BeatmapInfo beatmapInfo)
{
string refetchPath = realm.Run(r => r.Find<BeatmapInfo>(beatmapInfo.ID)?.File?.File.Hash);
return refetchPath == null || refetchPath == beatmapInfo.File?.File.Hash;
}
#region IResourceStorageProvider
TextureStore IBeatmapResourceProvider.LargeTextureStore => largeTextureStore;
@@ -15,6 +15,7 @@ using osu.Game.Input;
using osu.Game.Input.Bindings;
using osu.Game.Localisation;
using osu.Game.Overlays;
using osu.Game.Overlays.Dashboard.Friends;
using osu.Game.Overlays.Mods.Input;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Edit.Compose.Components;
@@ -234,6 +235,9 @@ namespace osu.Game.Configuration
// intentionally uses `DateTime?` and not `DateTimeOffset?` because the latter fails due to `DateTimeOffset` not implementing `IConvertible`
SetDefault(OsuSetting.LastOnlineTagsPopulation, (DateTime?)null);
SetDefault(OsuSetting.DashboardSortMode, UserSortCriteria.LastVisit);
SetDefault(OsuSetting.DashboardDisplayStyle, OverlayPanelDisplayStyle.Card);
}
protected override bool CheckLookupContainsPrivateInformation(OsuSetting lookup)
@@ -486,5 +490,8 @@ namespace osu.Game.Configuration
LastOnlineTagsPopulation,
AutomaticallyAdjustBeatmapOffset,
DashboardSortMode,
DashboardDisplayStyle,
}
}
@@ -140,7 +140,7 @@ namespace osu.Game.Configuration
LabelText = attr.Label,
TooltipText = attr.Description,
Current = bNumber,
KeyboardStep = 0.1f,
KeyboardStep = bNumber.Precision,
};
break;
@@ -151,7 +151,7 @@ namespace osu.Game.Configuration
LabelText = attr.Label,
TooltipText = attr.Description,
Current = bNumber,
KeyboardStep = 0.1f,
KeyboardStep = (float)bNumber.Precision,
};
break;
@@ -161,7 +161,8 @@ namespace osu.Game.Configuration
{
LabelText = attr.Label,
TooltipText = attr.Description,
Current = bNumber
Current = bNumber,
KeyboardStep = bNumber.Precision,
};
break;
@@ -76,6 +76,15 @@ namespace osu.Game.Database
statistics.Value.Usage = cache.Count;
}
/// <summary>
/// Completely purge the cache.
/// </summary>
public virtual void Clear()
{
cache.Clear();
statistics.Value.Usage = 0;
}
protected bool CheckExists(TLookup lookup, [MaybeNullWhen(false)] out TValue value) =>
cache.TryGetValue(lookup, out value);
+5
View File
@@ -12,6 +12,7 @@ using osu.Game.Beatmaps;
using osu.Game.Extensions;
using osu.Game.Models;
using osu.Game.Overlays.Notifications;
using osu.Game.Utils;
using Realms;
namespace osu.Game.Database
@@ -87,6 +88,10 @@ namespace osu.Game.Database
public void AddFile(TModel item, Stream contents, string filename, Realm realm)
{
filename = filename.ToStandardisedPath();
if (FilesystemSanityCheckHelpers.IncursPathTraversalRisk(filename))
throw new InvalidOperationException($@"Filename ""{filename}"" is not allowed.");
var existing = item.GetFile(filename);
if (existing != null)
+17 -3
View File
@@ -17,6 +17,7 @@ using osu.Game.Extensions;
using osu.Game.IO.Archives;
using osu.Game.Models;
using osu.Game.Overlays.Notifications;
using osu.Game.Utils;
using Realms;
namespace osu.Game.Database
@@ -221,7 +222,15 @@ namespace osu.Game.Database
foreach (string piece in realmFile.Filename.Split('/').Select(f => f.GetValidFilename()))
destinationPath = Path.Combine(destinationPath, piece);
Directory.CreateDirectory(Path.GetDirectoryName(destinationPath)!);
string destinationDirectory = Path.GetDirectoryName(destinationPath)!;
if (!FilesystemSanityCheckHelpers.IsSubDirectory(parent: mountedPath, child: destinationDirectory))
{
Logger.Log($@"Skipping attempt to mount {realmFile.Filename} due to detected escape out of mounted path.", LoggingTarget.Database);
continue;
}
Directory.CreateDirectory(destinationDirectory);
// Consider using hard links here to make this instant.
using (var inStream = Files.Storage.GetStream(sourcePath))
@@ -361,6 +370,9 @@ namespace osu.Game.Database
// We intentionally delay adding to realm to avoid blocking on a write during disk operations.
foreach (var filenames in getShortenedFilenames(archive))
{
if (FilesystemSanityCheckHelpers.IncursPathTraversalRisk(filenames.shortened))
throw new InvalidOperationException($@"Filename ""{filenames.original}"" is not allowed.");
using (Stream s = archive.GetStream(filenames.original))
files.Add(new RealmNamedFileUsage(Files.Add(s, realm, false, parameters.PreferHardLinks), filenames.shortened));
}
@@ -474,8 +486,10 @@ namespace osu.Game.Database
foreach (RealmNamedFileUsage file in item.Files.Where(f => HashableFileTypes.Any(ext => f.Filename.EndsWith(ext, StringComparison.OrdinalIgnoreCase))).OrderBy(f => f.Filename))
{
using (Stream s = Files.Store.GetStream(file.File.GetStoragePath()))
s.CopyTo(hashable);
using (Stream? s = Files.Store.GetStream(file.File.GetStoragePath()))
{
s?.CopyTo(hashable);
}
}
if (hashable.Length > 0)
@@ -51,7 +51,7 @@ namespace osu.Game.Database
var beatmap = new Beatmap();
HitResult maxRulesetJudgement = ruleset.GetHitResults().First().result;
HitResult maxRulesetJudgement = ruleset.GetHitResultsForDisplay().First().result;
// This is a list of all results, ordered from best to worst.
// We are constructing a "best possible" score from the statistics provided because it's the best we can do.
@@ -33,6 +33,9 @@ namespace osu.Game.Graphics.Carousel
/// </summary>
protected partial class ScrollContainer : UserTrackingScrollContainer, IKeyBindingHandler<GlobalAction>
{
public Action? OnPageUp { get; init; }
public Action? OnPageDown { get; init; }
public readonly Container Panels;
public void SetLayoutHeight(float height) => Panels.Height = height;
@@ -127,6 +130,22 @@ namespace osu.Game.Graphics.Carousel
protected override bool IsDragging => base.IsDragging || AbsoluteScrolling;
protected override bool OnKeyDown(KeyDownEvent e)
{
switch (e.Key)
{
case Key.PageUp:
OnPageUp?.Invoke();
return true;
case Key.PageDown:
OnPageDown?.Invoke();
return true;
}
return base.OnKeyDown(e);
}
public bool OnPressed(KeyBindingPressEvent<GlobalAction> e)
{
switch (e.Action)
@@ -201,6 +220,8 @@ namespace osu.Game.Graphics.Carousel
private readonly Drawable box;
private bool capturingMouseDown;
protected override float MinimumDimSize => SCROLL_BAR_WIDTH * 3;
private const float expanded_size_ratio = 2;
@@ -261,6 +282,7 @@ namespace osu.Game.Graphics.Carousel
{
if (!base.OnMouseDown(e)) return false;
capturingMouseDown = true;
updateVisuals(e);
return true;
}
@@ -275,13 +297,14 @@ namespace osu.Game.Graphics.Carousel
{
if (e.Button != MouseButton.Left) return;
capturingMouseDown = false;
updateVisuals(e);
base.OnMouseUp(e);
}
private void updateVisuals(MouseEvent e)
{
if (IsDragged || e.PressedButtons.Contains(MouseButton.Left))
if (capturingMouseDown)
box.FadeColour(highlightColour, 100);
else if (IsHovered)
box.FadeColour(hoverColour, 100);
+81 -16
View File
@@ -317,6 +317,8 @@ namespace osu.Game.Graphics.Carousel
{
Masking = false,
RelativeSizeAxes = Axes.Both,
OnPageUp = () => Scheduler.AddOnce(traverseFromKey, new TraversalOperation(TraversalType.Page, -1)),
OnPageDown = () => Scheduler.AddOnce(traverseFromKey, new TraversalOperation(TraversalType.Page, 1)),
};
Items.BindCollectionChanged((_, args) =>
@@ -538,30 +540,40 @@ namespace osu.Game.Graphics.Carousel
}
return false;
}
void traverseFromKey(TraversalOperation traversal)
private void traverseFromKey(TraversalOperation traversal)
{
switch (traversal.Type)
{
switch (traversal.Type)
{
case TraversalType.Keyboard:
traverseKeyboardSelection(traversal.Direction);
break;
case TraversalType.Keyboard:
traverseKeyboardSelection(traversal.Direction);
break;
case TraversalType.Set:
traverseSetSelection(traversal.Direction);
break;
case TraversalType.Page:
traverseKeyboardPage(traversal.Direction);
break;
case TraversalType.Group:
traverseGroupSelection(traversal.Direction);
break;
case TraversalType.Set:
traverseSetSelection(traversal.Direction);
break;
default:
throw new ArgumentOutOfRangeException();
}
case TraversalType.Group:
traverseGroupSelection(traversal.Direction);
break;
default:
throw new ArgumentOutOfRangeException();
}
}
private enum TraversalType { Keyboard, Set, Group }
private enum TraversalType
{
Keyboard,
Set,
Page,
Group
}
private record TraversalOperation(TraversalType Type, int Direction);
@@ -617,6 +629,59 @@ namespace osu.Game.Graphics.Carousel
} while (newIndex != originalIndex);
}
/// <summary>
/// Performs a page-wise keyboard traversal in the carousel, moving the selection by approximately one "page" of items.
/// </summary>
/// <param name="direction">Positive for downwards, negative for upwards.</param>
private void traverseKeyboardPage(int direction)
{
if (carouselItems == null || carouselItems.Count == 0)
return;
int startIndex = currentKeyboardSelection.Index ?? (direction > 0 ? carouselItems.Count - 1 : 0);
// Compute the number of visible panels to treat as one page.
// Reduced by 50% to account for the search bar covering the top items.
int visiblePanelsCount = Math.Max(1, Scroll.Panels.Count / 2);
int visibleCount = 0;
int i = startIndex;
while (i >= 0 && i < carouselItems.Count)
{
i += direction;
if (i < 0 || i >= carouselItems.Count)
break;
var item = carouselItems[i];
if (!item.IsVisible)
continue;
visibleCount++;
if (visibleCount >= visiblePanelsCount)
{
setKeyboardSelection(item.Model);
ScrollToSelection();
playTraversalSound();
return;
}
}
// If we are at the beginning or end and there are not enough items left to scroll through a complete page, then we go to the last or first item.
var fallback = direction > 0
? carouselItems.LastOrDefault(x => x.IsVisible)
: carouselItems.FirstOrDefault(x => x.IsVisible);
if (fallback != null && !CheckModelEquality(fallback.Model, currentKeyboardSelection.Model))
{
setKeyboardSelection(fallback.Model);
ScrollToSelection();
playTraversalSound();
}
}
/// <summary>
/// Select the next valid group selection relative to a current selection.
/// This is generally for keyboard based traversal.
@@ -11,6 +11,7 @@ using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Localisation;
using osu.Framework.Graphics.Containers;
using osu.Framework.Utils;
namespace osu.Game.Graphics.Cursor
{
@@ -93,10 +94,10 @@ namespace osu.Game.Graphics.Cursor
protected override void PopIn()
{
instantMovement |= !IsPresent;
this.FadeIn(500, Easing.OutQuint);
this.FadeIn(300, Easing.OutQuint);
}
protected override void PopOut() => this.Delay(150).FadeOut(500, Easing.OutQuint);
protected override void PopOut() => this.Delay(150).FadeOut(300, Easing.OutQuint);
public override void Move(Vector2 pos)
{
@@ -107,7 +108,8 @@ namespace osu.Game.Graphics.Cursor
}
else
{
this.MoveTo(pos, 200, Easing.OutQuint);
// This method is called every frame so we can do this safely here.
Position = Interpolation.ValueAt(Time.Elapsed, Position, pos, 0, 120, Easing.OutQuint);
}
}
}

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