#nullable enable
using System.Drawing;
namespace Terminal.Gui;
///
/// Represents a dimension that automatically sizes the view to fit all the view's Content, SubViews, and/or Text.
///
///
///
/// See .
///
///
/// This is a low-level API that is typically used internally by the layout system. Use the various static
/// methods on the class to create objects instead.
///
///
public class DimAuto () : Dim
{
private readonly Dim? _maximumContentDim;
///
/// Gets the maximum dimension the View's ContentSize will be fit to. NOT CURRENTLY SUPPORTED.
///
// ReSharper disable once ConvertToAutoProperty
public required Dim? MaximumContentDim
{
get => _maximumContentDim;
init => _maximumContentDim = value;
}
private readonly Dim? _minimumContentDim;
///
/// Gets the minimum dimension the View's ContentSize will be constrained to.
///
// ReSharper disable once ConvertToAutoProperty
public required Dim? MinimumContentDim
{
get => _minimumContentDim;
init => _minimumContentDim = value;
}
private readonly DimAutoStyle _style;
///
/// Gets the style of the DimAuto.
///
// ReSharper disable once ConvertToAutoProperty
public required DimAutoStyle Style
{
get => _style;
init => _style = value;
}
///
public override string ToString () { return $"Auto({Style},{MinimumContentDim},{MaximumContentDim})"; }
internal override int Calculate (int location, int superviewContentSize, View us, Dimension dimension)
{
var textSize = 0;
var subviewsSize = 0;
int autoMin = MinimumContentDim?.GetAnchor (superviewContentSize) ?? 0;
int autoMax = MaximumContentDim?.GetAnchor (superviewContentSize) ?? int.MaxValue;
if (Style.FastHasFlags (DimAutoStyle.Text))
{
textSize = int.Max (autoMin, dimension == Dimension.Width ? us.TextFormatter.Size.Width : us.TextFormatter.Size.Height);
}
if (Style.FastHasFlags (DimAutoStyle.Content))
{
if (!us.ContentSizeTracksViewport)
{
// ContentSize was explicitly set. Ignore subviews.
subviewsSize = dimension == Dimension.Width ? us.GetContentSize ().Width : us.GetContentSize ().Height;
}
else
{
// ContentSize was NOT explicitly set. Use subviews to determine size.
// TODO: This whole body of code is a WIP (for https://github.com/gui-cs/Terminal.Gui/pull/3451).
subviewsSize = 0;
List includedSubviews = us.Subviews.ToList ();//.Where (v => !v.ExcludeFromLayout).ToList ();
List subviews;
#region Not Anchored and Are Not Dependent
// Start with subviews that are not anchored to the end, aligned, or dependent on content size
// [x] PosAnchorEnd
// [x] PosAlign
// [ ] PosCenter
// [ ] PosPercent
// [ ] PosView
// [ ] PosFunc
// [x] DimFill
// [ ] DimPercent
// [ ] DimFunc
// [ ] DimView
if (dimension == Dimension.Width)
{
subviews = includedSubviews.Where (v => v.X is not PosAnchorEnd
&& v.X is not PosAlign
// && v.X is not PosCenter
&& v.Width is not DimAuto
&& v.Width is not DimFill).ToList ();
}
else
{
subviews = includedSubviews.Where (v => v.Y is not PosAnchorEnd
&& v.Y is not PosAlign
// && v.Y is not PosCenter
&& v.Height is not DimAuto
&& v.Height is not DimFill).ToList ();
}
for (var i = 0; i < subviews.Count; i++)
{
View v = subviews [i];
int size = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height;
if (size > subviewsSize)
{
// BUGBUG: Should we break here? Or choose min/max?
subviewsSize = size;
}
}
#endregion Not Anchored and Are Not Dependent
#region Anchored
// Now, handle subviews that are anchored to the end
// [x] PosAnchorEnd
if (dimension == Dimension.Width)
{
subviews = includedSubviews.Where (v => v.X is PosAnchorEnd).ToList ();
}
else
{
subviews = includedSubviews.Where (v => v.Y is PosAnchorEnd).ToList ();
}
int maxAnchorEnd = 0;
for (var i = 0; i < subviews.Count; i++)
{
View v = subviews [i];
maxAnchorEnd = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height;
}
subviewsSize += maxAnchorEnd;
#endregion Anchored
//#region Aligned
//// Now, handle subviews that are anchored to the end
//// [x] PosAnchorEnd
//int maxAlign = 0;
//if (dimension == Dimension.Width)
//{
// // Use Linq to get a list of distinct GroupIds from the subviews
// List groupIds = includedSubviews.Select (v => v.X is PosAlign posAlign ? posAlign.GroupId : -1).Distinct ().ToList ();
// foreach (var groupId in groupIds)
// {
// List dimensionsList = new ();
// // PERF: If this proves a perf issue, consider caching a ref to this list in each item
// List posAlignsInGroup = includedSubviews.Where (
// v =>
// {
// return dimension switch
// {
// Dimension.Width when v.X is PosAlign alignX => alignX.GroupId == groupId,
// Dimension.Height when v.Y is PosAlign alignY => alignY.GroupId == groupId,
// _ => false
// };
// })
// .Select (v => dimension == Dimension.Width ? v.X as PosAlign : v.Y as PosAlign)
// .ToList ();
// if (posAlignsInGroup.Count == 0)
// {
// continue;
// }
// maxAlign = PosAlign.CalculateMinDimension (groupId, includedSubviews, dimension);
// }
//}
//else
//{
// subviews = includedSubviews.Where (v => v.Y is PosAlign).ToList ();
//}
//subviewsSize = int.Max (subviewsSize, maxAlign);
//#endregion Aligned
#region Auto
if (dimension == Dimension.Width)
{
subviews = includedSubviews.Where (v => v.Width is DimAuto).ToList ();
}
else
{
subviews = includedSubviews.Where (v => v.Height is DimAuto).ToList ();
}
int maxAuto = 0;
for (var i = 0; i < subviews.Count; i++)
{
View v = subviews [i];
//if (dimension == Dimension.Width)
//{
// v.SetRelativeLayout (new Size (autoMax - subviewsSize, 0));
//}
//else
//{
// v.SetRelativeLayout (new Size (0, autoMax - subviewsSize));
//}
maxAuto = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height;
if (maxAuto > subviewsSize)
{
// BUGBUG: Should we break here? Or choose min/max?
subviewsSize = maxAuto;
}
}
// subviewsSize += maxAuto;
#endregion Auto
//#region Center
//// Now, handle subviews that are Centered
//if (dimension == Dimension.Width)
//{
// subviews = us.Subviews.Where (v => v.X is PosCenter).ToList ();
//}
//else
//{
// subviews = us.Subviews.Where (v => v.Y is PosCenter).ToList ();
//}
//int maxCenter = 0;
//for (var i = 0; i < subviews.Count; i++)
//{
// View v = subviews [i];
// maxCenter = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height;
//}
//subviewsSize += maxCenter;
//#endregion Center
#region Are Dependent
// Now, go back to those that are dependent on content size
// [x] DimFill
// [ ] DimPercent
if (dimension == Dimension.Width)
{
subviews = includedSubviews.Where (v => v.Width is DimFill).ToList ();
}
else
{
subviews = includedSubviews.Where (v => v.Height is DimFill).ToList ();
}
int maxFill = 0;
for (var i = 0; i < subviews.Count; i++)
{
View v = subviews [i];
if (autoMax == int.MaxValue)
{
autoMax = superviewContentSize;
}
if (dimension == Dimension.Width)
{
v.SetRelativeLayout (new Size (autoMax - subviewsSize, 0));
}
else
{
v.SetRelativeLayout (new Size (0, autoMax - subviewsSize));
}
maxFill = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height;
}
subviewsSize += maxFill;
#endregion Are Dependent
}
}
// All sizes here are content-relative; ignoring adornments.
// We take the largest of text and content.
int max = int.Max (textSize, subviewsSize);
// And, if min: is set, it wins if larger
max = int.Max (max, autoMin);
// And, if max: is set, it wins if smaller
max = int.Min (max, autoMax);
// Factor in adornments
Thickness thickness = us.GetAdornmentsThickness ();
max += dimension switch
{
Dimension.Width => thickness.Horizontal,
Dimension.Height => thickness.Vertical,
Dimension.None => 0,
_ => throw new ArgumentOutOfRangeException (nameof (dimension), dimension, null)
};
return max;
}
internal override bool ReferencesOtherViews ()
{
// BUGBUG: This is not correct. _contentSize may be null.
return false; //_style.HasFlag (DimAutoStyle.Content);
}
///
public override bool Equals (object? other)
{
if (other is not DimAuto auto)
{
return false;
}
return auto.MinimumContentDim == MinimumContentDim &&
auto.MaximumContentDim == MaximumContentDim &&
auto.Style == Style;
}
///
public override int GetHashCode ()
{
return HashCode.Combine (MinimumContentDim, MaximumContentDim, Style);
}
}