372 lines
17 KiB
C#
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;
|
|
}
|
|
}
|