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 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 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 strips, Rectangle clientBounds) { int totalFixedSize = 0; List percenageStrips = new List(); 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 SliceItemsInStrips(LayoutGroup group, Rectangle clientBounds, LayoutContext context) { LayoutItemCollection items = group.Items; List strips = new List(); 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 { /// /// Collection of items inside of strip. /// public List Items = new List(); /// /// Total width of all items with fixed width in the strip. /// public int TotalFixedWidth = 0; /// /// True if this is left most strip in group which spans whole group height. /// public bool IsLeftSpanStrip = false; /// /// True if this is right most strip in group which spans whole group height. /// public bool IsRightSpanStrip = false; /// /// Strip height determined as maximum height of items in it. /// public int StripHeightAbsolute = 0; /// /// Minimum strip height according to the larges MinSize of contained items. /// public int MinStripHeightAbsolute = 0; /// /// Maximum percentage strip height for all items in it. /// public int StripHeightPercentage = 0; /// /// The largest text-baseline for all items in strip. /// public int LargestTextBaseline = 0; } }