using System; using System.Collections; using System.Drawing; #if AdvTree namespace DevComponents.Tree.TextMarkup #elif DOTNETBAR namespace DevComponents.UI.ContentManager #elif SUPERGRID namespace DevComponents.SuperGrid.TextMarkup #elif LAYOUT namespace DevComponents.DotNetBar.Layout.TextMarkup #endif { /// /// Represents the serial content layout manager that arranges content blocks in series next to each other. /// public class SerialContentLayoutManager:IContentLayout { #region Events /// /// Occurs when X, Y position of next block is calcualted. /// public event LayoutManagerPositionEventHandler NextPosition; /// /// Occurs before new block is layed out. /// public event LayoutManagerLayoutEventHandler BeforeNewBlockLayout; #endregion #region Private Variables private int m_BlockSpacing=0; private bool m_FitContainerOversize=false; private bool m_FitContainer = false; private bool m_VerticalFitContainerWidth = false; private bool m_HorizontalFitContainerHeight = false; private eContentOrientation m_ContentOrientation=eContentOrientation.Horizontal; private eContentAlignment m_ContentAlignment=eContentAlignment.Left; private eContentVerticalAlignment m_ContentVerticalAlignment=eContentVerticalAlignment.Middle; private eContentVerticalAlignment m_BlockLineAlignment = eContentVerticalAlignment.Middle; private bool m_EvenHeight=false; private bool m_MultiLine=false; private bool m_RightToLeft = false; private bool m_OversizeDistribute = false; #endregion /// /// Creates new instance of the class. /// public SerialContentLayoutManager() { } #region IContentLayout Members /// /// Performs layout of the content block. /// /// Container bounds to layout content blocks in. /// Content blocks to layout. /// Block layout manager that resizes the content blocks. /// The bounds of the content blocks within the container bounds. public virtual Rectangle Layout(Rectangle containerBounds, IBlock[] contentBlocks, BlockLayoutManager blockLayout) { Rectangle blocksBounds=Rectangle.Empty; Point position=containerBounds.Location; ArrayList lines=new ArrayList(); lines.Add(new BlockLineInfo()); BlockLineInfo currentLine=lines[0] as BlockLineInfo; bool switchToNewLine = false; bool canStartOnNewLine = true; int visibleIndex = 0; bool callLayout = true; if (_EqualItemSize) { Size largestItemSize = Size.Empty; foreach (IBlock block in contentBlocks) { if (!block.Visible) { block.Bounds = Rectangle.Empty; continue; } if (BeforeNewBlockLayout != null) { LayoutManagerLayoutEventArgs e = new LayoutManagerLayoutEventArgs(block, position, visibleIndex); BeforeNewBlockLayout(this, e); position = e.CurrentPosition; if (e.CancelLayout) continue; } visibleIndex++; Size availableSize = containerBounds.Size; blockLayout.Layout(block, availableSize); if (block.Bounds.Width > largestItemSize.Width) largestItemSize.Width = block.Bounds.Width; if (block.Bounds.Height > largestItemSize.Height) largestItemSize.Height = block.Bounds.Height; } foreach (IBlock block in contentBlocks) { if (!block.Visible) continue; block.Bounds=new Rectangle(block.Bounds.Location, largestItemSize); } callLayout = false; } foreach(IBlock block in contentBlocks) { if(!block.Visible) { block.Bounds = Rectangle.Empty; continue; } if (BeforeNewBlockLayout != null) { LayoutManagerLayoutEventArgs e = new LayoutManagerLayoutEventArgs(block, position, visibleIndex); BeforeNewBlockLayout(this, e); position = e.CurrentPosition; if (e.CancelLayout) continue; } visibleIndex++; Size availableSize = containerBounds.Size; bool isBlockElement = false; bool isNewLineTriggger = false; bool isContainer = false; if (block is IBlockExtended) { IBlockExtended ex = block as IBlockExtended; isBlockElement = ex.IsBlockElement; isNewLineTriggger = ex.IsNewLineAfterElement; canStartOnNewLine = ex.CanStartNewLine; isContainer = ex.IsBlockContainer; } else canStartOnNewLine = true; if (!isBlockElement && !isContainer) { if (m_ContentOrientation == eContentOrientation.Horizontal) availableSize.Width = (containerBounds.Right - position.X); else availableSize.Height = (containerBounds.Bottom - position.Y); } // Resize the content block if(callLayout) blockLayout.Layout(block, availableSize); if(m_MultiLine && currentLine.Blocks.Count > 0) { if (m_ContentOrientation == eContentOrientation.Horizontal && (position.X + block.Bounds.Width > containerBounds.Right && canStartOnNewLine || isBlockElement || switchToNewLine)) { position.X=containerBounds.X; position.Y+=(currentLine.LineSize.Height+m_BlockSpacing); currentLine=new BlockLineInfo(); currentLine.Line=lines.Count; lines.Add(currentLine); } else if (m_ContentOrientation == eContentOrientation.Vertical && (position.Y + block.Bounds.Height > containerBounds.Bottom && canStartOnNewLine || isBlockElement || switchToNewLine)) { position.Y=containerBounds.Y; position.X+=(currentLine.LineSize.Width+m_BlockSpacing); currentLine=new BlockLineInfo(); currentLine.Line=lines.Count; lines.Add(currentLine); } } if(m_ContentOrientation==eContentOrientation.Horizontal) { if(block.Bounds.Height>currentLine.LineSize.Height) currentLine.LineSize.Height=block.Bounds.Height; currentLine.LineSize.Width=position.X+block.Bounds.Width-containerBounds.X; } else if(m_ContentOrientation==eContentOrientation.Vertical) { if(block.Bounds.Width>currentLine.LineSize.Width) currentLine.LineSize.Width=block.Bounds.Width; currentLine.LineSize.Height=position.Y+block.Bounds.Height-containerBounds.Y; } currentLine.Blocks.Add(block); if (block.Visible) currentLine.VisibleItemsCount++; Rectangle r = new Rectangle(position, block.Bounds.Size); r.X += block.Margin.Left; r.Y += block.Margin.Top; block.Bounds = r; if (blocksBounds.IsEmpty) { r = block.Bounds; // Make sure that blocks bounds take in account any margin r.X -= block.Margin.Left; r.Y -= block.Margin.Top; r.Width += block.Margin.Horizontal; r.Height += block.Margin.Vertical; blocksBounds = r; } else blocksBounds = Rectangle.Union(blocksBounds, block.Bounds); switchToNewLine = isBlockElement | isNewLineTriggger; position = GetNextPosition(block, position, ref switchToNewLine); } blocksBounds=AlignResizeBlocks(containerBounds, blocksBounds, lines); if (m_RightToLeft) blocksBounds = MirrorContent(containerBounds, blocksBounds, contentBlocks); blocksBounds = blockLayout.FinalizeLayout(containerBounds, blocksBounds, lines); return blocksBounds; } #endregion #region Internals private struct SizeExtended { public int Width; public int Height; public float WidthReduction; public float HeightReduction; public bool UseAbsoluteWidth; } private Rectangle AlignResizeBlocks(Rectangle containerBounds,Rectangle blocksBounds,ArrayList lines) { Rectangle newBounds=Rectangle.Empty; if(containerBounds.IsEmpty || blocksBounds.IsEmpty || ((BlockLineInfo)lines[0]).Blocks.Count==0) return newBounds; //Reverting to EventHeight property check so the buttons on RibbonBar are properly stretched so they consume same height. // Be very careful when changing this code! if (m_ContentAlignment == eContentAlignment.Left && m_ContentVerticalAlignment == eContentVerticalAlignment.Top && !m_FitContainer && !m_FitContainerOversize && !m_EvenHeight && m_BlockLineAlignment == eContentVerticalAlignment.Top && (!(m_ContentOrientation == eContentOrientation.Vertical && (m_VerticalFitContainerWidth || m_EvenHeight)) || m_MultiLine)) return blocksBounds; Point[] offset=new Point[lines.Count]; SizeExtended[] sizeOffset = new SizeExtended[lines.Count]; foreach(BlockLineInfo lineInfo in lines) { if(m_ContentOrientation==eContentOrientation.Horizontal) { if(m_FitContainer && containerBounds.Width>lineInfo.LineSize.Width || m_FitContainerOversize && lineInfo.LineSize.Width>containerBounds.Width) { if (m_OversizeDistribute && containerBounds.Width < lineInfo.LineSize.Width * .75) { sizeOffset[lineInfo.Line].Width = (int)Math.Floor((float)(containerBounds.Width - lineInfo.VisibleItemsCount * m_BlockSpacing) / (float)lineInfo.VisibleItemsCount); sizeOffset[lineInfo.Line].UseAbsoluteWidth = true; } else sizeOffset[lineInfo.Line].Width = ((containerBounds.Width - lineInfo.VisibleItemsCount * m_BlockSpacing) - lineInfo.LineSize.Width) / lineInfo.VisibleItemsCount; sizeOffset[lineInfo.Line].WidthReduction = (float)(containerBounds.Width - lineInfo.VisibleItemsCount * m_BlockSpacing) / (float)lineInfo.LineSize.Width; blocksBounds.Width=containerBounds.Width; } if (m_HorizontalFitContainerHeight && containerBounds.Height > blocksBounds.Height && lines.Count == 1) sizeOffset[lineInfo.Line].Height = (containerBounds.Height - lineInfo.LineSize.Height) / lines.Count; } else { if(m_FitContainer && containerBounds.Height>lineInfo.LineSize.Height || m_FitContainerOversize && lineInfo.LineSize.Height>containerBounds.Height) { if (m_OversizeDistribute && containerBounds.Width < lineInfo.LineSize.Width * .75) { sizeOffset[lineInfo.Line].Height = (int)Math.Floor((float)(containerBounds.Height - lineInfo.VisibleItemsCount * m_BlockSpacing) / (float)lineInfo.VisibleItemsCount); sizeOffset[lineInfo.Line].UseAbsoluteWidth = true; } else sizeOffset[lineInfo.Line].Height = ((containerBounds.Height - lineInfo.VisibleItemsCount * m_BlockSpacing) - lineInfo.LineSize.Height) / lineInfo.VisibleItemsCount; sizeOffset[lineInfo.Line].HeightReduction = (float)(containerBounds.Height - lineInfo.VisibleItemsCount * m_BlockSpacing) / (float)lineInfo.LineSize.Height; blocksBounds.Height=containerBounds.Height; } if (m_VerticalFitContainerWidth && containerBounds.Width > blocksBounds.Width && lines.Count==1) sizeOffset[lineInfo.Line].Width = (containerBounds.Width - lineInfo.LineSize.Width) / lines.Count; } if(m_ContentOrientation==eContentOrientation.Horizontal && !m_FitContainer) { if(containerBounds.Width>blocksBounds.Width && m_FitContainerOversize || !m_FitContainerOversize) { switch(m_ContentAlignment) { case eContentAlignment.Right: if (containerBounds.Width > lineInfo.LineSize.Width) offset[lineInfo.Line].X = containerBounds.Width - lineInfo.LineSize.Width; break; case eContentAlignment.Center: if (containerBounds.Width > lineInfo.LineSize.Width) offset[lineInfo.Line].X = (containerBounds.Width - lineInfo.LineSize.Width) / 2; break; } } } if(m_ContentOrientation==eContentOrientation.Vertical && !m_FitContainer) { if(containerBounds.Height>blocksBounds.Height && m_FitContainerOversize || !m_FitContainerOversize) { switch(m_ContentVerticalAlignment) { case eContentVerticalAlignment.Bottom: if (containerBounds.Height > lineInfo.LineSize.Height) offset[lineInfo.Line].Y = containerBounds.Height - lineInfo.LineSize.Height; break; case eContentVerticalAlignment.Middle: if (containerBounds.Height > lineInfo.LineSize.Height) offset[lineInfo.Line].Y = (containerBounds.Height - lineInfo.LineSize.Height) / 2; break; } } } } if (m_VerticalFitContainerWidth && containerBounds.Width > blocksBounds.Width && m_ContentOrientation==eContentOrientation.Vertical) blocksBounds.Width = containerBounds.Width; else if(m_HorizontalFitContainerHeight && containerBounds.Height>blocksBounds.Height && m_ContentOrientation==eContentOrientation.Horizontal) blocksBounds.Height = containerBounds.Height; if(m_ContentOrientation==eContentOrientation.Horizontal) { foreach(BlockLineInfo lineInfo in lines) { foreach(IBlock block in lineInfo.Blocks) { if(!block.Visible) continue; Rectangle r=block.Bounds; if(m_EvenHeight && lineInfo.LineSize.Height>0) r.Height=lineInfo.LineSize.Height; r.Offset(offset[lineInfo.Line]); if(m_ContentVerticalAlignment==eContentVerticalAlignment.Middle) { // Takes care of offset rounding error when both content is vertically centered and blocks in line are centered if (m_BlockLineAlignment == eContentVerticalAlignment.Middle) r.Offset(0,((containerBounds.Height-blocksBounds.Height)+(lineInfo.LineSize.Height-r.Height))/2); else r.Offset(0,(containerBounds.Height-blocksBounds.Height)/2); // Line alignment of the block if (m_BlockLineAlignment == eContentVerticalAlignment.Bottom) r.Offset(0, lineInfo.LineSize.Height - r.Height); } else if(m_ContentVerticalAlignment==eContentVerticalAlignment.Bottom) r.Offset(0,containerBounds.Height-blocksBounds.Height); // To avoid rounding offset errors when dividing this is split see upper part if(m_ContentVerticalAlignment!=eContentVerticalAlignment.Middle) { // Line alignment of the block if (m_BlockLineAlignment == eContentVerticalAlignment.Middle) r.Offset(0, (lineInfo.LineSize.Height - r.Height) / 2); else if (m_BlockLineAlignment == eContentVerticalAlignment.Bottom) r.Offset(0, lineInfo.LineSize.Height - r.Height); } if(sizeOffset[lineInfo.Line].Width!=0) { if (m_OversizeDistribute) { int nw = sizeOffset[lineInfo.Line].UseAbsoluteWidth ? sizeOffset[lineInfo.Line].Width : (int)Math.Floor(r.Width * sizeOffset[lineInfo.Line].WidthReduction); offset[lineInfo.Line].X += nw - r.Width; r.Width = nw; } else { r.Width += sizeOffset[lineInfo.Line].Width; offset[lineInfo.Line].X += sizeOffset[lineInfo.Line].Width; } } r.Height+=sizeOffset[lineInfo.Line].Height; block.Bounds=r; if(newBounds.IsEmpty) newBounds=block.Bounds; else newBounds=Rectangle.Union(newBounds,block.Bounds); } // Adjust for left-over size adjustment for odd difference between container width and the total block width if (!m_OversizeDistribute && sizeOffset[lineInfo.Line].Width != 0 && containerBounds.Width - (lineInfo.LineSize.Width + sizeOffset[lineInfo.Line].Width * lineInfo.Blocks.Count) != 0) { Rectangle r=((IBlock)lineInfo.Blocks[lineInfo.Blocks.Count-1]).Bounds; r.Width+=containerBounds.Width-(lineInfo.LineSize.Width+sizeOffset[lineInfo.Line].Width*lineInfo.Blocks.Count); ((IBlock)lineInfo.Blocks[lineInfo.Blocks.Count-1]).Bounds=r; } if (!m_FitContainer && m_ContentAlignment != eContentAlignment.Left && _ReserveLeftSpace) { // For block elements they should consume the space available to the left otherwise // the parent is not aware that the space has been consumed newBounds.Width += (newBounds.X - containerBounds.X); newBounds.X = containerBounds.X; } } } else { foreach(BlockLineInfo lineInfo in lines) { foreach(IBlock block in lineInfo.Blocks) { if(!block.Visible) continue; Rectangle r=block.Bounds; if(m_EvenHeight && lineInfo.LineSize.Width>0) r.Width=lineInfo.LineSize.Width - block.Margin.Horizontal; r.Offset(offset[lineInfo.Line]); if(m_ContentAlignment==eContentAlignment.Center) r.Offset(((containerBounds.Width-blocksBounds.Width)+(lineInfo.LineSize.Width-r.Width))/2,0); //r.Offset((containerBounds.Width-blocksBounds.Width)/2+(lineInfo.LineSize.Width-r.Width)/2,0); else if(m_ContentAlignment==eContentAlignment.Right) r.Offset((containerBounds.Width-blocksBounds.Width)+lineInfo.LineSize.Width-r.Width,0); r.Width+=sizeOffset[lineInfo.Line].Width; if(sizeOffset[lineInfo.Line].Height!=0) { if (m_OversizeDistribute) { int nw = sizeOffset[lineInfo.Line].UseAbsoluteWidth ? sizeOffset[lineInfo.Line].Height : (int)Math.Floor(r.Height * sizeOffset[lineInfo.Line].HeightReduction); offset[lineInfo.Line].Y += nw - r.Height; r.Height = nw; } else { r.Height += sizeOffset[lineInfo.Line].Height; offset[lineInfo.Line].Y += sizeOffset[lineInfo.Line].Height; } } block.Bounds=r; if (newBounds.IsEmpty) { r.Y -= block.Margin.Top; // Account for any margin set on first element r.X -= block.Margin.Left; r.Width += block.Margin.Horizontal; r.Height += block.Margin.Vertical; newBounds = r; } else newBounds = Rectangle.Union(newBounds, block.Bounds); } if (!m_OversizeDistribute && sizeOffset[lineInfo.Line].Height != 0 && containerBounds.Height - (lineInfo.LineSize.Height + sizeOffset[lineInfo.Line].Height * lineInfo.Blocks.Count) != 0) { Rectangle r=((IBlock)lineInfo.Blocks[lineInfo.Blocks.Count-1]).Bounds; r.Height+=containerBounds.Height-(lineInfo.LineSize.Height+sizeOffset[lineInfo.Line].Height*lineInfo.Blocks.Count); ((IBlock)lineInfo.Blocks[lineInfo.Blocks.Count-1]).Bounds=r; } } } return newBounds; } private Point GetNextPosition(IBlock block, Point position, ref bool switchToNewLine) { if (NextPosition != null) { LayoutManagerPositionEventArgs e = new LayoutManagerPositionEventArgs(); e.Block = block; e.CurrentPosition = position; e.SwitchToNewLine = switchToNewLine; NextPosition(this, e); switchToNewLine = e.SwitchToNewLine; if (e.Cancel) return e.NextPosition; } if (m_ContentOrientation == eContentOrientation.Horizontal) position.X += block.Bounds.Width + m_BlockSpacing + block.Margin.Horizontal; else position.Y += block.Bounds.Height + m_BlockSpacing + block.Margin.Vertical; return position; } internal class BlockLineInfo { public BlockLineInfo() {} public ArrayList Blocks=new ArrayList(); public Size LineSize=Size.Empty; public int Line=0; public int VisibleItemsCount = 0; } private Rectangle MirrorContent(Rectangle containerBounds, Rectangle blockBounds, IBlock[] contentBlocks) { int xOffset = (blockBounds.X - containerBounds.X); if (blockBounds.Width < containerBounds.Width) blockBounds.X = containerBounds.Right - ((blockBounds.X - containerBounds.X) + blockBounds.Width); else if (blockBounds.Width > containerBounds.Width) containerBounds.Width = blockBounds.Width; foreach (IBlock block in contentBlocks) { if (!block.Visible) continue; Rectangle r = block.Bounds; block.Bounds = new Rectangle(containerBounds.Right - ((r.X - containerBounds.X) + r.Width), r.Y, r.Width, r.Height); } return blockBounds; } #endregion #region Properties private bool _ReserveLeftSpace = false; /// /// Indicates whether space to the left of center and right aligned blocks is blocked out by stretching the block so it consumes that space. /// public bool ReserveLeftSpace { get { return _ReserveLeftSpace; } set { _ReserveLeftSpace = value; } } /// /// Gets or sets the spacing in pixels between content blocks. Default value is 0. /// public virtual int BlockSpacing { get {return m_BlockSpacing;} set {m_BlockSpacing=value;} } /// /// Gets or sets whether content blocks are forced to fit the container bounds if they /// occupy more space than it is available by container. Default value is false. /// public virtual bool FitContainerOversize { get {return m_FitContainerOversize;} set {m_FitContainerOversize=value;} } /// /// Gets or sets whether content blocks are resized to fit the container bound if they /// occupy less space than it is available by container. Default value is false. /// public virtual bool FitContainer { get {return m_FitContainer;} set {m_FitContainer=value;} } /// /// Gets or sets whether content blocks are resized (Width) to fit container bounds if they /// occupy less space than the actual container width. Applies to the Vertical orientation only. Default value is false. /// public virtual bool VerticalFitContainerWidth { get { return m_VerticalFitContainerWidth; } set { m_VerticalFitContainerWidth = value; } } /// /// Gets or sets whether content blocks are resized (Height) to fit container bounds if they /// occupy less space than the actual container height. Applies to the Horizontal orientation only. Default value is false. /// public virtual bool HorizontalFitContainerHeight { get { return m_HorizontalFitContainerHeight; } set { m_HorizontalFitContainerHeight = value; } } /// /// Gets or sets the content orientation. Default value is Horizontal. /// public virtual eContentOrientation ContentOrientation { get {return m_ContentOrientation;} set {m_ContentOrientation=value;} } /// /// Gets or sets the content vertical alignment. Default value is Middle. /// public virtual eContentVerticalAlignment ContentVerticalAlignment { get {return m_ContentVerticalAlignment;} set {m_ContentVerticalAlignment=value;} } /// /// Gets or sets the block line vertical alignment. Default value is Middle. /// public virtual eContentVerticalAlignment BlockLineAlignment { get { return m_BlockLineAlignment; } set { m_BlockLineAlignment = value; } } /// /// Gets or sets the content horizontal alignment. Default value is Left. /// public virtual eContentAlignment ContentAlignment { get {return m_ContentAlignment;} set {m_ContentAlignment=value;} } /// /// Gets or sets whether all content blocks are resized so they have same height which is height of the tallest content block. Default value is false. /// public virtual bool EvenHeight { get {return m_EvenHeight;} set {m_EvenHeight=value;} } /// /// Gets or sets whether oversized blocks are resized based on the percentage reduction instead of based on equal pixel distribution. Default value is false. /// public virtual bool OversizeDistribute { get { return m_OversizeDistribute; } set { m_OversizeDistribute = value; } } /// /// Gets or sets whether content is wrapped into new line if it exceeds the width of the container. /// public bool MultiLine { get {return m_MultiLine;} set {m_MultiLine=value;} } /// /// Gets or sets whether layout is right-to-left. /// public bool RightToLeft { get { return m_RightToLeft; } set { m_RightToLeft = value; } } private bool _EqualItemSize = false; /// /// Gets or sets whether all items are equaly sized based on the size of the largest item in the list. /// public bool EqualItemSize { get { return _EqualItemSize; } set { _EqualItemSize = value; } } #endregion } /// /// Represents event arguments for SerialContentLayoutManager.NextPosition event. /// public class LayoutManagerPositionEventArgs : EventArgs { /// /// Gets or sets the block that is layed out. /// public IBlock Block = null; /// /// Gets or sets the current block position. /// public Point CurrentPosition = Point.Empty; /// /// Gets or sets the calculated next block position. /// public Point NextPosition = Point.Empty; /// /// Cancels default position calculation. /// public bool Cancel = false; public bool SwitchToNewLine; } /// /// Represents event arguments for the SerialContentLayoutManager layout events. /// public class LayoutManagerLayoutEventArgs : EventArgs { /// /// Gets or sets the reference block object. /// public IBlock Block = null; /// /// Gets or sets the position block will assume. /// public Point CurrentPosition = Point.Empty; /// /// Cancel the layout of the block, applies only to BeforeXXX layout event. /// public bool CancelLayout = false; /// /// Gets or sets the visibility index of the block. /// public int BlockVisibleIndex = 0; /// /// Creates new instance of the class and initializes it with default values. /// public LayoutManagerLayoutEventArgs(IBlock block, Point currentPosition, int visibleIndex) { this.Block = block; this.CurrentPosition = currentPosition; this.BlockVisibleIndex = visibleIndex; } } /// /// Delegate for SerialContentLayoutManager.NextPosition event. /// public delegate void LayoutManagerPositionEventHandler(object sender, LayoutManagerPositionEventArgs e); /// /// Delegate for the SerialContentLayoutManager layout events. /// public delegate void LayoutManagerLayoutEventHandler(object sender, LayoutManagerLayoutEventArgs e); }