2021-04-27 17:03:49 +08:00
// 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.
2023-02-22 17:17:03 +08:00
using System.Collections.Generic ;
2023-04-30 08:00:35 +08:00
using System.IO ;
2022-03-21 16:01:46 +08:00
using System.Linq ;
2021-04-29 12:53:01 +08:00
using NUnit.Framework ;
2022-03-16 21:23:19 +08:00
using osu.Framework.Allocation ;
2023-04-30 08:00:35 +08:00
using osu.Framework.Extensions.IEnumerableExtensions ;
2023-02-22 16:56:59 +08:00
using osu.Framework.Extensions.ObjectExtensions ;
2021-04-30 11:35:58 +08:00
using osu.Framework.Graphics ;
2022-03-21 16:01:46 +08:00
using osu.Framework.Graphics.UserInterface ;
2023-04-30 08:00:35 +08:00
using osu.Framework.Input ;
2021-04-27 17:03:49 +08:00
using osu.Framework.Testing ;
2022-03-16 21:23:19 +08:00
using osu.Game.Overlays ;
2022-03-21 16:01:46 +08:00
using osu.Game.Overlays.Settings ;
2023-01-26 17:21:04 +08:00
using osu.Game.Overlays.SkinEditor ;
2021-04-28 14:14:48 +08:00
using osu.Game.Rulesets ;
using osu.Game.Rulesets.Osu ;
2023-02-16 14:24:37 +08:00
using osu.Game.Screens.Edit ;
2022-03-21 16:01:46 +08:00
using osu.Game.Screens.Play.HUD.HitErrorMeters ;
2022-07-28 16:58:07 +08:00
using osu.Game.Skinning ;
2023-03-07 13:22:19 +08:00
using osu.Game.Skinning.Components ;
using osuTK ;
2022-03-21 16:01:46 +08:00
using osuTK.Input ;
2021-04-27 17:03:49 +08:00
namespace osu.Game.Tests.Visual.Gameplay
{
2022-11-24 13:32:20 +08:00
public partial class TestSceneSkinEditor : PlayerTestScene
2021-04-27 17:03:49 +08:00
{
2023-02-22 16:56:59 +08:00
private SkinEditor skinEditor = null ! ;
2021-04-29 12:53:01 +08:00
2021-05-13 13:54:52 +08:00
protected override bool Autoplay = > true ;
2022-03-16 21:23:19 +08:00
[Cached]
private readonly OverlayColourProvider colourProvider = new OverlayColourProvider ( OverlayColourScheme . Blue ) ;
2023-02-16 14:24:37 +08:00
[Cached]
public readonly EditorClipboard Clipboard = new EditorClipboard ( ) ;
2023-02-22 17:17:03 +08:00
private SkinComponentsContainer targetContainer = > Player . ChildrenOfType < SkinComponentsContainer > ( ) . First ( ) ;
2021-04-27 17:03:49 +08:00
[SetUpSteps]
2021-04-29 12:53:01 +08:00
public override void SetUpSteps ( )
2021-04-27 17:03:49 +08:00
{
2021-04-29 12:53:01 +08:00
base . SetUpSteps ( ) ;
2021-04-27 17:03:49 +08:00
2023-02-22 17:17:03 +08:00
AddUntilStep ( "wait for hud load" , ( ) = > targetContainer . ComponentsLoaded ) ;
2022-07-28 16:58:07 +08:00
2021-05-10 21:36:20 +08:00
AddStep ( "reload skin editor" , ( ) = >
2021-04-29 12:53:01 +08:00
{
2023-02-22 16:56:59 +08:00
if ( skinEditor . IsNotNull ( ) )
skinEditor . Expire ( ) ;
2022-03-21 16:01:46 +08:00
Player . ScaleTo ( 0.4f ) ;
2021-04-29 12:53:01 +08:00
LoadComponentAsync ( skinEditor = new SkinEditor ( Player ) , Add ) ;
2021-04-27 17:03:49 +08:00
} ) ;
2023-02-22 16:56:59 +08:00
AddUntilStep ( "wait for loaded" , ( ) = > skinEditor . IsLoaded ) ;
2021-04-27 17:03:49 +08:00
}
2023-03-14 20:04:51 +08:00
[Test]
public void TestDragSelection ( )
{
BigBlackBox box1 = null ! ;
BigBlackBox box2 = null ! ;
BigBlackBox box3 = null ! ;
AddStep ( "Add big black boxes" , ( ) = >
{
var target = Player . ChildrenOfType < SkinComponentsContainer > ( ) . First ( ) ;
target . Add ( box1 = new BigBlackBox
{
Position = new Vector2 ( - 90 ) ,
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
} ) ;
target . Add ( box2 = new BigBlackBox
{
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
} ) ;
target . Add ( box3 = new BigBlackBox
{
Position = new Vector2 ( 90 ) ,
Anchor = Anchor . Centre ,
Origin = Anchor . Centre ,
} ) ;
} ) ;
2023-03-15 14:16:48 +08:00
// This step is specifically added to reproduce an edge case which was found during cyclic selection development.
// If everything is working as expected it should not affect the subsequent drag selections.
AddRepeatStep ( "Select top left" , ( ) = >
{
InputManager . MoveMouseTo ( box1 . ScreenSpaceDrawQuad . TopLeft + new Vector2 ( box1 . ScreenSpaceDrawQuad . Width / 8 ) ) ;
InputManager . Click ( MouseButton . Left ) ;
} , 2 ) ;
2023-03-14 20:04:51 +08:00
AddStep ( "Begin drag top left" , ( ) = >
{
InputManager . MoveMouseTo ( box1 . ScreenSpaceDrawQuad . TopLeft - new Vector2 ( box1 . ScreenSpaceDrawQuad . Width / 4 ) ) ;
InputManager . PressButton ( MouseButton . Left ) ;
} ) ;
2023-04-30 22:10:59 +08:00
AddStep ( "Drag to bottom right" , ( ) = >
{
InputManager . MoveMouseTo ( box3 . ScreenSpaceDrawQuad . TopRight + new Vector2 ( - box3 . ScreenSpaceDrawQuad . Width / 8 , box3 . ScreenSpaceDrawQuad . Height / 4 ) ) ;
} ) ;
2023-03-14 20:04:51 +08:00
2023-04-30 22:10:59 +08:00
AddStep ( "Release button" , ( ) = >
{
InputManager . ReleaseButton ( MouseButton . Left ) ;
} ) ;
2023-03-14 20:04:51 +08:00
AddAssert ( "First two boxes selected" , ( ) = > skinEditor . SelectedComponents , ( ) = > Is . EqualTo ( new [ ] { box1 , box2 } ) ) ;
AddStep ( "Begin drag bottom right" , ( ) = >
{
InputManager . MoveMouseTo ( box3 . ScreenSpaceDrawQuad . BottomRight + new Vector2 ( box3 . ScreenSpaceDrawQuad . Width / 4 ) ) ;
InputManager . PressButton ( MouseButton . Left ) ;
} ) ;
2023-04-30 22:10:59 +08:00
AddStep ( "Drag to top left" , ( ) = >
{
InputManager . MoveMouseTo ( box2 . ScreenSpaceDrawQuad . Centre - new Vector2 ( box2 . ScreenSpaceDrawQuad . Width / 4 ) ) ;
} ) ;
2023-03-14 20:04:51 +08:00
2023-04-30 22:10:59 +08:00
AddStep ( "Release button" , ( ) = >
{
InputManager . ReleaseButton ( MouseButton . Left ) ;
} ) ;
2023-03-14 20:04:51 +08:00
AddAssert ( "Last two boxes selected" , ( ) = > skinEditor . SelectedComponents , ( ) = > Is . EqualTo ( new [ ] { box2 , box3 } ) ) ;
2023-03-21 20:25:50 +08:00
// Test cyclic selection doesn't trigger in this state.
AddStep ( "click on black box stack" , ( ) = > InputManager . Click ( MouseButton . Left ) ) ;
AddAssert ( "Last two boxes still selected" , ( ) = > skinEditor . SelectedComponents , ( ) = > Is . EqualTo ( new [ ] { box2 , box3 } ) ) ;
2023-03-14 20:04:51 +08:00
}
2023-03-07 13:22:19 +08:00
[Test]
public void TestCyclicSelection ( )
{
2023-07-19 15:17:42 +08:00
List < SkinBlueprint > blueprints = new List < SkinBlueprint > ( ) ;
2023-03-07 13:22:19 +08:00
2023-07-19 15:17:42 +08:00
AddStep ( "clear list" , ( ) = > blueprints . Clear ( ) ) ;
for ( int i = 0 ; i < 3 ; i + + )
2023-03-07 13:22:19 +08:00
{
2023-07-19 15:17:42 +08:00
AddStep ( "Add big black box" , ( ) = >
{
InputManager . MoveMouseTo ( skinEditor . ChildrenOfType < BigBlackBox > ( ) . First ( ) ) ;
InputManager . Click ( MouseButton . Left ) ;
} ) ;
2023-03-07 13:22:19 +08:00
2023-07-19 15:17:42 +08:00
AddStep ( "store box" , ( ) = >
{
// Add blueprints one-by-one so we have a stable order for testing reverse cyclic selection against.
blueprints . Add ( skinEditor . ChildrenOfType < SkinBlueprint > ( ) . Single ( s = > s . IsSelected ) ) ;
} ) ;
}
2023-03-07 13:22:19 +08:00
2023-07-19 15:17:42 +08:00
AddAssert ( "Three black boxes added" , ( ) = > targetContainer . Components . OfType < BigBlackBox > ( ) . Count ( ) , ( ) = > Is . EqualTo ( 3 ) ) ;
2023-03-07 13:22:19 +08:00
2023-07-19 15:17:42 +08:00
AddAssert ( "Selection is last" , ( ) = > skinEditor . SelectedComponents . Single ( ) , ( ) = > Is . EqualTo ( blueprints [ 2 ] . Item ) ) ;
2023-03-07 13:22:19 +08:00
AddStep ( "move cursor to black box" , ( ) = >
{
// Slightly offset from centre to avoid random failures (see https://github.com/ppy/osu-framework/issues/5669).
InputManager . MoveMouseTo ( ( ( Drawable ) blueprints [ 0 ] . Item ) . ScreenSpaceDrawQuad . Centre + new Vector2 ( 1 ) ) ;
} ) ;
AddStep ( "click on black box stack" , ( ) = > InputManager . Click ( MouseButton . Left ) ) ;
2023-07-19 15:17:42 +08:00
AddAssert ( "Selection is second last" , ( ) = > skinEditor . SelectedComponents . Single ( ) , ( ) = > Is . EqualTo ( blueprints [ 1 ] . Item ) ) ;
2023-03-07 13:22:19 +08:00
AddStep ( "click on black box stack" , ( ) = > InputManager . Click ( MouseButton . Left ) ) ;
2023-07-19 15:17:42 +08:00
AddAssert ( "Selection is last" , ( ) = > skinEditor . SelectedComponents . Single ( ) , ( ) = > Is . EqualTo ( blueprints [ 0 ] . Item ) ) ;
2023-03-07 13:22:19 +08:00
AddStep ( "click on black box stack" , ( ) = > InputManager . Click ( MouseButton . Left ) ) ;
2023-07-19 15:17:42 +08:00
AddAssert ( "Selection is first" , ( ) = > skinEditor . SelectedComponents . Single ( ) , ( ) = > Is . EqualTo ( blueprints [ 2 ] . Item ) ) ;
2023-03-21 20:25:50 +08:00
AddStep ( "select all boxes" , ( ) = >
{
skinEditor . SelectedComponents . Clear ( ) ;
skinEditor . SelectedComponents . AddRange ( targetContainer . Components . OfType < BigBlackBox > ( ) . Skip ( 1 ) ) ;
} ) ;
AddAssert ( "all boxes selected" , ( ) = > skinEditor . SelectedComponents , ( ) = > Has . Count . EqualTo ( 2 ) ) ;
AddStep ( "click on black box stack" , ( ) = > InputManager . Click ( MouseButton . Left ) ) ;
AddStep ( "click on black box stack" , ( ) = > InputManager . Click ( MouseButton . Left ) ) ;
AddStep ( "click on black box stack" , ( ) = > InputManager . Click ( MouseButton . Left ) ) ;
AddAssert ( "all boxes still selected" , ( ) = > skinEditor . SelectedComponents , ( ) = > Has . Count . EqualTo ( 2 ) ) ;
2023-03-07 13:22:19 +08:00
}
2023-04-30 08:00:35 +08:00
[Test]
2023-04-30 18:05:55 +08:00
public void TestUndoEditHistory ( )
2023-04-30 08:00:35 +08:00
{
SkinComponentsContainer firstTarget = null ! ;
TestSkinEditorChangeHandler changeHandler = null ! ;
byte [ ] defaultState = null ! ;
IEnumerable < ISerialisableDrawable > testComponents = null ! ;
AddStep ( "Load necessary things" , ( ) = >
{
firstTarget = Player . ChildrenOfType < SkinComponentsContainer > ( ) . First ( ) ;
changeHandler = new TestSkinEditorChangeHandler ( firstTarget ) ;
2023-04-30 08:32:20 +08:00
2023-05-01 18:53:58 +08:00
changeHandler . SaveState ( ) ;
2023-04-30 08:00:35 +08:00
defaultState = changeHandler . GetCurrentState ( ) ;
2023-04-30 08:32:20 +08:00
testComponents = new [ ]
{
targetContainer . Components . First ( ) ,
targetContainer . Components [ targetContainer . Components . Count / 2 ] ,
targetContainer . Components . Last ( )
} ;
2023-04-30 08:00:35 +08:00
} ) ;
AddStep ( "Press undo" , ( ) = > InputManager . Keys ( PlatformAction . Undo ) ) ;
AddAssert ( "Nothing changed" , ( ) = > defaultState . SequenceEqual ( changeHandler . GetCurrentState ( ) ) ) ;
2023-04-30 08:32:20 +08:00
AddStep ( "Add components" , ( ) = >
2023-04-30 08:00:35 +08:00
{
InputManager . MoveMouseTo ( skinEditor . ChildrenOfType < BigBlackBox > ( ) . First ( ) ) ;
InputManager . Click ( MouseButton . Left ) ;
InputManager . Click ( MouseButton . Left ) ;
InputManager . Click ( MouseButton . Left ) ;
} ) ;
2023-04-30 18:05:55 +08:00
revertAndCheckUnchanged ( ) ;
AddStep ( "Move components" , ( ) = >
{
changeHandler . BeginChange ( ) ;
testComponents . ForEach ( c = > ( ( Drawable ) c ) . Position + = Vector2 . One ) ;
changeHandler . EndChange ( ) ;
} ) ;
revertAndCheckUnchanged ( ) ;
2023-04-30 08:00:35 +08:00
AddStep ( "Select components" , ( ) = > skinEditor . SelectedComponents . AddRange ( testComponents ) ) ;
AddStep ( "Bring to front" , ( ) = > skinEditor . BringSelectionToFront ( ) ) ;
2023-04-30 18:05:55 +08:00
revertAndCheckUnchanged ( ) ;
2023-04-30 08:00:35 +08:00
AddStep ( "Remove components" , ( ) = > testComponents . ForEach ( c = > firstTarget . Remove ( c , false ) ) ) ;
2023-04-30 18:05:55 +08:00
revertAndCheckUnchanged ( ) ;
void revertAndCheckUnchanged ( )
{
AddStep ( "Revert changes" , ( ) = > changeHandler . RestoreState ( int . MinValue ) ) ;
2023-04-30 22:10:59 +08:00
AddAssert ( "Current state is same as default" , ( ) = > defaultState . SequenceEqual ( changeHandler . GetCurrentState ( ) ) ) ;
2023-04-30 18:05:55 +08:00
}
2023-04-30 08:00:35 +08:00
}
2023-02-22 17:17:03 +08:00
[TestCase(false)]
[TestCase(true)]
public void TestBringToFront ( bool alterSelectionOrder )
{
AddAssert ( "Ensure over three components available" , ( ) = > targetContainer . Components . Count , ( ) = > Is . GreaterThan ( 3 ) ) ;
IEnumerable < ISerialisableDrawable > originalOrder = null ! ;
AddStep ( "Save order of components before operation" , ( ) = > originalOrder = targetContainer . Components . Take ( 3 ) . ToArray ( ) ) ;
if ( alterSelectionOrder )
AddStep ( "Select first three components in reverse order" , ( ) = > skinEditor . SelectedComponents . AddRange ( originalOrder . Reverse ( ) ) ) ;
else
AddStep ( "Select first three components" , ( ) = > skinEditor . SelectedComponents . AddRange ( originalOrder ) ) ;
AddAssert ( "Components are not front-most" , ( ) = > targetContainer . Components . TakeLast ( 3 ) . ToArray ( ) , ( ) = > Is . Not . EqualTo ( skinEditor . SelectedComponents ) ) ;
AddStep ( "Bring to front" , ( ) = > skinEditor . BringSelectionToFront ( ) ) ;
AddAssert ( "Ensure components are now front-most in original order" , ( ) = > targetContainer . Components . TakeLast ( 3 ) . ToArray ( ) , ( ) = > Is . EqualTo ( originalOrder ) ) ;
AddStep ( "Bring to front again" , ( ) = > skinEditor . BringSelectionToFront ( ) ) ;
AddAssert ( "Ensure components are still front-most in original order" , ( ) = > targetContainer . Components . TakeLast ( 3 ) . ToArray ( ) , ( ) = > Is . EqualTo ( originalOrder ) ) ;
}
[TestCase(false)]
[TestCase(true)]
public void TestSendToBack ( bool alterSelectionOrder )
{
AddAssert ( "Ensure over three components available" , ( ) = > targetContainer . Components . Count , ( ) = > Is . GreaterThan ( 3 ) ) ;
IEnumerable < ISerialisableDrawable > originalOrder = null ! ;
AddStep ( "Save order of components before operation" , ( ) = > originalOrder = targetContainer . Components . TakeLast ( 3 ) . ToArray ( ) ) ;
if ( alterSelectionOrder )
AddStep ( "Select last three components in reverse order" , ( ) = > skinEditor . SelectedComponents . AddRange ( originalOrder . Reverse ( ) ) ) ;
else
AddStep ( "Select last three components" , ( ) = > skinEditor . SelectedComponents . AddRange ( originalOrder ) ) ;
AddAssert ( "Components are not back-most" , ( ) = > targetContainer . Components . Take ( 3 ) . ToArray ( ) , ( ) = > Is . Not . EqualTo ( skinEditor . SelectedComponents ) ) ;
AddStep ( "Send to back" , ( ) = > skinEditor . SendSelectionToBack ( ) ) ;
AddAssert ( "Ensure components are now back-most in original order" , ( ) = > targetContainer . Components . Take ( 3 ) . ToArray ( ) , ( ) = > Is . EqualTo ( originalOrder ) ) ;
AddStep ( "Send to back again" , ( ) = > skinEditor . SendSelectionToBack ( ) ) ;
AddAssert ( "Ensure components are still back-most in original order" , ( ) = > targetContainer . Components . Take ( 3 ) . ToArray ( ) , ( ) = > Is . EqualTo ( originalOrder ) ) ;
}
2021-04-29 12:53:01 +08:00
[Test]
public void TestToggleEditor ( )
{
2023-02-22 16:56:59 +08:00
AddToggleStep ( "toggle editor visibility" , _ = > skinEditor . ToggleVisibility ( ) ) ;
2021-04-29 12:53:01 +08:00
}
2022-03-21 16:01:46 +08:00
[Test]
public void TestEditComponent ( )
{
2022-09-08 23:47:55 +08:00
BarHitErrorMeter hitErrorMeter = null ! ;
2022-03-21 16:01:46 +08:00
AddStep ( "select bar hit error blueprint" , ( ) = >
{
var blueprint = skinEditor . ChildrenOfType < SkinBlueprint > ( ) . First ( b = > b . Item is BarHitErrorMeter ) ;
hitErrorMeter = ( BarHitErrorMeter ) blueprint . Item ;
2023-02-22 16:56:59 +08:00
skinEditor . SelectedComponents . Clear ( ) ;
2022-03-21 16:01:46 +08:00
skinEditor . SelectedComponents . Add ( blueprint . Item ) ;
} ) ;
2022-09-08 23:47:55 +08:00
AddStep ( "move by keyboard" , ( ) = > InputManager . Key ( Key . Right ) ) ;
AddAssert ( "hitErrorMeter moved" , ( ) = > hitErrorMeter . X ! = 0 ) ;
2022-03-21 16:01:46 +08:00
AddAssert ( "value is default" , ( ) = > hitErrorMeter . JudgementLineThickness . IsDefault ) ;
AddStep ( "hover first slider" , ( ) = >
{
InputManager . MoveMouseTo (
skinEditor . ChildrenOfType < SkinSettingsToolbox > ( ) . First ( )
. ChildrenOfType < SettingsSlider < float > > ( ) . First ( )
. ChildrenOfType < SliderBar < float > > ( ) . First ( )
) ;
} ) ;
AddStep ( "adjust slider via keyboard" , ( ) = > InputManager . Key ( Key . Left ) ) ;
AddAssert ( "value is less than default" , ( ) = > hitErrorMeter . JudgementLineThickness . Value < hitErrorMeter . JudgementLineThickness . Default ) ;
}
2021-04-29 12:53:01 +08:00
protected override Ruleset CreatePlayerRuleset ( ) = > new OsuRuleset ( ) ;
2023-04-30 08:00:35 +08:00
private partial class TestSkinEditorChangeHandler : SkinEditorChangeHandler
{
public TestSkinEditorChangeHandler ( Drawable targetScreen )
: base ( targetScreen )
{
}
public byte [ ] GetCurrentState ( )
{
using var stream = new MemoryStream ( ) ;
WriteCurrentStateToStream ( stream ) ;
byte [ ] newState = stream . ToArray ( ) ;
return newState ;
}
}
2021-04-27 17:03:49 +08:00
}
}