372 lines
17 KiB
C#

using System;
using System.Collections.Generic;
using System.Text;
using System.Drawing;
namespace DevComponents.DotNetBar.Layout
{
internal class ItemLayout
{
public void Layout(LayoutGroup group, Rectangle clientBounds, LayoutContext context)
{
if (clientBounds.IsEmpty) return;
List<LayoutItemStrip> strips = SliceItemsInStrips(group, clientBounds, context);
ResizeStrips(strips, clientBounds);
Rectangle totalBounds = FinalizeItemSize(strips, clientBounds, context);
group.ActualBounds = totalBounds;
//Console.WriteLine("{0} -> {1}", clientBounds, totalBounds);
}
private LayoutContext CreateLayoutContext(LayoutContext context, int startAbsoluteIndex)
{
LayoutContext newContext = new LayoutContext(context.LayoutControl, context.Graphics, context.Font);
newContext.RightToLeft = context.RightToLeft;
newContext.AbsoluteIndex = startAbsoluteIndex;
return newContext;
}
private Rectangle FinalizeItemSize(List<LayoutItemStrip> strips, Rectangle clientBounds, LayoutContext context)
{
int sizeLimit = GetSizeLimit(clientBounds);
int stripSizeLimit = GetStripsSizeLimit(clientBounds);
Rectangle totalBounds = new Rectangle(clientBounds.Location, Size.Empty);
Point loc = clientBounds.Location;
Rectangle fullClientBounds = clientBounds;
int absIndex = context.AbsoluteIndex;
foreach (LayoutItemStrip strip in strips)
{
LayoutItemBase layoutItem = strip.Items[0];
int clientBoundsWidth = clientBounds.Width;
if (strip.IsLeftSpanStrip)
{
Size size = new Size(Math.Max(MinItemSize(layoutItem), ActualItemSize(layoutItem, fullClientBounds.Width)),
Math.Max(strip.MinStripHeightAbsolute, clientBounds.Height));
Rectangle r = new Rectangle(loc, size);
clientBounds.X += size.Width;
clientBounds.Width -= size.Width;
loc = clientBounds.Location;
layoutItem.SetBounds(r, (layoutItem.SharedTextSizeEnabled ? context.LargestTextSize : layoutItem.RealTextSize));
if (layoutItem is LayoutGroup)
{
LayoutContext groupContext = CreateLayoutContext(context, absIndex++);
((LayoutGroup)layoutItem).Layout(groupContext);
absIndex = groupContext.AbsoluteIndex;
}
totalBounds = Rectangle.Union(r, totalBounds);
layoutItem.AbsoluteIndex = absIndex++;
continue;
}
else if (strip.IsRightSpanStrip)
{
Size size = new Size(Math.Max(MinItemSize(layoutItem), ActualItemSize(layoutItem, fullClientBounds.Width)),
Math.Max(strip.MinStripHeightAbsolute, clientBounds.Height));
Rectangle r = new Rectangle(clientBounds.Right - size.Width, clientBounds.Y, size.Width, size.Height);
clientBounds.Width -= size.Width;
layoutItem.SetBounds(r, layoutItem.SharedTextSizeEnabled ? context.LargestTextSize : layoutItem.RealTextSize);
if (layoutItem is LayoutGroup)
{
LayoutContext groupContext = CreateLayoutContext(context, 10000/*absIndex++*/); // Pushing the tab index for right span strip to the right most
((LayoutGroup)layoutItem).Layout(groupContext);
//absIndex = groupContext.AbsoluteIndex;
}
totalBounds = Rectangle.Union(r, totalBounds);
layoutItem.AbsoluteIndex = absIndex++;
//layoutItem.ActualTextSize = layoutItem.SharedTextSizeEnabled ? context.LargestTextSize : layoutItem.RealTextSize;
continue;
}
bool repeat = false;
int repeatCount = 0;
int startAbsIndex = absIndex;
do
{
loc.X = clientBounds.X;
repeatCount++;
absIndex = startAbsIndex;
int totalPercentWidth = 0;
foreach (LayoutItemBase item in strip.Items)
{
Size size = new Size(Math.Max(MinItemSize(item), ActualItemSize(item, clientBoundsWidth - strip.TotalFixedWidth)),
strip.StripHeightAbsolute);
if (item.WidthType == eLayoutSizeType.Percent)
totalPercentWidth += item.Width;
if (strip.Items.Count == 1 && size.Width < clientBoundsWidth && clientBoundsWidth >= MinItemSize(item) && item.WidthType == eLayoutSizeType.Percent)
size.Width = clientBoundsWidth;
else if (loc.X + size.Width > clientBounds.X + clientBoundsWidth && strip.Items.Count > 1)
{
if (strip.Items[strip.Items.Count - 1] == item && MinItemSize(item) <= clientBoundsWidth - (loc.X - clientBounds.X))
size.Width = clientBoundsWidth - (loc.X - clientBounds.X);
else
{
clientBoundsWidth -= (loc.X + size.Width) - (clientBounds.X + clientBoundsWidth);
repeat = true;
break;
}
}
else if(strip.TotalFixedWidth == 0 && totalPercentWidth == 100 && strip.Items[strip.Items.Count - 1] == item && size.Width<clientBoundsWidth - (loc.X - clientBounds.X))
size.Width = clientBoundsWidth - (loc.X - clientBounds.X);
Rectangle r = new Rectangle(loc, size);
item.StripTextBaseline = strip.LargestTextBaseline;
item.SetBounds(r, item.SharedTextSizeEnabled ? context.LargestTextSize : item.RealTextSize);
if (item is LayoutGroup)
{
LayoutContext groupContext = CreateLayoutContext(context, absIndex++);
((LayoutGroup)item).Layout(groupContext);
absIndex = groupContext.AbsoluteIndex;
}
item.AbsoluteIndex = absIndex++;
totalBounds = Rectangle.Union(r, totalBounds);
loc.X += size.Width;
}
} while (repeat && repeatCount < 2);
loc.Y += strip.StripHeightAbsolute;
loc.X = clientBounds.X;
}
context.AbsoluteIndex = absIndex;
return totalBounds;
}
private void ResizeStrips(List<LayoutItemStrip> strips, Rectangle clientBounds)
{
int totalFixedSize = 0;
List<LayoutItemStrip> percenageStrips = new List<LayoutItemStrip>();
foreach (LayoutItemStrip strip in strips)
{
if (strip.StripHeightPercentage == 0)
totalFixedSize += strip.StripHeightAbsolute;
else
percenageStrips.Add(strip);
}
if (percenageStrips.Count > 0)
{
int sizeLimit = GetStripsSizeLimit(clientBounds);
if (totalFixedSize < sizeLimit)
sizeLimit -= totalFixedSize;
foreach (LayoutItemStrip strip in percenageStrips)
{
strip.StripHeightAbsolute = Math.Max(strip.StripHeightAbsolute, (sizeLimit * strip.StripHeightPercentage) / 100);
strip.StripHeightAbsolute = Math.Max(strip.StripHeightAbsolute, strip.MinStripHeightAbsolute);
}
}
}
private List<LayoutItemStrip> SliceItemsInStrips(LayoutGroup group, Rectangle clientBounds, LayoutContext context)
{
LayoutItemCollection items = group.Items;
List<LayoutItemStrip> strips = new List<LayoutItemStrip>();
if (items.Count == 0) return strips;
int sizeLimit = GetSizeLimit(clientBounds);
int currentPercent = 0;
int currentFixedSize = 0;
int currentTotalMinSize = 0;
int start = 0, end = items.Count;
Size largestTextSize = Size.Empty;
LayoutItemStrip currentStrip = new LayoutItemStrip();
// Check whether first or last item spans total client bounds effectively reducing the client width
int firstVisibleIndex = GetFirstVisibleItemIndex(items);
int lastVisibleIndex = GetLastVisibleItemIndex(items);
LayoutItemBase spanItem = null;
if (firstVisibleIndex >= 0) spanItem = items[firstVisibleIndex];
if (spanItem != null && !IsFixedStripSize(spanItem) && ItemStripSize(spanItem) >= 100)
{
if (spanItem.IsTextSizeShared)
largestTextSize = Helpers.Max(largestTextSize, spanItem.MeasureText(context));
else
spanItem.MeasureText(context);
sizeLimit -= ActualItemSize(spanItem, clientBounds.Width);
currentStrip.IsLeftSpanStrip = true;
currentStrip.StripHeightPercentage = 100;
currentStrip.MinStripHeightAbsolute = spanItem.MinSize.Height;
currentStrip.Items.Add(spanItem);
strips.Add(currentStrip);
currentStrip = new LayoutItemStrip();
start = firstVisibleIndex + 1;
}
if (firstVisibleIndex != lastVisibleIndex && lastVisibleIndex > 0)
{
spanItem = items[lastVisibleIndex];
if (!IsFixedStripSize(spanItem) && ItemStripSize(spanItem) >= 100 && !(!IsFixedSize(spanItem) && ItemSize(spanItem) >= 100))
{
if (spanItem.IsTextSizeShared)
largestTextSize = Helpers.Max(largestTextSize, spanItem.MeasureText(context));
else
spanItem.MeasureText(context);
sizeLimit -= ActualItemSize(spanItem, clientBounds.Width);
currentStrip.IsRightSpanStrip = true;
currentStrip.StripHeightPercentage = 100;
currentStrip.MinStripHeightAbsolute = spanItem.MinSize.Height;
currentStrip.Items.Add(spanItem);
strips.Add(currentStrip);
currentStrip = new LayoutItemStrip();
end = lastVisibleIndex;
}
}
for (int i = start; i < end; i++)
{
LayoutItemBase item = items[i];
if (!item.Visible) continue;
int itemSize = ItemSize(item);
if (item.IsTextSizeShared)
largestTextSize = Helpers.Max(largestTextSize, item.MeasureText(context));
else
item.MeasureText(context);
if (IsFixedSize(item))
{
if (currentTotalMinSize + itemSize > sizeLimit && currentTotalMinSize > 0 || currentPercent >= 100)
{
// Trigger new line
currentStrip.TotalFixedWidth = currentFixedSize;
strips.Add(currentStrip);
currentStrip = new LayoutItemStrip();
currentFixedSize = Math.Max(itemSize, MinItemSize(item)); ;
currentTotalMinSize = itemSize;
currentPercent = 0;
}
else
{
currentFixedSize += Math.Max(itemSize, MinItemSize(item));
currentTotalMinSize += itemSize;
}
if (item.HeightType == eLayoutSizeType.Percent)
currentStrip.StripHeightPercentage = Math.Max(currentStrip.StripHeightPercentage, item.Height);
else
currentStrip.StripHeightAbsolute = Math.Max(currentStrip.StripHeightAbsolute, item.Height);
}
else
{
int minItemSize = MinItemSize(item);
if (currentPercent + itemSize > 100 && currentPercent > 0 || (currentTotalMinSize + minItemSize > sizeLimit || itemSize > 100) && currentTotalMinSize > 0)
{
// Trigger new line
currentStrip.TotalFixedWidth = currentFixedSize;
strips.Add(currentStrip);
currentStrip = new LayoutItemStrip();
currentFixedSize = 0;
currentTotalMinSize = 0;
currentPercent = 0;
}
//else
//{
currentPercent += itemSize;
currentTotalMinSize += minItemSize;
//}
if (item.HeightType == eLayoutSizeType.Percent)
currentStrip.StripHeightPercentage = Math.Max(currentStrip.StripHeightPercentage, item.Height);
else
currentStrip.StripHeightAbsolute = Math.Max(currentStrip.StripHeightAbsolute, item.Height);
}
currentStrip.MinStripHeightAbsolute = Math.Max(currentStrip.MinStripHeightAbsolute, item.MinSize.Height);
if (item.IsTextBaselineShared)
currentStrip.LargestTextBaseline = Math.Max(currentStrip.LargestTextBaseline, item.TextBaseline);
currentStrip.Items.Add(item);
}
if (currentStrip.Items.Count > 0)
{
strips.Add(currentStrip);
currentStrip.TotalFixedWidth = currentFixedSize;
}
context.LargestTextSize = largestTextSize;
return strips;
}
private int GetFirstVisibleItemIndex(LayoutItemCollection items)
{
for (int i = 0; i < items.Count; i++)
{
if (items[i].Visible) return i;
}
return -1;
}
private int GetLastVisibleItemIndex(LayoutItemCollection items)
{
for (int i = items.Count - 1; i >= 0; i--)
{
if (items[i].Visible) return i;
}
return -1;
}
private int ActualItemSize(LayoutItemBase item, int clientSize)
{
if (item.WidthType == eLayoutSizeType.Absolute)
return item.Width;
else
return (Math.Min(100, (item.Width == 99 ? 100 : item.Width)) * clientSize) / 100; // 99% is special case where we have relative size item inside of fixed size items on single line
}
private int MinItemSize(LayoutItemBase item)
{
return item.MinSize.Width;
}
private int GetSizeLimit(Rectangle clientBounds)
{
return clientBounds.Width;
}
private int GetStripsSizeLimit(Rectangle clientBounds)
{
return clientBounds.Height;
}
private int ItemSize(LayoutItemBase item)
{
return item.Width;
}
private int ItemStripSize(LayoutItemBase item)
{
return item.Height;
}
private bool IsFixedSize(LayoutItemBase item)
{
return item.IsWidthFixed;
}
private bool IsFixedStripSize(LayoutItemBase item)
{
return item.IsHeightFixed;
}
}
internal class LayoutItemStrip
{
/// <summary>
/// Collection of items inside of strip.
/// </summary>
public List<LayoutItemBase> Items = new List<LayoutItemBase>();
/// <summary>
/// Total width of all items with fixed width in the strip.
/// </summary>
public int TotalFixedWidth = 0;
/// <summary>
/// True if this is left most strip in group which spans whole group height.
/// </summary>
public bool IsLeftSpanStrip = false;
/// <summary>
/// True if this is right most strip in group which spans whole group height.
/// </summary>
public bool IsRightSpanStrip = false;
/// <summary>
/// Strip height determined as maximum height of items in it.
/// </summary>
public int StripHeightAbsolute = 0;
/// <summary>
/// Minimum strip height according to the larges MinSize of contained items.
/// </summary>
public int MinStripHeightAbsolute = 0;
/// <summary>
/// Maximum percentage strip height for all items in it.
/// </summary>
public int StripHeightPercentage = 0;
/// <summary>
/// The largest text-baseline for all items in strip.
/// </summary>
public int LargestTextBaseline = 0;
}
}