#nullable enable
using System.Diagnostics;
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 maxCalculatedSize = 0;
int autoMin = MinimumContentDim?.GetAnchor (superviewContentSize) ?? 0;
int screen = dimension == Dimension.Width ? Application.Screen.Width * 4 : Application.Screen.Height * 4;
int autoMax = MaximumContentDim?.GetAnchor (superviewContentSize) ?? screen;
if (Style.FastHasFlags (DimAutoStyle.Text))
{
if (dimension == Dimension.Width)
{
us.TextFormatter.Size = new (autoMax, 2048);
textSize = int.Max (autoMin, us.TextFormatter.FormatAndGetSize ().Width);
us.TextFormatter.Size = new Size (textSize, 2048);
}
else
{
if (us.TextFormatter.Size.Width == 0)
{
us.TextFormatter.Size = us.TextFormatter.GetAutoSize ();
}
textSize = int.Max (autoMin, us.TextFormatter.FormatAndGetSize ().Height);
us.TextFormatter.Size = us.TextFormatter.Size with { Height = textSize };
}
}
if (Style.FastHasFlags (DimAutoStyle.Content))
{
if (!us.ContentSizeTracksViewport)
{
// ContentSize was explicitly set. Use `us.ContentSize` to determine size.
maxCalculatedSize = dimension == Dimension.Width ? us.GetContentSize ().Width : us.GetContentSize ().Height;
}
else
{
maxCalculatedSize = textSize;
// ContentSize was NOT explicitly set. Use `us.Subviews` to determine size.
List includedSubviews = us.Subviews.ToList ();
// If [x] it can cause `us.ContentSize` to change.
// If [ ] it doesn't need special processing for us to determine `us.ContentSize`.
// -------------------- Pos types that are dependent on `us.Subviews`
// [ ] PosAlign - Position is dependent on other views with `GroupId` AND `us.ContentSize`
// [x] PosView - Position is dependent on `subview.Target` - it can cause a change in `us.ContentSize`
// [x] PosCombine - Position is dependent if `Pos.Has ([one of the above]` - it can cause a change in `us.ContentSize`
// -------------------- Pos types that are dependent on `us.ContentSize`
// [ ] PosAlign - Position is dependent on other views with `GroupId` AND `us.ContentSize`
// [x] PosAnchorEnd - Position is dependent on `us.ContentSize` AND `subview.Frame` - it can cause a change in `us.ContentSize`
// [ ] PosCenter - Position is dependent `us.ContentSize` AND `subview.Frame`
// [ ] PosPercent - Position is dependent `us.ContentSize`
// [x] PosCombine - Position is dependent if `Pos.Has ([one of the above]` - it can cause a change in `us.ContentSize`
// -------------------- Pos types that are not dependent on either `us.Subviews` or `us.ContentSize`
// [ ] PosAbsolute - Position is fixed.
// [ ] PosFunc - Position is internally calculated.
// -------------------- Dim types that are dependent on `us.Subviews`
// [x] DimView - Dimension is dependent on `subview.Target`
// [x] DimCombine - Dimension is dependent if `Dim.Has ([one of the above]` - it can cause a change in `us.ContentSize`
// -------------------- Dim types that are dependent on `us.ContentSize`
// [ ] DimFill - Dimension is dependent on `us.ContentSize`
// [ ] DimPercent - Dimension is dependent on `us.ContentSize`
// [ ] DimCombine - Dimension is dependent if `Dim.Has ([one of the above]`
// -------------------- Dim types that are not dependent on either `us.Subviews` or `us.ContentSize`
// [ ] DimAuto - Dimension is internally calculated
// [ ] DimAbsolute - Dimension is fixed
// [ ] DimFunc - Dimension is internally calculated
// ======================================================
// Do the easy stuff first - subviews whose position and size are not dependent on other views or content size
// ======================================================
// [ ] PosAbsolute - Position is fixed.
// [ ] PosFunc - Position is internally calculated
// [ ] DimAuto - Dimension is internally calculated
// [ ] DimAbsolute - Dimension is fixed
// [ ] DimFunc - Dimension is internally calculated
List notDependentSubViews;
if (dimension == Dimension.Width)
{
notDependentSubViews = includedSubviews.Where (v => v.Width is { } &&
(v.X is PosAbsolute or PosFunc || v.Width is DimAuto or DimAbsolute or DimFunc) &&
!v.X.Has (typeof (PosAnchorEnd), out _) &&
!v.X.Has (typeof (PosAlign), out _) &&
!v.X.Has (typeof (PosView), out _) &&
!v.Width.Has (typeof (DimView), out _) &&
!v.X.Has (typeof (PosCenter), out _)).ToList ();
}
else
{
notDependentSubViews = includedSubviews.Where (v => v.Height is { } &&
(v.Y is PosAbsolute or PosFunc || v.Height is DimAuto or DimAbsolute or DimFunc) &&
!v.Y.Has (typeof (PosAnchorEnd), out _) &&
!v.Y.Has (typeof (PosAlign), out _) &&
!v.Y.Has (typeof (PosView), out _) &&
!v.Height.Has (typeof (DimView), out _) &&
!v.Y.Has (typeof (PosCenter), out _)).ToList ();
}
for (var i = 0; i < notDependentSubViews.Count; i++)
{
View v = notDependentSubViews [i];
int size = 0;
if (dimension == Dimension.Width)
{
int width = v.Width!.Calculate (0, 0, v, dimension);
size = v.X.GetAnchor (0) + width;
}
else
{
int height = v.Height!.Calculate (0, 0, v, dimension);
size = v.Y.GetAnchor (0) + height;
}
if (size > maxCalculatedSize)
{
maxCalculatedSize = size;
}
}
// ************** We now have some idea of `us.ContentSize` ***************
#region Centered
// [ ] PosCenter - Position is dependent `us.ContentSize` AND `subview.Frame`
List centeredSubViews;
if (dimension == Dimension.Width)
{
centeredSubViews = us.Subviews.Where (v => v.X.Has (typeof (PosCenter), out _)).ToList ();
}
else
{
centeredSubViews = us.Subviews.Where (v => v.Y.Has (typeof (PosCenter), out _)).ToList ();
}
int maxCentered = 0;
for (var i = 0; i < centeredSubViews.Count; i++)
{
View v = centeredSubViews [i];
if (dimension == Dimension.Width)
{
int width = v.Width!.Calculate (0, 0, v, dimension);
maxCentered = (v.X.GetAnchor (0) + width) * 2;
}
else
{
int height = v.Height!.Calculate (0, 0, v, dimension);
maxCentered = (v.Y.GetAnchor (0) + height) * 2;
}
}
maxCalculatedSize = int.Max (maxCalculatedSize, maxCentered);
#endregion Centered
#region Percent
// [ ] DimPercent - Dimension is dependent on `us.ContentSize`
List percentSubViews;
if (dimension == Dimension.Width)
{
percentSubViews = us.Subviews.Where (v => v.Width.Has (typeof (DimPercent), out _)).ToList ();
}
else
{
percentSubViews = us.Subviews.Where (v => v.Height.Has (typeof (DimPercent), out _)).ToList ();
}
int maxPercent = 0;
for (var i = 0; i < percentSubViews.Count; i++)
{
View v = percentSubViews [i];
if (dimension == Dimension.Width)
{
int width = v.Width!.Calculate (0, 0, v, dimension);
maxPercent = (v.X.GetAnchor (0) + width);
}
else
{
int height = v.Height!.Calculate (0, 0, v, dimension);
maxPercent = (v.Y.GetAnchor (0) + height);
}
}
maxCalculatedSize = int.Max (maxCalculatedSize, maxPercent);
#endregion Percent
#region Aligned
// [ ] PosAlign - Position is dependent on other views with `GroupId` AND `us.ContentSize`
int maxAlign = 0;
// Use Linq to get a list of distinct GroupIds from the subviews
List groupIds = includedSubviews.Select (
v =>
{
if (dimension == Dimension.Width)
{
if (v.X.Has (typeof (PosAlign), out Pos posAlign))
{
return ((PosAlign)posAlign).GroupId;
}
}
else
{
if (v.Y.Has (typeof (PosAlign), out Pos posAlign))
{
return ((PosAlign)posAlign).GroupId;
}
}
return -1;
}).Distinct ().ToList ();
foreach (var groupId in groupIds.Where (g => g != -1))
{
// 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);
}
maxCalculatedSize = int.Max (maxCalculatedSize, maxAlign);
#endregion Aligned
#region Anchored
// [x] PosAnchorEnd - Position is dependent on `us.ContentSize` AND `subview.Frame`
List anchoredSubViews;
if (dimension == Dimension.Width)
{
anchoredSubViews = includedSubviews.Where (v => v.X.Has (typeof (PosAnchorEnd), out _)).ToList ();
}
else
{
anchoredSubViews = includedSubviews.Where (v => v.Y.Has (typeof (PosAnchorEnd), out _)).ToList ();
}
int maxAnchorEnd = 0;
for (var i = 0; i < anchoredSubViews.Count; i++)
{
View v = anchoredSubViews [i];
// Need to set the relative layout for PosAnchorEnd subviews to calculate the size
if (dimension == Dimension.Width)
{
v.SetRelativeLayout (new Size (maxCalculatedSize, 0));
}
else
{
v.SetRelativeLayout (new Size (0, maxCalculatedSize));
}
maxAnchorEnd = dimension == Dimension.Width ? v.X.GetAnchor (maxCalculatedSize) + v.Frame.Width : v.Y.GetAnchor (maxCalculatedSize) + v.Frame.Height;
}
maxCalculatedSize = Math.Max (maxCalculatedSize, maxAnchorEnd);
#endregion Anchored
#region PosView
// [x] PosView - Position is dependent on `subview.Target` - it can cause a change in `us.ContentSize`
List posViewSubViews;
if (dimension == Dimension.Width)
{
posViewSubViews = includedSubviews.Where (v => v.X.Has (typeof (PosView), out _)).ToList ();
}
else
{
posViewSubViews = includedSubviews.Where (v => v.Y.Has (typeof (PosView), out _)).ToList ();
}
for (var i = 0; i < posViewSubViews.Count; i++)
{
View v = posViewSubViews [i];
// BUGBUG: The order may not be correct. May need to call TopologicalSort?
if (dimension == Dimension.Width)
{
v.SetRelativeLayout (new Size (maxCalculatedSize, 0));
}
else
{
v.SetRelativeLayout (new Size (0, maxCalculatedSize));
}
int maxPosView = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height;
if (maxPosView > maxCalculatedSize)
{
maxCalculatedSize = maxPosView;
}
}
#endregion PosView
// [x] PosCombine - Position is dependent if `Pos.Has ([one of the above]` - it can cause a change in `us.ContentSize`
#region DimView
// [x] DimView - Dimension is dependent on `subview.Target` - it can cause a change in `us.ContentSize`
List dimViewSubViews;
if (dimension == Dimension.Width)
{
dimViewSubViews = includedSubviews.Where (v => v.Width is { } && v.Width.Has (typeof (DimView), out _)).ToList ();
}
else
{
dimViewSubViews = includedSubviews.Where (v => v.Height is { } && v.Height.Has (typeof (DimView), out _)).ToList ();
}
for (var i = 0; i < dimViewSubViews.Count; i++)
{
View v = dimViewSubViews [i];
// BUGBUG: The order may not be correct. May need to call TopologicalSort?
if (dimension == Dimension.Width)
{
v.SetRelativeLayout (new Size (maxCalculatedSize, 0));
}
else
{
v.SetRelativeLayout (new Size (0, maxCalculatedSize));
}
int maxDimView = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height;
if (maxDimView > maxCalculatedSize)
{
maxCalculatedSize = maxDimView;
}
}
#endregion DimView
// [x] DimCombine - Dimension is dependent if `Dim.Has ([one of the above]` - it can cause a change in `us.ContentSize`
// // ======================================================
// // Now do PosAlign - It's dependent on other views with `GroupId` AND `us.ContentSize`
// // ======================================================
// // [ ] PosAlign - Position is dependent on other views with `GroupId` AND `us.ContentSize`
// #region Aligned
// 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;
// }
// // BUGBUG: ignores adornments
// maxAlign = PosAlign.CalculateMinDimension (groupId, includedSubviews, dimension);
// }
// }
// else
// {
// // BUGBUG: Incompletge
// subviews = includedSubviews.Where (v => v.Y is PosAlign).ToList ();
// }
// maxCalculatedSize = int.Max (maxCalculatedSize, maxAlign);
// #endregion Aligned
// // TODO: This whole body of code is a WIP (forhttps://github.com/gui-cs/Terminal.Gui/issues/3499).
// 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 > maxCalculatedSize)
// {
// // BUGBUG: Should we break here? Or choose min/max?
// maxCalculatedSize = size;
// }
// }
// #endregion Not Anchored and Are Not Dependent
// #region Auto
// #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
// // Set relative layout for all DimAuto subviews
// List dimAutoSubViews;
// int maxAuto = 0;
// if (dimension == Dimension.Width)
// {
// dimAutoSubViews = includedSubviews.Where (v => v.Width is DimAuto).ToList ();
// }
// else
// {
// dimAutoSubViews = includedSubviews.Where (v => v.Height is DimAuto).ToList ();
// }
// for (var i = 0; i < dimAutoSubViews.Count; i++)
// {
// View v = dimAutoSubViews [i];
// if (dimension == Dimension.Width)
// {
// // BUGBUG: ignores adornments
// v.SetRelativeLayout (new Size (autoMax - maxCalculatedSize, 0));
// }
// else
// {
// // BUGBUG: ignores adornments
// v.SetRelativeLayout (new Size (0, autoMax - maxCalculatedSize));
// }
// maxAuto = dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height;
// if (maxAuto > maxCalculatedSize)
// {
// // BUGBUG: Should we break here? Or choose min/max?
// maxCalculatedSize = maxAuto;
// }
// }
// // [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)
// {
// // BUGBUG: ignores adornments
// v.SetRelativeLayout (new Size (autoMax - maxCalculatedSize, 0));
// }
// else
// {
// // BUGBUG: ignores adornments
// v.SetRelativeLayout (new Size (0, autoMax - maxCalculatedSize));
// }
// maxFill = dimension == Dimension.Width ? v.Frame.Width : v.Frame.Height;
// }
// maxCalculatedSize += 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, maxCalculatedSize);
// 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);
// ************** We now definitively know `us.ContentSize` ***************
int oppositeScreen = dimension == Dimension.Width ? Application.Screen.Height * 4 : Application.Screen.Width * 4 ;
foreach (var v in us.Subviews)
{
if (dimension == Dimension.Width)
{
v.SetRelativeLayout (new Size (max, oppositeScreen));
}
else
{
v.SetRelativeLayout (new Size (oppositeScreen, max));
}
}
// Factor in adornments
Thickness thickness = us.GetAdornmentsThickness ();
var adornmentThickness = dimension switch
{
Dimension.Width => thickness.Horizontal,
Dimension.Height => thickness.Vertical,
Dimension.None => 0,
_ => throw new ArgumentOutOfRangeException (nameof (dimension), dimension, null)
};
max += adornmentThickness;
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);
}
}