Skip to content

Commit a0f949c

Browse files
authored
feat: [UI] Add gap support to layout panels (#2855)
1 parent 506946a commit a0f949c

File tree

7 files changed

+1102
-51
lines changed

7 files changed

+1102
-51
lines changed

sources/engine/Stride.UI.Tests/Layering/GridTests.cs

Lines changed: 346 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1441,5 +1441,351 @@ public void TestSurroudingAnchor()
14411441
Assert.Equal(new Vector2(-300, 0), grid.GetSurroudingAnchorDistances(Orientation.InDepth, 600));
14421442
Assert.Equal(new Vector2(-300, 0), grid.GetSurroudingAnchorDistances(Orientation.InDepth, 900));
14431443
}
1444+
1445+
/// <summary>
1446+
/// Test that column gaps work correctly with fixed strips
1447+
/// </summary>
1448+
[Fact]
1449+
public void TestColumnGapWithFixedStrips()
1450+
{
1451+
var grid = new Grid();
1452+
grid.ColumnGap = 10f;
1453+
1454+
// Create 3 fixed columns: 100, 200, 150
1455+
grid.ColumnDefinitions.Add(new StripDefinition(StripType.Fixed, 100));
1456+
grid.ColumnDefinitions.Add(new StripDefinition(StripType.Fixed, 200));
1457+
grid.ColumnDefinitions.Add(new StripDefinition(StripType.Fixed, 150));
1458+
1459+
// Total width should be 100 + 10 + 200 + 10 + 150 = 470
1460+
var child0 = new ArrangeValidator { ExpectedArrangeValue = new Vector3(100, 100, 0), ReturnedMeasuredValue = Vector3.Zero };
1461+
var child1 = new ArrangeValidator { ExpectedArrangeValue = new Vector3(200, 100, 0), ReturnedMeasuredValue = Vector3.Zero };
1462+
var child2 = new ArrangeValidator { ExpectedArrangeValue = new Vector3(150, 100, 0), ReturnedMeasuredValue = Vector3.Zero };
1463+
1464+
child1.DependencyProperties.Set(GridBase.ColumnPropertyKey, 1);
1465+
child2.DependencyProperties.Set(GridBase.ColumnPropertyKey, 2);
1466+
1467+
grid.Children.Add(child0);
1468+
grid.Children.Add(child1);
1469+
grid.Children.Add(child2);
1470+
1471+
grid.Measure(new Vector3(1000, 100, 100));
1472+
Assert.Equal(new Vector3(470, 0, 0), grid.DesiredSizeWithMargins);
1473+
1474+
grid.Arrange(new Vector3(1000, 100, 100), false);
1475+
1476+
// Check that children are positioned correctly with gaps
1477+
Assert.Equal(100, grid.ColumnDefinitions[0].ActualSize);
1478+
Assert.Equal(200, grid.ColumnDefinitions[1].ActualSize);
1479+
Assert.Equal(150, grid.ColumnDefinitions[2].ActualSize);
1480+
}
1481+
1482+
/// <summary>
1483+
/// Test that row gaps work correctly with auto strips
1484+
/// </summary>
1485+
[Fact]
1486+
public void TestRowGapWithAutoStrips()
1487+
{
1488+
var grid = new Grid();
1489+
grid.RowGap = 15f;
1490+
1491+
grid.RowDefinitions.Add(new StripDefinition(StripType.Auto));
1492+
grid.RowDefinitions.Add(new StripDefinition(StripType.Auto));
1493+
grid.RowDefinitions.Add(new StripDefinition(StripType.Auto));
1494+
1495+
// Children with different heights: 50, 75, 100
1496+
var child0 = new ArrangeValidator
1497+
{
1498+
ExpectedArrangeValue = new Vector3(0, 50, 0),
1499+
ReturnedMeasuredValue = new Vector3(0, 50, 0),
1500+
HorizontalAlignment = HorizontalAlignment.Center,
1501+
VerticalAlignment = VerticalAlignment.Stretch,
1502+
DepthAlignment = DepthAlignment.Center
1503+
};
1504+
var child1 = new ArrangeValidator
1505+
{
1506+
ExpectedArrangeValue = new Vector3(0, 75, 0),
1507+
ReturnedMeasuredValue = new Vector3(0, 75, 0),
1508+
HorizontalAlignment = HorizontalAlignment.Center,
1509+
VerticalAlignment = VerticalAlignment.Stretch,
1510+
DepthAlignment = DepthAlignment.Center
1511+
1512+
};
1513+
var child2 = new ArrangeValidator
1514+
{
1515+
ExpectedArrangeValue = new Vector3(0, 100, 0),
1516+
ReturnedMeasuredValue = new Vector3(0, 100, 0),
1517+
HorizontalAlignment = HorizontalAlignment.Center,
1518+
VerticalAlignment = VerticalAlignment.Stretch,
1519+
DepthAlignment = DepthAlignment.Center
1520+
1521+
};
1522+
1523+
child1.DependencyProperties.Set(GridBase.RowPropertyKey, 1);
1524+
child2.DependencyProperties.Set(GridBase.RowPropertyKey, 2);
1525+
1526+
grid.Children.Add(child0);
1527+
grid.Children.Add(child1);
1528+
grid.Children.Add(child2);
1529+
1530+
grid.Measure(new Vector3(100, 1000, 100));
1531+
// Total height should be 50 + 15 + 75 + 15 + 100 = 255
1532+
Assert.Equal(new Vector3(0, 255, 0), grid.DesiredSizeWithMargins);
1533+
1534+
grid.Arrange(new Vector3(100, 1000, 100), false);
1535+
1536+
Assert.Equal(50, grid.RowDefinitions[0].ActualSize);
1537+
Assert.Equal(75, grid.RowDefinitions[1].ActualSize);
1538+
Assert.Equal(100, grid.RowDefinitions[2].ActualSize);
1539+
}
1540+
1541+
/// <summary>
1542+
/// Test that layer gaps work correctly with star strips
1543+
/// </summary>
1544+
[Fact]
1545+
public void TestLayerGapWithStarStrips()
1546+
{
1547+
var grid = new Grid();
1548+
grid.LayerGap = 20f;
1549+
grid.DepthAlignment = DepthAlignment.Stretch;
1550+
1551+
// Create 2 star layers with ratio 1:2
1552+
grid.LayerDefinitions.Add(new StripDefinition(StripType.Star, 1));
1553+
grid.LayerDefinitions.Add(new StripDefinition(StripType.Star, 2));
1554+
1555+
var child0 = new ArrangeValidator
1556+
{
1557+
ExpectedArrangeValue = new Vector3(0, 0, 60),
1558+
ReturnedMeasuredValue = new Vector3(0, 0, 30),
1559+
HorizontalAlignment = HorizontalAlignment.Center,
1560+
VerticalAlignment = VerticalAlignment.Center,
1561+
DepthAlignment = DepthAlignment.Stretch
1562+
};
1563+
var child1 = new ArrangeValidator
1564+
{
1565+
ExpectedArrangeValue = new Vector3(0, 0, 120),
1566+
ReturnedMeasuredValue = new Vector3(0, 0, 60),
1567+
HorizontalAlignment = HorizontalAlignment.Center,
1568+
VerticalAlignment = VerticalAlignment.Center,
1569+
DepthAlignment = DepthAlignment.Stretch
1570+
};
1571+
1572+
child1.DependencyProperties.Set(GridBase.LayerPropertyKey, 1);
1573+
1574+
grid.Children.Add(child0);
1575+
grid.Children.Add(child1);
1576+
1577+
// Available depth: 200, gap: 20, remaining: 180
1578+
// Star ratio 1:2, so sizes should be 60 and 120
1579+
grid.Measure(new Vector3(100, 100, 200));
1580+
grid.Arrange(new Vector3(100, 100, 200), false);
1581+
1582+
Assert.Equal(60, grid.LayerDefinitions[0].ActualSize);
1583+
Assert.Equal(120, grid.LayerDefinitions[1].ActualSize);
1584+
}
1585+
1586+
/// <summary>
1587+
/// Test gaps with spanned elements
1588+
/// </summary>
1589+
[Fact]
1590+
public void TestGapsWithSpannedElements()
1591+
{
1592+
var grid = new Grid();
1593+
grid.ColumnGap = 10f;
1594+
grid.RowGap = 5f;
1595+
1596+
grid.ColumnDefinitions.Add(new StripDefinition(StripType.Fixed, 100));
1597+
grid.ColumnDefinitions.Add(new StripDefinition(StripType.Fixed, 150));
1598+
grid.ColumnDefinitions.Add(new StripDefinition(StripType.Fixed, 200));
1599+
grid.RowDefinitions.Add(new StripDefinition(StripType.Fixed, 80));
1600+
grid.RowDefinitions.Add(new StripDefinition(StripType.Fixed, 120));
1601+
1602+
// Element spanning 2 columns: should get 100 + 10 (gap) + 150 = 260 width
1603+
var spannedChild = new ArrangeValidator
1604+
{
1605+
ExpectedArrangeValue = new Vector3(260, 80, 0),
1606+
ReturnedMeasuredValue = new Vector3(200, 50, 0)
1607+
};
1608+
spannedChild.DependencyProperties.Set(GridBase.ColumnSpanPropertyKey, 2);
1609+
1610+
// Element spanning 2 rows: should get 80 + 5 (gap) + 120 = 205 height
1611+
var rowSpannedChild = new ArrangeValidator
1612+
{
1613+
ExpectedArrangeValue = new Vector3(200, 205, 0),
1614+
ReturnedMeasuredValue = new Vector3(150, 180, 0)
1615+
};
1616+
rowSpannedChild.DependencyProperties.Set(GridBase.ColumnPropertyKey, 2);
1617+
rowSpannedChild.DependencyProperties.Set(GridBase.RowSpanPropertyKey, 2);
1618+
1619+
grid.Children.Add(spannedChild);
1620+
grid.Children.Add(rowSpannedChild);
1621+
1622+
grid.Measure(new Vector3(1000, 1000, 100));
1623+
grid.Arrange(new Vector3(1000, 1000, 100), false);
1624+
1625+
// Verify total grid size includes gaps
1626+
// Columns: 100 + 10 + 150 + 10 + 200 = 470
1627+
// Rows: 80 + 5 + 120 = 205
1628+
Assert.Equal(new Vector3(470, 205, 0), grid.DesiredSizeWithMargins);
1629+
}
1630+
1631+
/// <summary>
1632+
/// Test that gaps are correctly applied in mixed strip types
1633+
/// </summary>
1634+
[Fact]
1635+
public void TestGapsWithMixedStripTypes()
1636+
{
1637+
var grid = new Grid();
1638+
grid.ColumnGap = 8f;
1639+
1640+
grid.ColumnDefinitions.Add(new StripDefinition(StripType.Fixed, 50));
1641+
grid.ColumnDefinitions.Add(new StripDefinition(StripType.Auto));
1642+
grid.ColumnDefinitions.Add(new StripDefinition(StripType.Star, 1));
1643+
grid.ColumnDefinitions.Add(new StripDefinition(StripType.Fixed, 100));
1644+
1645+
var child0 = new ArrangeValidator { ExpectedArrangeValue = new Vector3(50, 100, 0), ReturnedMeasuredValue = new Vector3(30, 0, 0) };
1646+
var child1 = new ArrangeValidator { ExpectedArrangeValue = new Vector3(80, 100, 0), ReturnedMeasuredValue = new Vector3(80, 0, 0) };
1647+
var child2 = new ArrangeValidator { ExpectedArrangeValue = new Vector3(146, 100, 0), ReturnedMeasuredValue = new Vector3(100, 0, 0) };
1648+
var child3 = new ArrangeValidator { ExpectedArrangeValue = new Vector3(100, 100, 0), ReturnedMeasuredValue = new Vector3(90, 0, 0) };
1649+
1650+
child1.DependencyProperties.Set(GridBase.ColumnPropertyKey, 1);
1651+
child2.DependencyProperties.Set(GridBase.ColumnPropertyKey, 2);
1652+
child3.DependencyProperties.Set(GridBase.ColumnPropertyKey, 3);
1653+
1654+
grid.Children.Add(child0);
1655+
grid.Children.Add(child1);
1656+
grid.Children.Add(child2);
1657+
grid.Children.Add(child3);
1658+
1659+
// Available width: 400, gaps: 3 * 8 = 24, remaining: 376
1660+
// Fixed: 50 + 100 = 150, Auto: 80, Star gets: 376 - 150 - 80 = 146
1661+
grid.Measure(new Vector3(400, 100, 100));
1662+
grid.Arrange(new Vector3(400, 100, 100), false);
1663+
1664+
Assert.Equal(50, grid.ColumnDefinitions[0].ActualSize);
1665+
Assert.Equal(80, grid.ColumnDefinitions[1].ActualSize);
1666+
Assert.Equal(100, grid.ColumnDefinitions[3].ActualSize);
1667+
}
1668+
1669+
/// <summary>
1670+
/// Test that zero gaps work correctly (no gaps)
1671+
/// </summary>
1672+
[Fact]
1673+
public void TestZeroGaps()
1674+
{
1675+
var grid = new Grid();
1676+
grid.ColumnGap = 0f;
1677+
grid.RowGap = 0f;
1678+
grid.LayerGap = 0f;
1679+
1680+
grid.ColumnDefinitions.Add(new StripDefinition(StripType.Fixed, 100));
1681+
grid.ColumnDefinitions.Add(new StripDefinition(StripType.Fixed, 200));
1682+
grid.RowDefinitions.Add(new StripDefinition(StripType.Fixed, 150));
1683+
1684+
var child = new ArrangeValidator { ExpectedArrangeValue = new Vector3(100, 150, 0), ReturnedMeasuredValue = Vector3.Zero };
1685+
grid.Children.Add(child);
1686+
1687+
grid.Measure(new Vector3(1000, 1000, 100));
1688+
// Total should be exactly the sum of strip sizes with no gaps
1689+
Assert.Equal(new Vector3(300, 150, 0), grid.DesiredSizeWithMargins);
1690+
1691+
grid.Arrange(new Vector3(1000, 1000, 100), false);
1692+
}
1693+
1694+
/// <summary>
1695+
/// Test that negative gap values are coerced to zero
1696+
/// </summary>
1697+
[Fact]
1698+
public void TestNegativeGapsCoercion()
1699+
{
1700+
var grid = new Grid();
1701+
1702+
// Set negative gaps - should be coerced to 0
1703+
grid.ColumnGap = -10f;
1704+
grid.RowGap = -5f;
1705+
grid.LayerGap = -15f;
1706+
1707+
Assert.Equal(0f, grid.ColumnGap);
1708+
Assert.Equal(0f, grid.RowGap);
1709+
Assert.Equal(0f, grid.LayerGap);
1710+
}
1711+
1712+
/// <summary>
1713+
/// Test gaps with single strip (no gaps should be applied)
1714+
/// </summary>
1715+
[Fact]
1716+
public void TestGapsWithSingleStrip()
1717+
{
1718+
var grid = new Grid();
1719+
grid.ColumnGap = 20f;
1720+
grid.RowGap = 15f;
1721+
grid.LayerGap = 10f;
1722+
1723+
// Only one strip in each dimension - no gaps should be applied
1724+
grid.ColumnDefinitions.Add(new StripDefinition(StripType.Fixed, 100));
1725+
grid.RowDefinitions.Add(new StripDefinition(StripType.Fixed, 200));
1726+
grid.LayerDefinitions.Add(new StripDefinition(StripType.Fixed, 150));
1727+
1728+
var child = new ArrangeValidator
1729+
{
1730+
ExpectedArrangeValue = new Vector3(100, 200, 150),
1731+
ReturnedMeasuredValue = Vector3.Zero,
1732+
DepthAlignment = DepthAlignment.Stretch
1733+
};
1734+
grid.Children.Add(child);
1735+
1736+
grid.Measure(new Vector3(1000, 1000, 1000));
1737+
// Should be exactly the strip sizes with no gaps added
1738+
Assert.Equal(new Vector3(100, 200, 150), grid.DesiredSizeWithMargins);
1739+
1740+
grid.Arrange(new Vector3(1000, 1000, 1000), false);
1741+
}
1742+
1743+
/// <summary>
1744+
/// Test that changing gap values invalidates measure
1745+
/// </summary>
1746+
[Fact]
1747+
public void TestGapInvalidation()
1748+
{
1749+
var grid = new Grid();
1750+
grid.ColumnDefinitions.Add(new StripDefinition(StripType.Fixed, 100));
1751+
grid.ColumnDefinitions.Add(new StripDefinition(StripType.Fixed, 100));
1752+
1753+
// Test that changing gaps invalidates measure
1754+
UIElementLayeringTests.TestMeasureInvalidation(grid, () => grid.ColumnGap = 10f);
1755+
UIElementLayeringTests.TestMeasureInvalidation(grid, () => grid.RowGap = 15f);
1756+
UIElementLayeringTests.TestMeasureInvalidation(grid, () => grid.LayerGap = 20f);
1757+
}
1758+
1759+
/// <summary>
1760+
/// Test gaps with minimum and maximum strip constraints
1761+
/// </summary>
1762+
[Fact]
1763+
public void TestGapsWithMinMaxConstraints()
1764+
{
1765+
var grid = new Grid();
1766+
grid.ColumnGap = 10f;
1767+
1768+
grid.ColumnDefinitions.Add(new StripDefinition(StripType.Star, 1) { MinimumSize = 50, MaximumSize = 100 });
1769+
grid.ColumnDefinitions.Add(new StripDefinition(StripType.Star, 1) { MinimumSize = 75, MaximumSize = 150 });
1770+
1771+
var child0 = new ArrangeValidator { ExpectedArrangeValue = new Vector3(95, 100, 0), ReturnedMeasuredValue = new Vector3(80, 0, 0) };
1772+
var child1 = new ArrangeValidator { ExpectedArrangeValue = new Vector3(95, 100, 0), ReturnedMeasuredValue = new Vector3(90, 0, 0) };
1773+
1774+
child1.DependencyProperties.Set(GridBase.ColumnPropertyKey, 1);
1775+
1776+
grid.Children.Add(child0);
1777+
grid.Children.Add(child1);
1778+
1779+
// Available: 200, gap: 10, remaining: 190
1780+
// Equal distribution would be 95 each, but constraints apply
1781+
grid.Measure(new Vector3(200, 100, 100));
1782+
grid.Arrange(new Vector3(200, 100, 100), false);
1783+
1784+
// Should respect min/max constraints while accounting for gaps
1785+
Assert.True(grid.ColumnDefinitions[0].ActualSize >= 50);
1786+
Assert.True(grid.ColumnDefinitions[0].ActualSize <= 100);
1787+
Assert.True(grid.ColumnDefinitions[1].ActualSize >= 75);
1788+
Assert.True(grid.ColumnDefinitions[1].ActualSize <= 150);
1789+
}
14441790
}
14451791
}

0 commit comments

Comments
 (0)