|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
#region Using Declarations
using System.Diagnostics;
#if RIBBON_IN_FRAMEWORK
#if RIBBON_IN_FRAMEWORK
namespace System.Windows.Controls.Ribbon.Primitives
#else
namespace Microsoft.Windows.Controls.Ribbon.Primitives
#endif
{
#else
using Microsoft.Windows.Controls.Ribbon;
#endif
#endregion
/// <summary>
/// The default Items panel for the RibbonGalleryCategory class.
/// </summary>
public class RibbonGalleryItemsPanel : Panel
{
#region Private Methods
// It just determines if any ancestor supports StarLayout and is not in StarLayoutPass mode.
private bool IsAutoLayoutPass()
{
RibbonGallery gallery = Gallery;
if (gallery != null)
{
RibbonGalleryCategoriesPanel categoriesPanel = gallery.ItemsHostSite as RibbonGalleryCategoriesPanel;
if (categoriesPanel != null)
{
IContainsStarLayoutManager iContainsStarLayoutManager = (IContainsStarLayoutManager)categoriesPanel;
if (iContainsStarLayoutManager.StarLayoutManager != null)
return !iContainsStarLayoutManager.StarLayoutManager.IsStarLayoutPass;
}
}
return false;
}
private void AddScrollDeltaInfo(double sumOfHeight, int childrenCount)
{
RibbonGalleryCategory category = Category;
if (category != null)
{
// Adding virtual count of items and cumulative height to RGC for the purpose of calcualting
// avg height as scrolling delta in RibbonGalleryCategoriesPanel.
category.averageItemHeightInfo.Count = childrenCount;
category.averageItemHeightInfo.CumulativeHeight = sumOfHeight;
}
}
// Minimum number of Col must be shown is determined by this method
private int GetMinColumnCount()
{
RibbonGalleryCategory category = Category;
if (category != null)
{
return (int)category.MinColumnCount;
}
return 1;
}
// Maximum number of Col must be shown is determined by this method
private int GetMaxColumnCount()
{
RibbonGalleryCategory category = Category;
if (category != null)
{
return (int)category.MaxColumnCount;
}
return int.MaxValue;
}
// Gets the MaxColumnWidth if the shared scope is Gallery
// then MaxColumnWidth is used which is defined on Gallery to lay out all Items as Uniform width columns in scope of Gallery
// otherwise they layout as Uniform width column within the scope of current Category only.
private double GetMaxColumnWidth()
{
RibbonGallery gallery = Gallery;
if (gallery != null && SharedColumnSizeScopeAtGalleryLevel)
{
return gallery.MaxColumnWidth;
}
return _maxColumnWidth;
}
#if IN_RIBBON_GALLERY
// MaxItemHeight is the maximum height of an Item among all the Items of all categories in the gallery this panel is part of.
// MaximumItemHeight is used by InRibbonGallery to find the Item Box for uniformity.
private double GetMaxItemHeight()
{
RibbonGallery gallery = Gallery;
if (gallery != null)
{
return gallery.MaxItemHeight;
}
return _maxItemHeight;
}
#endif
// Sets the MaxColumnWidth on Gallery if the shared scope is Gallery
private void SetMaxColumnWidth(double value)
{
MaxColumnWidth = value;
RibbonGallery gallery = Gallery;
if (gallery != null)
{
if (SharedColumnSizeScopeAtGalleryLevel)
{
if (gallery.MaxColumnWidth < value || !gallery.IsMaxColumnWidthValid)
{
gallery.MaxColumnWidth = value;
gallery.IsMaxColumnWidthValid = true;
}
}
}
}
#if IN_RIBBON_GALLERY
private void SetMaxColumnWidthAndHeight(double maxWidth, double maxHeight)
{
SetMaxColumnWidth(maxWidth);
RibbonGallery gallery = Gallery;
if (gallery != null)
{
if (gallery.MaxItemHeight < maxHeight)
{
gallery.MaxItemHeight = maxHeight;
}
}
}
#endif
private double GetArrangeWidth()
{
RibbonGallery gallery = Gallery;
if (gallery != null && SharedColumnSizeScopeAtGalleryLevel)
{
return gallery.ArrangeWidth;
}
return _arrangeWidth;
}
// Sets the ArrangeWidth on Gallery if the shared scope is Gallery
private void SetArrangeWidth(double value)
{
_arrangeWidth = value;
RibbonGallery gallery = Gallery;
if (gallery != null && SharedColumnSizeScopeAtGalleryLevel)
{
gallery.ArrangeWidth = value;
gallery.IsArrangeWidthValid = true;
}
}
#endregion Private Methods
#region Protected Overrides
// There are three different scenarios:
// 1. In InRibbonGalleryMode where the gallery is shown within Ribbon via InRibbonGallery and Measure becomes responsibility
// of InRibbonGalleryModeMeasureOverride.
// 2. Popup mode : it's the common and RealMeasureOverride which is the common case of RibbonGallery hosted normally in Popup
// Weather it belongs to InRibbonGallery or any other control containing Gallery like RibbonComboBox/RibbonMenuButton etc.
// 3. Popup mode also utilizes AutoPass to be understood by hosting Control like RibbonComboBox/RibbonMenuButton etc.
protected override Size MeasureOverride(Size availableSize)
{
RibbonGallery gallery = Gallery;
#if IN_RIBBON_GALLERY
if (gallery != null && gallery.IsInInRibbonGalleryMode())
{
return InRibbonGalleryModeMeasureOverride(availableSize);
}
else
{
#endif
return RealMeasureOverride(availableSize);
#if IN_RIBBON_GALLERY
}
#endif
}
#if IN_RIBBON_GALLERY
// InRibbonGalleryMode where the gallery is shown within Ribbon via InRibbonGallery and Measure becomes responsibility
// of InRibbonGalleryModeMeasureOverride.
private Size InRibbonGalleryModeMeasureOverride(Size availableSize)
{
UIElementCollection children = InternalChildren;
Size panelSize = new Size();
Size childConstraint = new Size(double.PositiveInfinity, double.PositiveInfinity);
double maxItemHeight = 0;
double maxColumnWidth = 0;
double sumItemHeight = 0;
int columnCount = 0;
int childrenCount = children.Count;
int minColumnCount = GetMinColumnCount();
int maxColumnCount = GetMaxColumnCount();
Debug.Assert(maxColumnCount >= minColumnCount);
// Determine the maximum column width so that all items
// can be hosted in equispaced columns. row height is auto
// and depends on the maximum height of the items in that row
for (int i = 0; i < childrenCount; i++)
{
UIElement child = children[i] as UIElement;
// It has been already measure once directly in RibbonCategoriesPanel and hence will get short circuit already.
// The call still needed as to Measure in case if the Parent panel is being changed from
// RibbonGalleryCatgoriesPanel to something else.
child.Measure(childConstraint);
Size childSize = child.DesiredSize;
maxColumnWidth = Math.Max(maxColumnWidth, childSize.Width);
maxItemHeight = Math.Max(maxItemHeight, childSize.Height);
sumItemHeight += childSize.Height;
}
// It has been already calculated once directly in RibbonCategoriesPanel and hence will get short circuit already.
// The call still needs to be made in case if the Parent panel is being changed from RibbonGalleryCatgoriesPanel
// to something else.
SetMaxColumnWidthAndHeight(maxColumnWidth, maxItemHeight);
// Gets the final MaxColumnWidth for this panel.
maxColumnWidth = GetMaxColumnWidth();
maxItemHeight = GetMaxItemHeight();
AddScrollDeltaInfo(sumItemHeight, childrenCount);
if (!double.IsInfinity(availableSize.Width) && maxColumnWidth != 0)
{
columnCount = (int)(availableSize.Width / maxColumnWidth);
}
else
{
columnCount = childrenCount;
}
// Initialize Width and Height, specially in case of height it's more off leaving the empty space in panel where Items in other categories
// are to be rendered.
panelSize.Width = columnCount * maxColumnWidth;
RibbonGalleryCategory category = Category;
if (category != null)
{
panelSize.Height = category.RowOffset * maxItemHeight;
if (columnCount != 0)
{
// There could be space left in the last row of the previous category to render some of the items in this category.
Debug.Assert(category.ColumnOffset < columnCount);
int spotsLeftInPreviousRow = category.ColumnOffset == 0 ? 0 : columnCount - category.ColumnOffset;
if (childrenCount > spotsLeftInPreviousRow)
{
int remainingChildren = childrenCount - spotsLeftInPreviousRow;
category.ColumnEndOffSet = remainingChildren % columnCount;
category.RowCount = remainingChildren / columnCount; // Initialize RowCount as the number of full rows.
// Increment RowCount if we filled the previous row.
if (spotsLeftInPreviousRow > 0)
category.RowCount++;
// Increment RowCount if we started a new row.
if (category.ColumnEndOffSet > 0)
category.RowCount++;
}
else
{
// All children can fit in the previous row.
category.ColumnEndOffSet = (category.ColumnOffset + childrenCount) % columnCount;
category.RowCount = 1;
}
}
else
{
category.ColumnEndOffSet = 0;
category.RowCount = 0;
}
panelSize.Height += category.RowCount * maxItemHeight;
}
return panelSize;
}
#endif
private Size RealMeasureOverride(Size availableSize)
{
// Iterate through all of the children. For each row first measure # of children
// to infinity and gather their Max of DesiredWidths. Also, calculate the
// columnCount. Then space permitting measure as many more
// children that will fit into that row till columnCount is acheived and get
// MaxHeight for that Row. Cumulative height of such Rows gives you DesiredHeight
// for Panel.
// Return desired size for this Panel as the
// new Size(ColumnCount * MaxColumnWidth, cumulative RowHeights).
UIElementCollection children = InternalChildren;
Size panelSize = new Size();
Size childConstraint = new Size(double.PositiveInfinity, double.PositiveInfinity);
double maxRowHeight = 0;
double maxItemHeight = 0;
double sumItemHeight = 0;
int columnCount = 0;
int childrenCount = children.Count;
RibbonGallery parentGallery = Gallery;
double maxColumnWidth = (parentGallery != null && parentGallery.IsMaxColumnWidthValid) ? GetMaxColumnWidth() : 0.0;
int minColumnCount = GetMinColumnCount();
int maxColumnCount = GetMaxColumnCount();
Debug.Assert(maxColumnCount >= minColumnCount);
// Determine the maximum column width so that all items
// can be hosted in equispaced columns. row height is auto
// and depends on the maximum height of the items in that row
for (int i = 0; i < childrenCount; i++)
{
UIElement child = children[i] as UIElement;
child.Measure(childConstraint);
Size childSize = child.DesiredSize;
maxColumnWidth = Math.Max(maxColumnWidth, childSize.Width);
maxItemHeight = Math.Max(maxItemHeight, childSize.Height);
sumItemHeight += childSize.Height;
}
// if none of the children has substantial width, panelsize would be equivalent to zero.
if (maxColumnWidth == 0.0)
{
return panelSize;
}
// Updates the MaxColumnWidth of this Category as well as the of the parent Gallery if suffices all conditions.
SetMaxColumnWidth(maxColumnWidth);
// Gets the final MaxColumnWidth for this panel.
maxColumnWidth = GetMaxColumnWidth();
AddScrollDeltaInfo(sumItemHeight, childrenCount);
if (!IsAutoLayoutPass())
{
if (!double.IsInfinity(availableSize.Width))
{
columnCount = Math.Min(Math.Max(minColumnCount, Math.Min((int)(availableSize.Width / maxColumnWidth), childrenCount)), maxColumnCount);
RibbonGalleryCategory category = Category;
if (parentGallery != null && category != null)
{
if (SharedColumnSizeScopeAtGalleryLevel && parentGallery.ColumnsStretchToFill)
{
// Since Gallery is a SharedColumnScope, store ArrangeWidth to be shared by the entire Gallery
double arrangeWidth = GetArrangeWidth();
if (!parentGallery.IsArrangeWidthValid)
{
// Calculate ArrangeWidth such that columnCount no. of columns occupy all of availableSize.Width
columnCount = Math.Min(Math.Max(minColumnCount, Math.Min((int)(availableSize.Width / maxColumnWidth), childrenCount)), maxColumnCount);
arrangeWidth = Math.Max(availableSize.Width / columnCount, maxColumnWidth);
SetArrangeWidth(arrangeWidth);
}
else
{
// Once a valid arrangeWidth has been computed, we use arrangeWidth to determine number of columns.
columnCount = Math.Min(Math.Max(minColumnCount, Math.Min((int)(availableSize.Width / arrangeWidth), childrenCount)), maxColumnCount);
}
}
else if (!SharedColumnSizeScopeAtGalleryLevel && category.ColumnsStretchToFill)
{
// Since category is a sharedColumnScope.
// Calculate and store _arrangeWidth locally for just this category
if (!_isArrangeWidthValid)
{
columnCount = Math.Min(Math.Max(minColumnCount, Math.Min((int)(availableSize.Width / maxColumnWidth), childrenCount)), maxColumnCount);
_arrangeWidth = Math.Max(availableSize.Width / columnCount, maxColumnWidth);
_isArrangeWidthValid = true;
}
else
{
// Once a valid arrangeWidth has been computed, we use arrangeWidth to determine number of columns.
columnCount = Math.Min(Math.Max(minColumnCount, Math.Min((int)(availableSize.Width / _arrangeWidth), childrenCount)), maxColumnCount);
}
}
}
}
else
{
columnCount = Math.Max(minColumnCount, Math.Min(childrenCount, maxColumnCount));
}
// Finds row Items once ColumnWidth is determined to fetch MaxHeight of a particular row.
// Also adds these height to acheive cumulative height which is desired height of the panel.
for (int i = 0; i < childrenCount; i++)
{
UIElement child = children[i] as UIElement;
Size childSize = child.DesiredSize;
maxRowHeight = Math.Max(maxRowHeight, childSize.Height);
if ((i + 1) % columnCount == 0 || i == childrenCount - 1)
{
panelSize.Height += maxRowHeight;
// Save the maxRowHeight so it can be used for PageDown operations
_maxRowHeight = maxRowHeight;
maxRowHeight = 0;
}
}
}
else
{
columnCount = minColumnCount;
panelSize.Height = maxItemHeight;
}
panelSize.Width = columnCount * maxColumnWidth;
return panelSize;
}
// There are two different scenarios:
// 1. In InRibbonGalleryMode where the gallery is shown within Ribbon via InRibbonGallery and Arrange becomes responsibility
// of InRibbonGalleryModeMeasureOverride.
// 2. Popup mode : it's the common and RealArrangeOverride which is the common case of RibbonGallery hosted normally in Popup
// weather it belongs to InRibbonGallery or any other control containing Gallery like RibbonComboBox/RibbonMenuButton etc.
protected override Size ArrangeOverride(Size finalSize)
{
RibbonGallery gallery = Gallery;
#if IN_RIBBON_GALLERY
if (gallery != null && gallery.IsInInRibbonGalleryMode())
{
return InRibbonGalleryModeArrangeOverride(finalSize);
}
else
{
#endif
return RealArrangeOverride(finalSize);
#if IN_RIBBON_GALLERY
}
#endif
}
#if IN_RIBBON_GALLERY
private Size InRibbonGalleryModeArrangeOverride(Size finalSize)
{
// Get final coumn count by finalsizw.width , MaxColumnWidth
// Iterate through children one row at a time.
// Arrange the first row at offset 0 and the next
// row just below that and so on. Besure to arrange
// the children within each row uniformly based on
// the MaxColumnWidth that was computed during the
// Measure pass.
UIElementCollection children = InternalChildren;
double rowStartHeight = 0;
double rowStartWidth = 0;
int finalColumnCount = 0;
int childrenCount = children.Count;
double maxColumnWidth = GetMaxColumnWidth();
double maxItemHeight = GetMaxItemHeight();
//Calculate the available column count based on final space
//keeping the same column width.
if (maxColumnWidth == 0.0)
return finalSize;
if (!double.IsInfinity(finalSize.Width) && maxColumnWidth != 0)
{
finalColumnCount = (int)(finalSize.Width / maxColumnWidth);
}
else
{
finalColumnCount = childrenCount;
}
if (finalColumnCount == 0)
{
return finalSize;
}
RibbonGalleryCategory category = Category;
// Calculate the starting offsets in pixels from actual Row and Coulmn offsets.
if (category != null)
{
rowStartHeight = category.RowOffset * maxItemHeight;
rowStartWidth = category.ColumnOffset * maxColumnWidth;
}
for (int i = 0; i < childrenCount; i++)
{
children[i].Arrange(new Rect(rowStartWidth, rowStartHeight, maxColumnWidth, maxItemHeight));
rowStartWidth += maxColumnWidth;
if ((i + category.ColumnOffset + 1) % finalColumnCount == 0)
{
rowStartHeight += maxItemHeight;
rowStartWidth = 0;
}
}
return finalSize;
}
#endif
private Size RealArrangeOverride(Size finalSize)
{
// Get final coumn count by finalsizw.width , MaxColumnWidth
// Iterate through children one row at a time.
// Arrange the first row at offset 0 and the next
// row just below that and so on. Besure to arrange
// the children within each row uniformly based on
// the MaxColumnWidth that was computed during the
// Measure pass.
UIElementCollection children = InternalChildren;
double rowStartHeight = 0;
double rowStartWidth = 0;
double maxRowHeight = 0.0;
int finalColumnCount = 0;
int rowStartIndex = 0;
int childrenCount = children.Count;
int minColumnCount = GetMinColumnCount();
int maxColumnCount = GetMaxColumnCount();
RibbonGallery parentGallery = Gallery;
RibbonGalleryCategory category = Category;
double arrangeWidth = 0.0;
if (parentGallery != null && category != null)
{
if (SharedColumnSizeScopeAtGalleryLevel && parentGallery.ColumnsStretchToFill)
{
// If sharedScope is Gallery, fetch global ArrangeWidth
arrangeWidth = GetArrangeWidth();
}
else if (category.IsSharedColumnSizeScope && category.ColumnsStretchToFill)
{
// SharedScope is Category, use local arrangeWidth.
arrangeWidth = _arrangeWidth;
}
else
{
// ColumnStretchToFill is false.
arrangeWidth = GetMaxColumnWidth();
}
}
//Calculate the available column count based on final space
//keeping the same column width.
if (arrangeWidth == 0.0)
return finalSize;
finalColumnCount = Math.Max(minColumnCount, Math.Min((int)(finalSize.Width / arrangeWidth), maxColumnCount));
if (finalColumnCount == 0)
{
return finalSize;
}
for (int i = 0; i < childrenCount; i++)
{
maxRowHeight = Math.Max(maxRowHeight, children[i].DesiredSize.Height);
if ((i + 1) % finalColumnCount == 0 || i == childrenCount-1)
{
//Arrange the row
for (int j = rowStartIndex; j <= i; j++)
{
children[j].Arrange(new Rect(rowStartWidth, rowStartHeight, arrangeWidth, maxRowHeight));
rowStartWidth += arrangeWidth;
}
rowStartHeight += maxRowHeight;
maxRowHeight = 0;
rowStartIndex = i + 1;
rowStartWidth = 0;
}
}
return finalSize;
}
#endregion Protected Overrides
#region Data
private RibbonGalleryCategory Category
{
get
{
return (RibbonGalleryCategory)ItemsControl.GetItemsOwner(this);
}
}
private RibbonGallery Gallery
{
get
{
RibbonGalleryCategory category = this.Category;
if (category != null)
{
return category.RibbonGallery;
}
return null;
}
}
// Shared column size scope can be at either the gallery or category level.
// Gallery level is the default.
private bool SharedColumnSizeScopeAtGalleryLevel
{
get
{
RibbonGalleryCategory category = Category;
if (category != null &&
category.IsSharedColumnSizeScope)
{
return false;
}
return true;
}
}
internal double MaxColumnWidth
{
get { return _maxColumnWidth; }
private set
{
if (_maxColumnWidth != value)
{
_maxColumnWidth = value;
_isArrangeWidthValid = false;
}
}
}
internal double MaxRowHeight
{
get { return _maxRowHeight; }
}
// this is local value of maxColumnWidth per category
private double _maxColumnWidth = 0, _arrangeWidth = 0.0;
private double _maxRowHeight = 0;
#if IN_RIBBON_GALLERY
private double _maxItemHeight = 0;
#endif
private bool _isArrangeWidthValid = false;
#endregion
}
}
|