2021-04-01 19:36:56 +02:00
using System ;
2021-06-25 12:45:00 +02:00
using System.Diagnostics ;
2021-04-01 19:36:56 +02:00
using System.Linq ;
2024-10-29 23:31:49 +01:00
using System.Runtime.CompilerServices ;
2021-04-01 19:36:56 +02:00
using Microsoft.Xna.Framework ;
2024-07-19 20:02:28 +02:00
using MLEM.Maths ;
2021-04-01 19:36:56 +02:00
using MLEM.Ui ;
using MLEM.Ui.Elements ;
using MLEM.Ui.Style ;
using NUnit.Framework ;
2022-12-13 13:11:36 +01:00
namespace Tests ;
2021-04-01 19:36:56 +02:00
2024-10-26 14:41:36 +02:00
public class UiTests : GameTestFixture {
2021-04-27 21:17:06 +02:00
2022-10-27 10:22:25 +02:00
[Test]
public void TestInvalidPanel ( ) {
var invalidPanel = new Panel ( Anchor . Center , Vector2 . Zero , Vector2 . Zero ) {
SetWidthBasedOnChildren = true ,
SetHeightBasedOnChildren = true
} ;
invalidPanel . AddChild ( new Paragraph ( Anchor . AutoRight , 1 , "This is some test text!" , true ) ) ;
invalidPanel . AddChild ( new VerticalSpace ( 1 ) ) ;
2024-10-07 18:32:22 +02:00
Assert . Throws < ArithmeticException > ( ( ) = > this . AddAndUpdate ( invalidPanel , out _ , out _ ) ) ;
2022-10-27 10:22:25 +02:00
}
2021-04-01 19:36:56 +02:00
2022-10-27 10:22:25 +02:00
[Test]
public void TestOddlyAlignedPanel ( ) {
var oddPanel = new Panel ( Anchor . Center , Vector2 . One , Vector2 . Zero , true ) { SetWidthBasedOnChildren = true } ;
oddPanel . AddChild ( new Group ( Anchor . TopCenter , new Vector2 ( 100 ) , false ) ) ;
oddPanel . AddChild ( new Group ( Anchor . AutoRight , new Vector2 ( 120 ) , false ) ) ;
2024-10-07 18:32:22 +02:00
this . AddAndUpdate ( oddPanel , out _ , out _ ) ;
2022-10-27 10:22:25 +02:00
Assert . AreEqual ( 120 + 10 , oddPanel . DisplayArea . Width ) ;
}
2021-04-01 19:36:56 +02:00
2022-10-27 10:22:25 +02:00
[Test]
public void TestComplexPanel ( ) {
var group = new Group ( Anchor . TopLeft , Vector2 . One , false ) ;
var panel = group . AddChild ( new Panel ( Anchor . Center , new Vector2 ( 150 , 150 ) , Vector2 . Zero , false , true , false ) {
ChildPadding = new Padding ( 5 , 5 , 5 , 5 )
} ) ;
for ( var i = 0 ; i < 5 ; i + + ) {
var button = panel . AddChild ( new Button ( Anchor . AutoLeft , new Vector2 ( 1 ) ) {
SetHeightBasedOnChildren = true ,
Padding = new Padding ( 0 , 0 , 0 , 1 ) ,
ChildPadding = new Padding ( 3 )
2022-08-16 14:20:32 +02:00
} ) ;
2022-10-27 10:22:25 +02:00
button . AddChild ( new Group ( Anchor . AutoLeft , new Vector2 ( 0.5F , 30 ) , false ) {
CanBeMoused = false
2022-08-16 14:20:32 +02:00
} ) ;
}
2024-10-07 18:32:22 +02:00
this . AddAndUpdate ( group , out _ , out _ ) ;
2022-10-27 10:22:25 +02:00
// group has 1 panel with 1 scroll bar, and the panel's 10 children
Assert . AreEqual ( 1 , group . GetChildren ( ) . Count ( ) ) ;
Assert . AreEqual ( 12 , group . GetChildren ( regardGrandchildren : true ) . Count ( ) ) ;
// panel 1 scroll bar and 5 buttons, each button has 1 group, so 11 grandchildren
Assert . AreEqual ( 6 , panel . GetChildren ( ) . Count ( ) ) ;
Assert . AreEqual ( 11 , panel . GetChildren ( regardGrandchildren : true ) . Count ( ) ) ;
var testBtn = panel . GetChildren < Button > ( ) . First ( ) ;
// panel's width is 150, minus child padding of 5 on each side, and scroll bar's width of 5 and gap of 1
const int panelContentWidth = 150 - 5 - 5 - 5 - 1 ;
Assert . AreEqual ( testBtn . DisplayArea . Width , panelContentWidth ) ;
// button's width, minus child padding of 3 left and 3 right, divided by 2 because of group's width
Assert . AreEqual ( testBtn . GetChildren < Group > ( ) . Single ( ) . DisplayArea . Width , ( panelContentWidth - 3 - 3 ) * 0.5F ) ;
}
2022-08-16 14:20:32 +02:00
2022-10-27 10:22:25 +02:00
[Test]
public void TestAbsoluteAutoSize ( ) {
var parent = new Panel ( Anchor . AutoLeft , new Vector2 ( 200 , 100 ) , Vector2 . Zero ) {
ChildPadding = Padding . Empty
} ;
var el1 = parent . AddChild ( new Button ( Anchor . AutoLeft , new Vector2 ( 0.5F , 0.75F ) ) {
AutoSizeAddedAbsolute = new Vector2 ( - 50 , 25 )
} ) ;
var el2 = parent . AddChild ( new Button ( Anchor . AutoLeft , new Vector2 ( 0.25F , - 0.5F ) ) {
AutoSizeAddedAbsolute = new Vector2 ( - 25 , 50 )
} ) ;
2024-10-07 18:32:22 +02:00
this . AddAndUpdate ( parent , out _ , out _ ) ;
2022-10-27 10:22:25 +02:00
Assert . AreEqual ( 0.5F * 200 - 50 , el1 . DisplayArea . Width ) ;
Assert . AreEqual ( 0.75F * 100 + 25 , el1 . DisplayArea . Height ) ;
const float el2Width = 0.25F * 200 - 25 ;
Assert . AreEqual ( el2Width , el2 . DisplayArea . Width ) ;
Assert . AreEqual ( 0.5F * el2Width + 50 , el2 . DisplayArea . Height ) ;
}
[Test]
public void TestStyle ( ) {
var style = new StyleProp < string > ( ) ;
Assert . AreEqual ( null , style . Value ) ;
style = style . OrStyle ( "from style" ) ;
Assert . AreEqual ( "from style" , style . Value ) ;
style = "custom" ;
Assert . AreEqual ( "custom" , style . Value ) ;
style = style . OrStyle ( "from style again" ) ;
Assert . AreEqual ( "custom" , style . Value ) ;
var copy = style . OrStyle ( "copy from style" , byte . MaxValue ) ;
var weakCopy = style . OrStyle ( "weak copy" ) ;
Assert . AreEqual ( "copy from style" , copy . Value ) ;
Assert . AreEqual ( "custom" , weakCopy . Value ) ;
Assert . AreEqual ( "custom" , style . Value ) ;
}
2021-04-04 18:31:51 +02:00
2022-10-27 10:22:25 +02:00
[Test]
2024-10-07 18:32:22 +02:00
public void TestAutoAreaPerformanceDeep ( ) {
2022-10-27 10:22:25 +02:00
for ( var i = 1 ; i < = 100 ; i + + ) {
2024-10-07 21:51:35 +02:00
var main = new Group ( Anchor . TopLeft , new Vector2 ( 50 ) ) ;
2022-10-27 10:22:25 +02:00
var group = main ;
2024-10-07 21:51:35 +02:00
for ( var g = 0 ; g < i ; g + + )
group = group . AddChild ( new Group ( Anchor . TopLeft , Vector2 . One ) ) ;
2024-10-07 18:32:22 +02:00
this . AddAndUpdate ( main , out var addTime , out var updateTime ) ;
2022-10-27 10:22:25 +02:00
var allChildren = main . GetChildren ( regardGrandchildren : true ) ;
2024-10-26 14:41:36 +02:00
TestContext . WriteLine ( $"{allChildren.Count()} children, took {addTime.TotalMilliseconds * 1000000}ns to add, {updateTime.TotalMilliseconds * 1000000}ns to update, metrics {this.Game.UiSystem.Metrics}" ) ;
2021-06-25 12:45:00 +02:00
}
2022-10-27 10:22:25 +02:00
}
2021-06-25 12:45:00 +02:00
2024-10-07 18:32:22 +02:00
[Test]
public void TestAutoAreaPerformanceSideBySide ( ) {
for ( var i = 1 ; i < = 100 ; i + + ) {
2024-10-07 21:51:35 +02:00
var main = new Group ( Anchor . TopLeft , new Vector2 ( 50 ) ) ;
for ( var g = 0 ; g < i ; g + + )
main . AddChild ( new Group ( Anchor . AutoInlineIgnoreOverflow , new Vector2 ( 1F / i , 1 ) ) ) ;
2024-10-07 18:32:22 +02:00
this . AddAndUpdate ( main , out var addTime , out var updateTime ) ;
var allChildren = main . GetChildren ( regardGrandchildren : true ) ;
2024-10-26 14:41:36 +02:00
TestContext . WriteLine ( $"{allChildren.Count()} children, took {addTime.TotalMilliseconds * 1000000}ns to add, {updateTime.TotalMilliseconds * 1000000}ns to update, metrics {this.Game.UiSystem.Metrics}" ) ;
2024-10-07 18:32:22 +02:00
}
}
[Test]
public void TestAutoAreaPerformanceRandom ( ) {
for ( var i = 0 ; i < = 1000 ; i + = 10 ) {
var random = new Random ( 93829345 ) ;
2024-10-07 21:51:35 +02:00
var main = new Group ( Anchor . TopLeft , new Vector2 ( 50 ) ) ;
2024-10-07 18:32:22 +02:00
var group = main ;
for ( var g = 0 ; g < i ; g + + ) {
2024-10-07 21:51:35 +02:00
var newGroup = group . AddChild ( new Group ( Anchor . TopLeft , Vector2 . One ) ) ;
2024-10-07 18:32:22 +02:00
if ( random . NextSingle ( ) < = 0.25F )
group = newGroup ;
}
this . AddAndUpdate ( main , out var addTime , out var updateTime ) ;
var allChildren = main . GetChildren ( regardGrandchildren : true ) ;
2024-10-26 14:41:36 +02:00
TestContext . WriteLine ( $"{allChildren.Count()} children, took {addTime.TotalMilliseconds * 1000000}ns to add, {updateTime.TotalMilliseconds * 1000000}ns to update, metrics {this.Game.UiSystem.Metrics}" ) ;
2024-10-07 18:32:22 +02:00
}
}
2024-10-29 23:31:49 +01:00
// Stack overflow related to panel scrolling and scrollbar auto-hiding
2024-10-27 00:46:15 +02:00
[Test]
public void TestIssue27 ( [ Values ( 5 , 50 , 15 ) ] int numChildren ) {
var group = new SquishingGroup ( Anchor . TopLeft , Vector2 . One ) ;
var centerGroup = new ScissorGroup ( Anchor . TopCenter , Vector2 . One ) ;
var centerPanel = new Panel ( Anchor . TopRight , Vector2 . One ) ;
centerPanel . DrawColor = Color . Red ;
centerPanel . Padding = new MLEM . Maths . Padding ( 5 ) ;
centerGroup . AddChild ( centerPanel ) ;
group . AddChild ( centerGroup ) ;
var leftColumn = new Panel ( Anchor . TopLeft , new Vector2 ( 500 , 1 ) , scrollOverflow : true ) ;
group . AddChild ( leftColumn ) ;
for ( var i = 0 ; i < numChildren ; i + + ) {
var c = new Panel ( Anchor . AutoLeft , new Vector2 ( 1 , 30 ) ) ;
c . DrawColor = Color . Green ;
c . Padding = new MLEM . Maths . Padding ( 5 ) ;
leftColumn . AddChild ( c ) ;
}
var bottomPane = new Panel ( Anchor . BottomCenter , new Vector2 ( 1 , 500 ) ) ;
group . AddChild ( bottomPane ) ;
2024-10-29 23:31:49 +01:00
Assert . DoesNotThrow ( ( ) = > this . AddAndUpdate ( group , out _ , out _ ) ) ;
}
// Removing and re-adding to a scrolling panel causes a stack overflow
[Test]
public void TestIssue29StackOverflow ( [ Values ( 5 , 50 , 15 ) ] int numChildren ) {
var group = new SquishingGroup ( Anchor . TopLeft , Vector2 . One ) ;
var centerGroup = new ScissorGroup ( Anchor . TopCenter , Vector2 . One ) ;
var centerPanel = new Panel ( Anchor . TopRight , Vector2 . One ) ;
centerPanel . DrawColor = Color . Red ;
centerPanel . Padding = new MLEM . Maths . Padding ( 5 ) ;
centerGroup . AddChild ( centerPanel ) ;
group . AddChild ( centerGroup ) ;
var leftColumn = new SquishingGroup ( Anchor . TopLeft , new Vector2 ( 500 , 1 ) ) ;
group . AddChild ( leftColumn ) ;
var namePanel = new Panel ( Anchor . TopLeft , new Vector2 ( 1 , 50 ) , true ) ;
var test = new Panel ( Anchor . TopCenter , new Vector2 ( 1 , 30 ) ) ;
test . DrawColor = Color . Red ;
namePanel . AddChild ( test ) ;
var listView = new Panel ( Anchor . TopLeft , new Vector2 ( 1 , 1 ) , false , true ) ;
leftColumn . AddChild ( listView ) ;
leftColumn . AddChild ( namePanel ) ;
var bottomPane = new Panel ( Anchor . BottomCenter , new Vector2 ( 1 , 500 ) ) ;
group . AddChild ( bottomPane ) ;
Repopulate ( ) ;
Assert . DoesNotThrow ( ( ) = > this . AddAndUpdate ( group , out _ , out _ ) ) ;
Repopulate ( ) ;
Assert . DoesNotThrow ( ( ) = > UiTests . ForceUpdate ( group , out _ ) ) ;
void Repopulate ( ) {
listView . RemoveChildren ( ) ;
for ( var i = 0 ; i < numChildren ; i + + ) {
var c = new Panel ( Anchor . AutoLeft , new Vector2 ( 1 , 30 ) ) ;
c . DrawColor = Color . Green ;
c . Padding = new MLEM . Maths . Padding ( 5 ) ;
listView . AddChild ( c ) ;
}
}
}
// Adding children causes the scroll bar to disappear when it shouldn't
[Test]
public void TestIssue29Inconsistencies ( ) {
var group = new SquishingGroup ( Anchor . TopLeft , Vector2 . One ) ;
var centerGroup = new ScissorGroup ( Anchor . TopCenter , Vector2 . One ) ;
var centerPanel = new Panel ( Anchor . TopRight , Vector2 . One ) ;
centerPanel . DrawColor = Color . Red ;
centerPanel . Padding = new MLEM . Maths . Padding ( 5 ) ;
centerGroup . AddChild ( centerPanel ) ;
group . AddChild ( centerGroup ) ;
var listView = new Panel ( Anchor . TopLeft , new Vector2 ( 1 , 1 ) , false , true ) ;
group . AddChild ( listView ) ;
var bottomPane = new Panel ( Anchor . BottomCenter , new Vector2 ( 1 , 500 ) ) ;
group . AddChild ( bottomPane ) ;
Assert . DoesNotThrow ( ( ) = > this . AddAndUpdate ( group , out _ , out _ ) ) ;
var appeared = false ;
for ( var i = 0 ; i < 100 ; i + + ) {
var c = new Panel ( Anchor . AutoLeft , new Vector2 ( 1 , 50 ) ) ;
c . DrawColor = Color . Green ;
c . Padding = new MLEM . Maths . Padding ( 5 ) ;
listView . AddChild ( c ) ;
Console . WriteLine ( $"Adding child, up to {i}" ) ;
Assert . DoesNotThrow ( ( ) = > UiTests . ForceUpdate ( group , out _ ) ) ;
if ( appeared ) {
Assert . False ( listView . ScrollBar . IsHidden , $"Fail bar was hidden after {i} children" ) ;
} else if ( ! listView . ScrollBar . IsHidden ) {
appeared = true ;
}
}
Assert . True ( appeared , "Scroll bar never appeared" ) ;
2024-10-27 00:46:15 +02:00
}
2024-10-07 18:32:22 +02:00
private void AddAndUpdate ( Element element , out TimeSpan addTime , out TimeSpan updateTime ) {
2024-10-26 14:41:36 +02:00
foreach ( var root in this . Game . UiSystem . GetRootElements ( ) )
this . Game . UiSystem . Remove ( root . Name ) ;
this . Game . UiSystem . Metrics . ResetUpdates ( ) ;
2021-04-04 18:31:51 +02:00
2024-10-07 18:32:22 +02:00
var stopwatch = Stopwatch . StartNew ( ) ;
2024-10-26 14:41:36 +02:00
this . Game . UiSystem . Add ( "Test" , element ) ;
2024-10-07 18:32:22 +02:00
stopwatch . Stop ( ) ;
addTime = stopwatch . Elapsed ;
2024-10-29 23:31:49 +01:00
UiTests . ForceUpdate ( element , out updateTime ) ;
}
private static void ForceUpdate ( Element element , out TimeSpan updateTime ) {
var stopwatch = Stopwatch . StartNew ( ) ;
2022-10-27 10:22:25 +02:00
element . ForceUpdateArea ( ) ;
2024-10-07 18:32:22 +02:00
stopwatch . Stop ( ) ;
updateTime = stopwatch . Elapsed ;
2021-04-01 19:36:56 +02:00
}
2022-10-27 10:22:25 +02:00
2022-06-17 18:23:47 +02:00
}