Rewrote SetRelativeLayout to move logic in to the Pos & Dim classes. Massively beefed up unit tests.

This commit is contained in:
Tig
2024-04-16 17:15:50 -06:00
parent 264ba87e73
commit e8467f25c4
4 changed files with 217 additions and 177 deletions

View File

@@ -1,4 +1,6 @@
namespace Terminal.Gui;
using static Terminal.Gui.Pos;
namespace Terminal.Gui;
/// <summary>
/// Describes the position of a <see cref="View"/> which can be an absolute value, a percentage, centered, or
@@ -319,6 +321,12 @@ public class Pos
}
}
internal virtual int GetLocation (int superviewDimension, Dim dim, int autosize, bool autoSize)
{
return this.Anchor (superviewDimension);
}
internal class PosAbsolute (int n) : Pos
{
private readonly int _n = n;
@@ -354,12 +362,29 @@ public class Pos
}
return width - _offset;
}
internal override int GetLocation (int superviewDimension, Dim dim, int autosize, bool autoSize)
{
int newLocation = this.Anchor (superviewDimension);
if (this.UseDimForOffset)
{
newLocation -= dim.Anchor (superviewDimension);
}
return newLocation;
}
}
internal class PosCenter : Pos
{
public override string ToString () { return "Center"; }
internal override int Anchor (int width) { return width / 2; }
internal override int GetLocation (int superviewDimension, Dim dim, int autosize, bool autoSize)
{
var newDimension = Math.Max (dim.GetDimension (0, superviewDimension, autosize, autoSize), 0);
return Anchor (superviewDimension - newDimension);
}
}
internal class PosCombine (bool add, Pos left, Pos right) : Pos
@@ -381,6 +406,19 @@ public class Pos
return la - ra;
}
internal override int GetLocation (int superviewDimension, Dim dim, int autosize, bool autoSize)
{
int newDimension = dim.GetDimension (0, superviewDimension, autosize, autoSize);
int left = _left.GetLocation (superviewDimension, dim, autosize, autoSize);
int right = _right.GetLocation (superviewDimension, dim, autosize, autoSize);
if (_add)
{
return left + right;
}
return left - right;
}
}
internal class PosFactor (float n) : Pos
@@ -645,6 +683,13 @@ public class Dim
internal virtual int Anchor (int width) { return 0; }
internal virtual int GetDimension (int location, int dimension, int autosize, bool autoSize)
{
int newDimension = Math.Max (Anchor (dimension - location), 0);
return autoSize && autosize > newDimension ? autosize : newDimension;
}
// BUGBUG: newPos is never used.
private static void SetDimCombine (Dim left, DimCombine newPos) { (left as DimView)?.Target.SetNeedsLayout (); }
@@ -655,6 +700,15 @@ public class Dim
public override int GetHashCode () { return _n.GetHashCode (); }
public override string ToString () { return $"Absolute({_n})"; }
internal override int Anchor (int width) { return _n; }
internal override int GetDimension (int location, int dimension, int autosize, bool autoSize)
{
// DimAbsolute.Anchor (int width) ignores width and returns n
int newDimension = Math.Max (Anchor (0), 0);
// BUGBUG: AutoSize does two things: makes text fit AND changes the view's dimensions
return autoSize && autosize > newDimension ? autosize : newDimension;
}
}
internal class DimCombine (bool add, Dim left, Dim right) : Dim
@@ -676,6 +730,24 @@ public class Dim
return la - ra;
}
internal override int GetDimension (int location, int dimension, int autosize, bool autoSize)
{
int leftNewDim = _left.GetDimension (location, dimension, autosize, autoSize);
int rightNewDim = _right.GetDimension (location, dimension, autosize, autoSize);
int newDimension;
if (_add)
{
newDimension = leftNewDim + rightNewDim;
}
else
{
newDimension = Math.Max (0, leftNewDim - rightNewDim);
}
return autoSize && autosize > newDimension ? autosize : newDimension;
}
}
internal class DimFactor (float n, bool r = false) : Dim
@@ -688,6 +760,21 @@ public class Dim
public bool IsFromRemaining () { return _remaining; }
public override string ToString () { return $"Factor({_factor},{_remaining})"; }
internal override int Anchor (int width) { return (int)(width * _factor); }
internal override int GetDimension (int location, int dimension, int autosize, bool autoSize)
{
int newDimension;
if (_remaining)
{
newDimension = Math.Max (Anchor (dimension - location), 0);
}
else
{
newDimension = Anchor (dimension);
}
return autoSize && autosize > newDimension ? autosize : newDimension;
}
}
internal class DimFill (int margin) : Dim
@@ -723,11 +810,11 @@ public class Dim
}
string tside = side switch
{
0 => "Height",
1 => "Width",
_ => "unknown"
};
{
0 => "Height",
1 => "Width",
_ => "unknown"
};
return $"View({tside},{Target})";
}
@@ -735,11 +822,11 @@ public class Dim
internal override int Anchor (int width)
{
return side switch
{
0 => Target.Frame.Height,
1 => Target.Frame.Width,
_ => 0
};
{
0 => Target.Frame.Height,
1 => Target.Frame.Width,
_ => 0
};
}
}
}

View File

@@ -1011,7 +1011,6 @@ public partial class View
Debug.Assert (_width is { });
Debug.Assert (_height is { });
int newX, newW, newY, newH;
var autosize = Size.Empty;
if (AutoSize)
@@ -1021,176 +1020,19 @@ public partial class View
autosize = GetAutoSize ();
}
// TODO: Since GetNewLocationAndDimension does not depend on View, it can be moved into PosDim.cs
// TODO: to make architecture more clean. Do this after DimAuto is implemented and the
// TODO: View.AutoSize stuff is removed.
int newX, newW, newY, newH;
newX = _x.GetLocation (superviewContentSize.Width, _width, autosize.Width, AutoSize);
newW = _width.GetDimension (newX, superviewContentSize.Width, autosize.Width, AutoSize);
newY = _y.GetLocation (superviewContentSize.Height, _height, autosize.Height, AutoSize);
newH = _height.GetDimension (newY, superviewContentSize.Height, autosize.Height, AutoSize);
// Returns the new dimension (width or height) and location (x or y) for the View given
// the superview's Viewport
// the current Pos (View.X or View.Y)
// the current Dim (View.Width or View.Height)
// This method is called recursively if pos is Pos.PosCombine
(int newLocation, int newDimension) GetNewLocationAndDimension (
bool width,
Size superviewContentSize,
Pos pos,
Dim dim,
int autosizeDimension
)
{
// Gets the new dimension (width or height, dependent on `width`) of the given Dim given:
// location: the current location (x or y)
// dimension: the new dimension (width or height) (if relevant for Dim type)
// autosize: the size to use if autosize = true
// This method is recursive if d is Dim.DimCombine
int GetNewDimension (Dim d, int location, int dimension, int autosize)
{
int newDimension;
Rectangle newFrame = new (newX, newY, newW, newH);
switch (d)
{
case Dim.DimCombine combine:
// TODO: Move combine logic into DimCombine?
int leftNewDim = GetNewDimension (combine._left, location, dimension, autosize);
int rightNewDim = GetNewDimension (combine._right, location, dimension, autosize);
if (combine._add)
{
newDimension = leftNewDim + rightNewDim;
}
else
{
newDimension = leftNewDim - rightNewDim;
}
newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
break;
case Dim.DimFactor factor when !factor.IsFromRemaining ():
newDimension = d.Anchor (dimension);
newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
break;
case Dim.DimAbsolute:
// DimAbsolute.Anchor (int width) ignores width and returns n
newDimension = Math.Max (d.Anchor (0), 0);
// BUGBUG: AutoSize does two things: makes text fit AND changes the view's dimensions
newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
break;
case Dim.DimFill:
default:
newDimension = Math.Max (d.Anchor (dimension - location), 0);
newDimension = AutoSize && autosize > newDimension ? autosize : newDimension;
break;
}
return newDimension;
}
int newDimension, newLocation;
int superviewDimension = width ? superviewContentSize.Width : superviewContentSize.Height;
// Determine new location
switch (pos)
{
case Pos.PosCenter posCenter:
// For Center, the dimension is dependent on location, but we need to force getting the dimension first
// using a location of 0
newDimension = Math.Max (GetNewDimension (dim, 0, superviewDimension, autosizeDimension), 0);
newLocation = posCenter.Anchor (superviewDimension - newDimension);
newDimension = Math.Max (
GetNewDimension (dim, newLocation, superviewDimension, autosizeDimension),
0
);
break;
case Pos.PosCombine combine:
// TODO: Move combine logic into PosCombine?
int left, right;
(left, newDimension) = GetNewLocationAndDimension (
width,
superviewContentSize,
combine._left,
dim,
autosizeDimension
);
(right, newDimension) = GetNewLocationAndDimension (
width,
superviewContentSize,
combine._right,
dim,
autosizeDimension
);
if (combine._add)
{
newLocation = left + right;
}
else
{
newLocation = left - right;
}
newDimension = Math.Max (
GetNewDimension (dim, newLocation, superviewDimension, autosizeDimension),
0
);
break;
case Pos.PosAnchorEnd anchorEnd:
newLocation = anchorEnd.Anchor (superviewDimension);
if (anchorEnd.UseDimForOffset)
{
newLocation -= dim.Anchor (superviewDimension);
}
newDimension = Math.Max (
GetNewDimension (dim, newLocation, superviewDimension, autosizeDimension),
0
);
break;
case Pos.PosAbsolute:
case Pos.PosFactor:
case Pos.PosFunc:
case Pos.PosView:
default:
newLocation = pos?.Anchor (superviewDimension) ?? 0;
newDimension = Math.Max (
GetNewDimension (dim, newLocation, superviewDimension, autosizeDimension),
0
);
break;
}
return (newLocation, newDimension);
}
// horizontal/width
(newX, newW) = GetNewLocationAndDimension (true, superviewContentSize, _x, _width, autosize.Width);
// vertical/height
(newY, newH) = GetNewLocationAndDimension (false, superviewContentSize, _y, _height, autosize.Height);
Rectangle r = new (newX, newY, newW, newH);
if (Frame != r)
if (Frame != newFrame)
{
// Set the frame. Do NOT use `Frame` as it overwrites X, Y, Width, and Height, making
// the view LayoutStyle.Absolute.
SetFrame (r);
SetFrame (newFrame);
if (_x is Pos.PosAbsolute)
{

View File

@@ -1,6 +1,8 @@
using System.Globalization;
using System.Text;
using Xunit.Abstractions;
using static Terminal.Gui.Dim;
// Alias Console to MockConsole so we don't accidentally use Console
using Console = Terminal.Gui.FakeConsole;
@@ -22,6 +24,57 @@ public class DimTests
Thread.CurrentThread.CurrentUICulture = culture;
}
[Fact]
public void DimAbsolute_GetDimension_ReturnsCorrectValue ()
{
var dim = new DimAbsolute (10);
var result = dim.GetDimension (0, 100, 50, false);
Assert.Equal (10, result);
}
[Fact]
public void DimCombine_GetDimension_ReturnsCorrectValue ()
{
var dim1 = new DimAbsolute (10);
var dim2 = new DimAbsolute (20);
var dim = dim1 + dim2;
var result = dim.GetDimension (0, 100, 50, false);
Assert.Equal (30, result);
}
[Fact]
public void DimFactor_GetDimension_ReturnsCorrectValue ()
{
var dim = new DimFactor (0.5f);
var result = dim.GetDimension (0, 100, 50, false);
Assert.Equal (50, result);
}
[Fact]
public void DimFill_GetDimension_ReturnsCorrectValue ()
{
var dim = Dim.Fill ();
var result = dim.GetDimension (0, 100, 50, false);
Assert.Equal (100, result);
}
[Fact]
public void DimFunc_GetDimension_ReturnsCorrectValue ()
{
var dim = new DimFunc (() => 10);
var result = dim.GetDimension (0, 100, 50, false);
Assert.Equal (10, result);
}
[Fact]
public void DimView_GetDimension_ReturnsCorrectValue ()
{
var view = new View { Width = 10 };
var dim = new DimView (view, 1);
var result = dim.GetDimension (0, 100, 50, false);
Assert.Equal (10, result);
}
// TODO: This actually a SetRelativeLayout/LayoutSubViews test and should be moved
// A new test that does not depend on Application is needed.
[Fact]

View File

@@ -1,9 +1,67 @@
using Xunit.Abstractions;
using static Terminal.Gui.Dim;
using static Terminal.Gui.Pos;
namespace Terminal.Gui.ViewTests;
public class PosTests (ITestOutputHelper output)
{
[Fact]
public void PosAbsolute_GetLocation_ReturnsExpectedValue ()
{
var posAbsolute = new PosAbsolute (5);
var result = posAbsolute.GetLocation (10, new DimAbsolute (2), 1, false);
Assert.Equal (5, result);
}
[Fact]
public void PosAnchorEnd_GetLocation_ReturnsExpectedValue ()
{
var posAnchorEnd = new PosAnchorEnd (5);
var result = posAnchorEnd.GetLocation (10, new DimAbsolute (2), 1, false);
Assert.Equal (5, result);
}
[Fact]
public void PosCenter_GetLocation_ReturnsExpectedValue ()
{
var posCenter = new PosCenter ();
var result = posCenter.GetLocation (10, new DimAbsolute (2), 1, false);
Assert.Equal (4, result);
}
[Fact]
public void PosCombine_GetLocation_ReturnsExpectedValue ()
{
var posCombine = new PosCombine (true, new PosAbsolute (5), new PosAbsolute (3));
var result = posCombine.GetLocation (10, new DimAbsolute (2), 1, false);
Assert.Equal (8, result);
}
[Fact]
public void PosFactor_GetLocation_ReturnsExpectedValue ()
{
var posFactor = new PosFactor (0.5f);
var result = posFactor.GetLocation (10, new DimAbsolute (2), 1, false);
Assert.Equal (5, result);
}
[Fact]
public void PosFunc_GetLocation_ReturnsExpectedValue ()
{
var posFunc = new PosFunc (() => 5);
var result = posFunc.GetLocation (10, new DimAbsolute (2), 1, false);
Assert.Equal (5, result);
}
[Fact]
public void PosView_GetLocation_ReturnsExpectedValue ()
{
var posView = new PosView (new View { Frame = new Rectangle (5, 5, 10, 10) }, 0);
var result = posView.GetLocation (10, new DimAbsolute (2), 1, false);
Assert.Equal (5, result);
}
[Fact]
public void At_Equal ()
{