/// <summary> /// Handles the Measure pass during Layout /// </summary> /// <param name="availableSize">Available size</param> /// <returns>Total Size required to accommodate all the Children</returns> protected override Size MeasureOverride(Size availableSize) { // Compositor will be null the very first time if (_compositor == null) { InitializeComposition(); } // Clear any previously uninitialized items _uninitializedFluidItems.Clear(); var availableItemSize = new Size(Double.PositiveInfinity, Double.PositiveInfinity); // Iterate through all the UIElements in the Children collection foreach (var child in Children.Where(c => c != null)) { // Ask the child how much size it needs child.Measure(availableItemSize); // Check if the child is already added to the fluidElements collection if (FluidItems.Contains(child)) continue; // If the FluidItems collection does not contain this child it means it is newly // added to the FluidWrapPanel and is not initialized yet // Add the child to the fluidElements collection FluidItems.Add(child); // Add the child to the UninitializedFluidItems _uninitializedFluidItems.Add(child); // Get the visual of the child var visual = ElementCompositionPreview.GetElementVisual(child); visual.ImplicitAnimations = _implicitAnimationCollection; visual.CenterPoint = new Vector3((float)(child.DesiredSize.Width / 2), (float)(child.DesiredSize.Height / 2), 0); visual.Offset = new Vector3((float)-child.DesiredSize.Width, (float)-child.DesiredSize.Height, 0); _fluidVisuals[child] = visual; } // Unit size of a cell var cellSize = new Size(ItemWidth, ItemHeight); if ((availableSize.Width < 0.0d) || (availableSize.Width.IsZero()) || (availableSize.Height < 0.0d) || (availableSize.Height.IsZero()) || !FluidItems.Any()) { return cellSize; } // Calculate how many unit cells can fit in the given width (or height) when the // Orientation is Horizontal (or Vertical) _cellsPerLine = CalculateCellsPerLine(availableSize, cellSize, Orientation); // Convert the children's dimensions from Size to BitSize var childData = FluidItems.Select(child => new BitSize { Width = Math.Max(1, (int)Math.Floor((child.DesiredSize.Width / cellSize.Width) + 0.5)), Height = Math.Max(1, (int)Math.Floor((child.DesiredSize.Height / cellSize.Height) + 0.5)) }).ToList(); // If all the children have the same size as the cellSize then use optimized code // when a child is being dragged _isOptimized = !childData.Any(c => (c.Width != 1) || (c.Height != 1)); int matrixWidth; int matrixHeight; if (Orientation == Orientation.Horizontal) { // If the maximum width required by a child is more than the calculated cellsPerLine, then // the matrix width should be the maximum width of that child matrixWidth = Math.Max(childData.Max(s => s.Width), _cellsPerLine); // For purpose of calculating the true size of the panel, the height of the matrix must // be set to the cumulative height of all the children matrixHeight = childData.Sum(s => s.Height); } else { // For purpose of calculating the true size of the panel, the width of the matrix must // be set to the cumulative width of all the children matrixWidth = childData.Sum(s => s.Width); // If the maximum height required by a child is more than the calculated cellsPerLine, then // the matrix height should be the maximum height of that child matrixHeight = Math.Max(childData.Max(s => s.Height), _cellsPerLine); } // Create FluidBitMatrix to calculate the size required by the panel var matrix = new FluidBitMatrix(matrixHeight, matrixWidth, Orientation); var startIndex = 0L; foreach (var child in childData) { var width = child.Width; var height = child.Height; MatrixCell cell; if (matrix.TryFindRegion(startIndex, width, height, out cell)) { matrix.SetRegion(cell, width, height); } else { // If code reached here, it means that the child is too big to be accommodated // in the matrix. Normally this should not occur! throw new InvalidOperationException("Measure Pass: Unable to accommodate child in the panel!"); } if (!OptimizeChildPlacement) { // Update the startIndex so that the next child occupies a location which has // the same (or greater) row and/or column as this child startIndex = (Orientation == Orientation.Horizontal) ? cell.Row : cell.Col; } } // Calculate the true size of the matrix var matrixSize = matrix.GetFilledMatrixDimensions(); // Calculate the size required by the panel return new Size(matrixSize.Width * cellSize.Width, matrixSize.Height * cellSize.Height); }
/// <summary> /// Handles the Arrange pass during Layout /// </summary> /// <param name="finalSize">Final Size of the control</param> /// <returns>Total size occupied by all the Children</returns> protected override Size ArrangeOverride(Size finalSize) { var cellSize = new Size(ItemWidth, ItemHeight); if ((finalSize.Width < 0.0d) || (finalSize.Width.IsZero()) || (finalSize.Height < 0.0d) || (finalSize.Height.IsZero())) { finalSize = cellSize; } // Final size of the FluidWrapPanel _panelSize = finalSize; if (!FluidItems.Any()) { return finalSize; } // Create the animation for the uinitialized children var offsetAnimation = _compositor.CreateVector3KeyFrameAnimation(); offsetAnimation.Duration = InitializationAnimationDuration; // Calculate how many unit cells can fit in the given width (or height) when the // Orientation is Horizontal (or Vertical) _cellsPerLine = CalculateCellsPerLine(finalSize, cellSize, Orientation); // Convert the children's dimensions from Size to BitSize var childData = FluidItems.ToDictionary(child => child, child => new BitSize { Width = Math.Max(1, (int)Math.Floor((child.DesiredSize.Width / cellSize.Width) + 0.5)), Height = Math.Max(1, (int)Math.Floor((child.DesiredSize.Height / cellSize.Height) + 0.5)) }); // If all the children have the same size as the cellSize then use optimized code // when a child is being dragged _isOptimized = !childData.Values.Any(c => (c.Width != 1) || (c.Height != 1)); // Calculate matrix dimensions int matrixWidth; int matrixHeight; if (Orientation == Orientation.Horizontal) { // If the maximum width required by a child is more than the calculated cellsPerLine, then // the matrix width should be the maximum width of that child matrixWidth = Math.Max(childData.Values.Max(s => s.Width), _cellsPerLine); // For purpose of calculating the true size of the panel, the height of the matrix must // be set to the cumulative height of all the children matrixHeight = childData.Values.Sum(s => s.Height); } else { // For purpose of calculating the true size of the panel, the width of the matrix must // be set to the cumulative width of all the children matrixWidth = childData.Values.Sum(s => s.Width); // If the maximum height required by a child is more than the calculated cellsPerLine, then // the matrix height should be the maximum height of that child matrixHeight = Math.Max(childData.Values.Max(s => s.Height), _cellsPerLine); } // Create FluidBitMatrix to calculate the size required by the panel var matrix = new FluidBitMatrix(matrixHeight, matrixWidth, Orientation); var startIndex = 0L; _fluidBits.Clear(); foreach (var child in childData) { var width = child.Value.Width; var height = child.Value.Height; MatrixCell cell; if (matrix.TryFindRegion(startIndex, width, height, out cell)) { // Set the bits matrix.SetRegion(cell, width, height); // Arrange the child child.Key.Arrange(new Rect(0, 0, child.Key.DesiredSize.Width, child.Key.DesiredSize.Height)); // Convert MatrixCell location to actual location var pos = new Vector3((float)(cell.Col * cellSize.Width), (float)(cell.Row * cellSize.Height), 0); // Get the Bit Information for this child BitInfo info; info.Row = cell.Row; info.Col = cell.Col; info.Width = width; info.Height = height; _fluidBits.Add(child.Key, info); // If this child is not being dragged, then set the Offset of its visual if (!ReferenceEquals(child.Key, _dragElement)) { var visual = _fluidVisuals[child.Key]; // Is the child unitialized? if (_uninitializedFluidItems.Contains(child.Key)) { // Use explicit animation to position the uninitialized child to the new location // because implicit property animations do not run the first time // a Visual shows up on screen offsetAnimation.InsertKeyFrame(1f, pos); visual.StartAnimation(() => visual.Offset, offsetAnimation); } else { // Child has been already initialized. Set the Offset directly. The ImplicitAnimations // of the Child's visual will take care of animating it to the new location _fluidVisuals[child.Key].Offset = pos; } } } else { // If code reached here, it means that the child is too big to be accommodated // in the matrix. Normally this should not occur! throw new InvalidOperationException("Arrange Pass: Unable to accommodate child in the panel!"); } if (!OptimizeChildPlacement) { // Update the startIndex so that the next child occupies a location which has // the same (or greater) row and/or column as this child startIndex = (Orientation == Orientation.Horizontal) ? cell.Row : cell.Col; } } // All the uninitialized fluid items have been initialized, so clear the list _uninitializedFluidItems.Clear(); // Calculate the maximum cells along the width and height of the FluidWrapPanel _maxCellRows = (int)Math.Max(1, Math.Floor(_panelSize.Height / ItemHeight)); _maxCellCols = (int)Math.Max(1, Math.Floor(_panelSize.Width / ItemWidth)); return finalSize; }
/// <summary> /// Handles the situation when the user drags a dragElement which does not have /// unit size dimension. It checks if the dragElement can fit in the new location and /// the rest of the children can be rearranged successfully in the remaining space. /// </summary> /// <param name="position">Position of the pointer within the dragElement</param> /// <param name="positionInParent">Position of the pointer w.r.t. the FluidWrapPanel</param> /// <returns>True if successful otherwise False</returns> private bool TryFluidDrag(Point position, Point positionInParent) { // Get the index of the dragElement var dragCellIndex = FluidItems.IndexOf(_dragElement); // Convert the current location to MatrixCell which indicates the top left cell of the dragElement var currentCell = GetCellFromPoint(_fluidBits[_dragElement], position, positionInParent); // Check if the item being dragged can fit in the new cell location if (!IsValidCellPosition(_dragElement, currentCell)) return false; // Get the list of cells vacated when the dragElement moves to the new cell location var vacatedCells = GetVacatedCells(_dragElement, currentCell); // If none of the cells are vacated, no need to proceed further if (vacatedCells.Count == 0) { _lastExchangedElement = null; return false; } // Get the list of children overlapped by the var overlappedChildren = GetOverlappedChildren(_dragElement, currentCell); var dragInfo = _fluidBits[_dragElement]; // If there is only one overlapped child and its dimension matches the // dimensions of the dragElement, then exchange their indices if (overlappedChildren.Count == 1) { var element = overlappedChildren[0]; var info = _fluidBits[element]; var dragCellCount = info.Width * info.Height; if ((info.Width == dragInfo.Width) && (info.Height == dragInfo.Height)) { // If user moves the dragElement back to the lastExchangedElement's position, then it can // be exchanged again only if the dragElement has vacated all the cells occupied by it in // the previous location. if (ReferenceEquals(element, _lastExchangedElement) && (vacatedCells.Count != dragCellCount)) { return false; } // Exchange the dragElement and the overlapped element _lastExchangedElement = element; var index = FluidItems.IndexOf(element); // To prevent an IndexOutOfRangeException during the exchange // Remove the item with higher index first followed by the lower index item and then // Insert the items in the lower index first and then in the higher index if (index > dragCellIndex) { FluidItems.RemoveAt(index); FluidItems.RemoveAt(dragCellIndex); FluidItems.Insert(dragCellIndex, element); FluidItems.Insert(index, _dragElement); } else { FluidItems.RemoveAt(dragCellIndex); FluidItems.RemoveAt(index); FluidItems.Insert(index, _dragElement); FluidItems.Insert(dragCellIndex, element); } return true; } } // Since there are multiple overlapped children, we need to rearrange all the children // Create a temporary matrix to check if all the children are placed successfully // when the dragElement is moved to the new cell location var tempMatrix = new FluidBitMatrix(_maxCellRows, _maxCellCols, Orientation); // First set the cells corresponding to dragElement's cells in new location tempMatrix.SetRegion(currentCell, dragInfo.Width, dragInfo.Height); // Try to fit the remaining items var startIndex = 0L; var tempFluidBits = new Dictionary<UIElement, BitInfo>(); // Add the new bit information for dragElement dragInfo.Row = currentCell.Row; dragInfo.Col = currentCell.Col; tempFluidBits[_dragElement] = dragInfo; // Try placing the rest of the children in the matrix foreach (var item in _fluidBits.Where(t => !ReferenceEquals(t.Key, _dragElement))) { var width = item.Value.Width; var height = item.Value.Height; MatrixCell cell; if (tempMatrix.TryFindRegion(startIndex, width, height, out cell)) { // Set the bits tempMatrix.SetRegion(cell, width, height); // Capture the bit information BitInfo newinfo; newinfo.Row = cell.Row; newinfo.Col = cell.Col; newinfo.Width = width; newinfo.Height = height; tempFluidBits.Add(item.Key, newinfo); } else { // No suitable location was found to fit the current item. So the children cannot be // successfully placed after moving dragElement to new cell location. So dragElement // will not be moved. return false; } // Update the startIndex so that the next child occupies a location the same (or greater) // row and/or column as this child if (!OptimizeChildPlacement) { startIndex = (Orientation == Orientation.Horizontal) ? cell.Row : cell.Col; } } // All the children have been successfully readjusted, so now // Re-Index the children based on the panel's orientation var tempFluidItems = new List<UIElement>(); if (Orientation == Orientation.Horizontal) { for (var row = 0; row < _maxCellRows; row++) { for (var col = 0; col < _maxCellCols; col++) { var item = tempFluidBits.Where(t => t.Value.Contains(row, col)) .Select(t => t.Key).FirstOrDefault(); if ((item != null) && (!tempFluidItems.Contains(item))) { tempFluidItems.Add(item); } } } } else { for (var col = 0; col < _maxCellCols; col++) { for (var row = 0; row < _maxCellRows; row++) { var item = tempFluidBits.Where(t => t.Value.Contains(row, col)) .Select(t => t.Key).FirstOrDefault(); if ((item != null) && (!tempFluidItems.Contains(item))) { tempFluidItems.Add(item); } } } } // Update the new indices in FluidItems FluidItems.Clear(); foreach (var fluidItem in tempFluidItems) { FluidItems.Add(fluidItem); } // Clean up tempFluidItems.Clear(); tempFluidBits.Clear(); return true; }