diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/FancyZonesEditor.csproj b/src/modules/fancyzones/editor/FancyZonesEditor/FancyZonesEditor.csproj index e65c26603e..08289521c9 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/FancyZonesEditor.csproj +++ b/src/modules/fancyzones/editor/FancyZonesEditor/FancyZonesEditor.csproj @@ -110,6 +110,8 @@ Designer + + diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/GridData.cs b/src/modules/fancyzones/editor/FancyZonesEditor/GridData.cs new file mode 100644 index 0000000000..621b608fea --- /dev/null +++ b/src/modules/fancyzones/editor/FancyZonesEditor/GridData.cs @@ -0,0 +1,1055 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Windows; +using System.Windows.Controls; +using FancyZonesEditor.Models; + +namespace FancyZonesEditor +{ + public class GridData + { + public class ResizeInfo + { + public ResizeInfo() + { + } + + public int NewPercent { get; set; } + + public int AdjacentPercent { get; private set; } + + public int TotalPercent { get; private set; } + + public int CurrentPercent { get; set; } + + public double CurrentExtent { get; set; } + + public bool IsResizeAllowed + { + get + { + return (NewPercent > 0) && (NewPercent < TotalPercent); + } + } + + public void CalcNewPercent(double delta) + { + double newExtent = CurrentExtent + _adjacentExtent + delta; + NewPercent = (int)((CurrentPercent + AdjacentPercent) * newExtent / (CurrentExtent + _adjacentExtent)); + } + + public void CalcAdjacentZones(int index, int size, List info, Func indexCmpr) + { + int ind = index; + while (ind > 0 && indexCmpr(ind)) + { + ind--; + _adjacentExtent += info[ind].Extent; + AdjacentPercent += info[ind].Percent; + } + + TotalPercent = CurrentPercent + AdjacentPercent + info[index + 1].Percent; + + ind = index + 2; + while (ind < size && indexCmpr(ind)) + { + TotalPercent += info[ind].Percent; + ind++; + } + } + + public void FixAccuracyError(List info, List percents, int index) + { + int total = 0; + for (int i = 0; i < info.Count; i++) + { + total += info[i].Percent; + } + + int diff = total - 10000; + if (diff != 0) + { + TotalPercent -= diff; + + while (index >= info.Count) + { + index--; + } + + info[index].Percent -= diff; + percents[index] -= diff; + } + } + + private double _adjacentExtent; + } + + public GridData(GridLayoutModel model) + { + _model = model; + + int rows = model.Rows; + int cols = model.Columns; + + _rowInfo = new List(rows); + for (int row = 0; row < rows; row++) + { + _rowInfo.Add(new RowColInfo(model.RowPercents[row])); + } + + _colInfo = new List(cols); + for (int col = 0; col < cols; col++) + { + _colInfo.Add(new RowColInfo(model.ColumnPercents[col])); + } + } + + public int ZoneCount + { + get + { + int maxIndex = 0; + for (int row = 0; row < _model.Rows; row++) + { + for (int col = 0; col < _model.Columns; col++) + { + maxIndex = Math.Max(maxIndex, _model.CellChildMap[row, col]); + } + } + + return maxIndex; + } + } + + public Tuple RowColByIndex(int index) + { + int foundRow = -1; + int foundCol = -1; + + for (int row = 0; row < _model.Rows && foundRow == -1; row++) + { + for (int col = 0; col < _model.Columns && foundCol == -1; col++) + { + if (_model.CellChildMap[row, col] == index) + { + foundRow = row; + foundCol = col; + } + } + } + + return new Tuple(foundRow, foundCol); + } + + public int GetIndex(int row, int col) + { + return _model.CellChildMap[row, col]; + } + + public double ColumnTop(int column) + { + return _colInfo[column].Start; + } + + public double ColumnBottom(int column) + { + return _colInfo[column].End; + } + + public double RowStart(int row) + { + return _rowInfo[row].Start; + } + + public double RowEnd(int row) + { + return _rowInfo[row].End; + } + + public void SetIndex(int row, int col, int index) + { + _model.CellChildMap[row, col] = index; + } + + public void SplitColumn(int foundCol, int spliteeIndex, int newChildIndex, double space, double offset, double actualWidth) + { + int rows = _model.Rows; + int cols = _model.Columns + 1; + + int[,] cellChildMap = _model.CellChildMap; + int[,] newCellChildMap = new int[rows, cols]; + + int sourceCol = 0; + for (int col = 0; col < cols; col++) + { + for (int row = 0; row < rows; row++) + { + if (cellChildMap[row, sourceCol] > spliteeIndex || ((col > foundCol) && (cellChildMap[row, sourceCol] == spliteeIndex))) + { + newCellChildMap[row, col] = cellChildMap[row, sourceCol] + 1; + } + else + { + newCellChildMap[row, col] = cellChildMap[row, sourceCol]; + } + } + + if (col != foundCol) + { + sourceCol++; + } + } + + RowColInfo[] split = _colInfo[foundCol].Split(offset, space); + + _colInfo[foundCol] = split[0]; + _colInfo.Insert(foundCol + 1, split[1]); + + _model.ColumnPercents[foundCol] = split[0].Percent; + _model.ColumnPercents.Insert(foundCol + 1, split[1].Percent); + + double newTotalExtent = actualWidth - (space * (cols + 1)); + for (int col = 0; col < cols; col++) + { + if (col != foundCol && col != foundCol + 1) + { + _colInfo[col].RecalculatePercent(newTotalExtent); + } + } + + FixAccuracyError(_colInfo, _model.ColumnPercents); + _model.CellChildMap = newCellChildMap; + _model.Columns++; + } + + public void SplitRow(int foundRow, int spliteeIndex, int newChildIndex, double space, double offset, double actualHeight) + { + int rows = _model.Rows + 1; + int cols = _model.Columns; + + int[,] cellChildMap = _model.CellChildMap; + int[,] newCellChildMap = new int[rows, cols]; + + int sourceRow = 0; + for (int row = 0; row < rows; row++) + { + for (int col = 0; col < cols; col++) + { + if (cellChildMap[sourceRow, col] > spliteeIndex || ((row > foundRow) && (cellChildMap[sourceRow, col] == spliteeIndex))) + { + newCellChildMap[row, col] = cellChildMap[sourceRow, col] + 1; + } + else + { + newCellChildMap[row, col] = cellChildMap[sourceRow, col]; + } + } + + if (row != foundRow) + { + sourceRow++; + } + } + + RowColInfo[] split = _rowInfo[foundRow].Split(offset, space); + + _rowInfo[foundRow] = split[0]; + _rowInfo.Insert(foundRow + 1, split[1]); + + _model.RowPercents[foundRow] = split[0].Percent; + _model.RowPercents.Insert(foundRow + 1, split[1].Percent); + + double newTotalExtent = actualHeight - (space * (rows + 1)); + for (int row = 0; row < rows; row++) + { + if (row != foundRow && row != foundRow + 1) + { + _rowInfo[row].RecalculatePercent(newTotalExtent); + } + } + + FixAccuracyError(_rowInfo, _model.RowPercents); + _model.CellChildMap = newCellChildMap; + _model.Rows++; + } + + public void SplitOnDrag(GridResizer resizer, double delta, double space) + { + if (resizer.Orientation == Orientation.Vertical) + { + int rows = _model.Rows; + int cols = _model.Columns + 1; + int[,] cellChildMap = _model.CellChildMap; + int[,] newCellChildMap = new int[rows, cols]; + + int draggedResizerStartCol = resizer.StartCol; + + if (delta > 0) + { + int sourceCol = 0; + for (int col = 0; col < cols; col++) + { + for (int row = 0; row < rows; row++) + { + if (col == draggedResizerStartCol + 1 && (row < resizer.StartRow || row >= resizer.EndRow)) + { + newCellChildMap[row, col] = cellChildMap[row, sourceCol + 1]; + } + else + { + newCellChildMap[row, col] = cellChildMap[row, sourceCol]; + } + } + + if (col != draggedResizerStartCol) + { + sourceCol++; + } + } + + RowColInfo[] split = _colInfo[draggedResizerStartCol + 1].Split(delta, space); + + _colInfo[draggedResizerStartCol + 1] = split[0]; + _colInfo.Insert(draggedResizerStartCol + 2, split[1]); + + _model.ColumnPercents[draggedResizerStartCol + 1] = split[0].Percent; + _model.ColumnPercents.Insert(draggedResizerStartCol + 2, split[1].Percent); + } + else + { + int sourceCol = 0; + for (int col = 0; col < cols; col++) + { + for (int row = 0; row < rows; row++) + { + if (col == draggedResizerStartCol + 1 && (row >= resizer.StartRow && row < resizer.EndRow)) + { + newCellChildMap[row, col] = cellChildMap[row, sourceCol + 1]; + } + else + { + newCellChildMap[row, col] = cellChildMap[row, sourceCol]; + } + } + + if (col != draggedResizerStartCol) + { + sourceCol++; + } + } + + double offset = _colInfo[draggedResizerStartCol].End - _colInfo[draggedResizerStartCol].Start + delta; + RowColInfo[] split = _colInfo[draggedResizerStartCol].Split(offset - space, space); + + _colInfo[draggedResizerStartCol] = split[1]; + _colInfo.Insert(draggedResizerStartCol + 1, split[0]); + + _model.ColumnPercents[draggedResizerStartCol] = split[1].Percent; + _model.ColumnPercents.Insert(draggedResizerStartCol + 1, split[0].Percent); + } + + FixAccuracyError(_colInfo, _model.ColumnPercents); + _model.CellChildMap = newCellChildMap; + _model.Columns++; + } + else + { + int rows = _model.Rows + 1; + int cols = _model.Columns; + int[,] cellChildMap = _model.CellChildMap; + int[,] newCellChildMap = new int[rows, cols]; + + int draggedResizerStartRow = resizer.StartRow; + + if (delta > 0) + { + int sourcRow = 0; + for (int row = 0; row < rows; row++) + { + for (int col = 0; col < cols; col++) + { + if (row == draggedResizerStartRow + 1 && (col < resizer.StartCol || col >= resizer.EndCol)) + { + newCellChildMap[row, col] = cellChildMap[sourcRow + 1, col]; + } + else + { + newCellChildMap[row, col] = cellChildMap[sourcRow, col]; + } + } + + if (row != draggedResizerStartRow) + { + sourcRow++; + } + } + + RowColInfo[] split = _rowInfo[draggedResizerStartRow + 1].Split(delta, space); + + _rowInfo[draggedResizerStartRow + 1] = split[0]; + _rowInfo.Insert(draggedResizerStartRow + 2, split[1]); + + _model.RowPercents[draggedResizerStartRow + 1] = split[0].Percent; + _model.RowPercents.Insert(draggedResizerStartRow + 2, split[1].Percent); + } + else + { + int sourceRow = 0; + for (int row = 0; row < rows; row++) + { + for (int col = 0; col < cols; col++) + { + if (row == draggedResizerStartRow + 1 && (col >= resizer.StartCol && col < resizer.EndCol)) + { + newCellChildMap[row, col] = cellChildMap[sourceRow + 1, col]; + } + else + { + newCellChildMap[row, col] = cellChildMap[sourceRow, col]; + } + } + + if (row != draggedResizerStartRow) + { + sourceRow++; + } + } + + double offset = _rowInfo[draggedResizerStartRow].End - _rowInfo[draggedResizerStartRow].Start + delta; + RowColInfo[] split = _rowInfo[draggedResizerStartRow].Split(offset - space, space); + + _rowInfo[draggedResizerStartRow] = split[0]; + _rowInfo.Insert(draggedResizerStartRow + 1, split[1]); + + _model.RowPercents[draggedResizerStartRow] = split[0].Percent; + _model.RowPercents.Insert(draggedResizerStartRow + 1, split[1].Percent); + } + + FixAccuracyError(_rowInfo, _model.RowPercents); + _model.CellChildMap = newCellChildMap; + _model.Rows++; + } + } + + public void RecalculateZones(int spacing, Size arrangeSize) + { + int rows = _model.Rows; + int cols = _model.Columns; + + double totalWidth = arrangeSize.Width - (spacing * (cols + 1)); + double totalHeight = arrangeSize.Height - (spacing * (rows + 1)); + + double top = spacing; + for (int row = 0; row < _rowInfo.Count; row++) + { + double cellHeight = _rowInfo[row].Recalculate(top, totalHeight); + top += cellHeight + spacing; + } + + double left = spacing; + for (int col = 0; col < _colInfo.Count; col++) + { + double cellWidth = _colInfo[col].Recalculate(left, totalWidth); + left += cellWidth + spacing; + } + } + + public void ArrangeZones(UIElementCollection zones, int spacing) + { + int rows = _model.Rows; + int cols = _model.Columns; + int[,] cells = _model.CellChildMap; + + if (cells.Length < rows * cols) + { + // Merge was not finished yet, rows and cols values are invalid + return; + } + + double left, top; + for (int row = 0; row < rows; row++) + { + for (int col = 0; col < cols; col++) + { + int i = cells[row, col]; + if (((row == 0) || (cells[row - 1, col] != i)) && + ((col == 0) || (cells[row, col - 1] != i))) + { + // this is not a continuation of a span + GridZone zone = (GridZone)zones[i]; + left = _colInfo[col].Start; + top = _rowInfo[row].Start; + Canvas.SetLeft(zone, left); + Canvas.SetTop(zone, top); + zone.LabelID.Content = i + 1; + + int maxRow = row; + while (((maxRow + 1) < rows) && (cells[maxRow + 1, col] == i)) + { + maxRow++; + } + + zone.HorizontalSnapPoints = null; + if (maxRow > row) + { + zone.HorizontalSnapPoints = new double[maxRow - row]; + int pointsIndex = 0; + for (int walk = row; walk < maxRow; walk++) + { + zone.HorizontalSnapPoints[pointsIndex++] = _rowInfo[walk].End + (spacing / 2) - top; + } + } + + int maxCol = col; + while (((maxCol + 1) < cols) && (cells[row, maxCol + 1] == i)) + { + maxCol++; + } + + zone.VerticalSnapPoints = null; + if (maxCol > col) + { + zone.VerticalSnapPoints = new double[maxCol - col]; + int pointsIndex = 0; + for (int walk = col; walk < maxCol; walk++) + { + zone.VerticalSnapPoints[pointsIndex++] = _colInfo[walk].End + (spacing / 2) - left; + } + } + + zone.MinWidth = _colInfo[maxCol].End - left; + zone.MinHeight = _rowInfo[maxRow].End - top; + } + } + } + } + + public void ArrangeResizers(UIElementCollection adornerChildren, int spacing) + { + int rows = _model.Rows; + int cols = _model.Columns; + + foreach (GridResizer resizer in adornerChildren) + { + if (resizer.Orientation == Orientation.Horizontal) + { + if (resizer.EndCol <= cols && resizer.StartRow < rows) + { + // hard coding this as (resizer.ActualHeight / 2) will still evaluate to 0 here ... a layout hasn't yet happened + Canvas.SetTop(resizer, _rowInfo[resizer.StartRow].End + (spacing / 2) - 24); + Canvas.SetLeft(resizer, (_colInfo[resizer.EndCol - 1].End + _colInfo[resizer.StartCol].Start) / 2); + } + } + else + { + if (resizer.EndRow <= rows && resizer.StartCol < cols) + { + // hard coding this as (resizer.ActualWidth / 2) will still evaluate to 0 here ... a layout hasn't yet happened + Canvas.SetLeft(resizer, _colInfo[resizer.StartCol].End + (spacing / 2) - 24); + Canvas.SetTop(resizer, (_rowInfo[resizer.EndRow - 1].End + _rowInfo[resizer.StartRow].Start) / 2); + } + } + } + } + + public ResizeInfo CalculateResizeInfo(GridResizer resizer, double delta) + { + ResizeInfo res = new ResizeInfo(); + + int rowIndex = resizer.StartRow; + int colIndex = resizer.StartCol; + int[,] indices = _model.CellChildMap; + + List info; + List percents; + int index; + + if (resizer.Orientation == Orientation.Vertical) + { + res.CurrentExtent = _colInfo[colIndex].Extent; + res.CurrentPercent = _colInfo[colIndex].Percent; + + info = _colInfo; + percents = _model.ColumnPercents; + index = colIndex; + + Func indexCmpr = (ind) => + { + bool sameIndices = true; + for (int i = resizer.StartRow; i < resizer.EndRow && sameIndices; i++) + { + sameIndices &= indices[i, ind] == indices[i, ind - 1]; + } + + return sameIndices; + }; + + res.CalcAdjacentZones(colIndex, _model.Columns, _colInfo, indexCmpr); + } + else + { + res.CurrentExtent = _rowInfo[rowIndex].Extent; + res.CurrentPercent = _rowInfo[rowIndex].Percent; + + info = _rowInfo; + percents = _model.RowPercents; + index = rowIndex; + + Func indexCmpr = (ind) => + { + bool sameIndices = true; + for (int i = resizer.StartCol; i < resizer.EndCol && sameIndices; i++) + { + sameIndices &= indices[ind, i] == indices[ind - 1, i]; + } + + return sameIndices; + }; + + res.CalcAdjacentZones(rowIndex, _model.Rows, _rowInfo, indexCmpr); + } + + res.FixAccuracyError(info, percents, delta > 0 ? index + 2 : index + 1); + res.CalcNewPercent(delta); + return res; + } + + public void DragResizer(GridResizer resizer, ResizeInfo data) + { + List info; + List percents; + int index; + + if (resizer.Orientation == Orientation.Vertical) + { + info = _colInfo; + percents = _model.ColumnPercents; + index = resizer.StartCol; + } + else + { + info = _rowInfo; + percents = _model.RowPercents; + index = resizer.StartRow; + } + + int nextPercent = data.CurrentPercent + data.AdjacentPercent + info[index + 1].Percent; + + percents[index] = info[index].Percent = data.NewPercent - data.AdjacentPercent; + percents[index + 1] = info[index + 1].Percent = nextPercent - data.NewPercent; + } + + public bool SwapNegativePercents(Orientation orientation, int startRow, int endRow, int startCol, int endCol) + { + List percents; + int index; + Action swapIndicesPrevLine, swapIndicesNextLine; + + if (orientation == Orientation.Vertical) + { + percents = _model.ColumnPercents; + index = startCol; + + swapIndicesPrevLine = () => + { + for (int row = startRow; row < endRow; row++) + { + _model.CellChildMap[row, startCol] = _model.CellChildMap[row, startCol + 1]; + } + + for (int row = 0; row < _model.Rows; row++) + { + if (row < startRow || row >= endRow) + { + _model.CellChildMap[row, startCol] = _model.CellChildMap[row, startCol - 1]; + } + } + }; + + swapIndicesNextLine = () => + { + for (int row = startRow; row < endRow; row++) + { + _model.CellChildMap[row, startCol + 1] = _model.CellChildMap[row, startCol]; + } + + for (int row = 0; row < _model.Rows; row++) + { + if (row < startRow || row >= endRow) + { + _model.CellChildMap[row, startCol + 1] = _model.CellChildMap[row, startCol + 2]; + } + } + }; + } + else + { + percents = _model.RowPercents; + index = startRow; + + swapIndicesPrevLine = () => + { + for (int col = startCol; col < endCol; col++) + { + _model.CellChildMap[startRow, col] = _model.CellChildMap[startRow + 1, col]; + } + + for (int col = 0; col < _model.Columns; col++) + { + if (col < startCol || col >= endCol) + { + _model.CellChildMap[startRow, col] = _model.CellChildMap[startRow - 1, col]; + } + } + }; + + swapIndicesNextLine = () => + { + for (int col = startCol; col < endCol; col++) + { + _model.CellChildMap[startRow + 1, col] = _model.CellChildMap[startRow, col]; + } + + for (int col = 0; col < _model.Columns; col++) + { + if (col < startCol || col >= endCol) + { + _model.CellChildMap[startRow + 1, col] = _model.CellChildMap[startRow + 2, col]; + } + } + }; + } + + if (percents[index] < 0) + { + swapIndicesPrevLine(); + + percents[index] = -percents[index]; + percents[index - 1] = percents[index - 1] - percents[index]; + + if (orientation == Orientation.Vertical) + { + _colInfo[index].Percent = percents[index]; + _colInfo[index - 1].Percent = percents[index - 1]; + } + else + { + _rowInfo[index].Percent = percents[index]; + _rowInfo[index - 1].Percent = percents[index - 1]; + } + + return true; + } + + if (percents[index + 1] < 0) + { + swapIndicesNextLine(); + + percents[index + 1] = -percents[index + 1]; + percents[index] = percents[index] - percents[index + 1]; + + if (orientation == Orientation.Vertical) + { + _colInfo[index].Percent = percents[index]; + _colInfo[index + 1].Percent = percents[index + 1]; + } + else + { + _rowInfo[index].Percent = percents[index]; + _rowInfo[index + 1].Percent = percents[index + 1]; + } + + return true; + } + + return false; + } + + public int SwappedIndexAfterResize(GridResizer resizer) + { + if (resizer.Orientation == Orientation.Horizontal) + { + for (int i = 0; i < _model.Rows; i++) + { + if (_rowInfo[i].Percent < 0) + { + _rowInfo[i].Percent = -_rowInfo[i].Percent; + _rowInfo[i - 1].Percent -= _rowInfo[i].Percent; + _rowInfo[i + 1].Percent -= _rowInfo[i].Percent; + + _model.RowPercents[i - 1] = _rowInfo[i - 1].Percent; + _model.RowPercents[i] = _rowInfo[i].Percent; + _model.RowPercents[i + 1] = _rowInfo[i + 1].Percent; + + return i; + } + } + } + else + { + for (int i = 1; i < _model.Columns; i++) + { + if (_colInfo[i].Percent < 0) + { + _colInfo[i - 1].Percent += _colInfo[i].Percent; + _colInfo[i].Percent = -_colInfo[i].Percent; + + _model.ColumnPercents[i - 1] = _colInfo[i - 1].Percent; + _model.ColumnPercents[i] = _colInfo[i].Percent; + + return i; + } + } + } + + return -1; + } + + public void MergeZones(int startRow, int endRow, int startCol, int endCol, Action deleteAction, int zoneCount) + { + int[,] cells = _model.CellChildMap; + int mergedIndex = cells[startRow, startCol]; + + // maintain indices order after merge + Dictionary indexReplacement = new Dictionary(); + List zoneIndices = new List(zoneCount); + for (int i = 0; i < zoneCount; i++) + { + zoneIndices.Add(i); + } + + for (int row = startRow; row <= endRow; row++) + { + for (int col = startCol; col <= endCol; col++) + { + int childIndex = cells[row, col]; + if (childIndex != mergedIndex) + { + indexReplacement[childIndex] = mergedIndex; + zoneIndices[childIndex] = -1; + } + } + } + + for (int i = zoneIndices.Count - 1; i >= 0; i--) + { + int index = zoneIndices[i]; + if (index == -1) + { + deleteAction(i); + zoneIndices.RemoveAt(i); + } + } + + for (int i = zoneIndices.Count - 1; i >= 0; i--) + { + indexReplacement[zoneIndices[i]] = i; + } + + ReplaceIndicesToMaintainOrder(indexReplacement); + CollapseIndices(); + FixAccuracyError(_rowInfo, _model.RowPercents); + FixAccuracyError(_colInfo, _model.ColumnPercents); + } + + public void ReplaceIndicesToMaintainOrder(int zoneCount) + { + int[,] cells = _model.CellChildMap; + Dictionary indexReplacement = new Dictionary(); + List zoneIndices = new List(zoneCount); + HashSet zoneIndexSet = new HashSet(zoneCount); + + for (int i = 0; i < zoneCount; i++) + { + zoneIndices.Add(i); + } + + for (int row = 0; row < _model.Rows; row++) + { + for (int col = 0; col < _model.Columns; col++) + { + zoneIndexSet.Add(cells[row, col]); + } + } + + int j = 0; + foreach (int index in zoneIndexSet) + { + indexReplacement[index] = zoneIndices[j]; + j++; + } + + ReplaceIndicesToMaintainOrder(indexReplacement); + } + + private void ReplaceIndicesToMaintainOrder(Dictionary indexReplacement) + { + int[,] cells = _model.CellChildMap; + + for (int row = 0; row < _model.Rows; row++) + { + for (int col = 0; col < _model.Columns; col++) + { + cells[row, col] = indexReplacement[cells[row, col]]; + } + } + } + + private void CollapseIndices() + { + List rowsToRemove = new List(), colsToRemove = new List(); + int[,] cellChildMap = _model.CellChildMap; + + int arrayShift = 0; + for (int row = 1; row < _model.Rows; row++) + { + bool couldBeRemoved = true; + for (int col = 0; col < _model.Columns && couldBeRemoved; col++) + { + if (cellChildMap[row, col] != cellChildMap[row - 1, col]) + { + couldBeRemoved = false; + } + } + + if (couldBeRemoved) + { + _rowInfo[row - 1 - arrayShift].Percent += _rowInfo[row - arrayShift].Percent; + _rowInfo.RemoveAt(row - arrayShift); + + _model.RowPercents[row - 1 - arrayShift] += _model.RowPercents[row - arrayShift]; + _model.RowPercents.RemoveAt(row - arrayShift); + + rowsToRemove.Add(row); + arrayShift++; + } + } + + arrayShift = 0; + for (int col = 1; col < _model.Columns; col++) + { + bool couldBeRemoved = true; + for (int row = 0; row < _model.Rows && couldBeRemoved; row++) + { + if (cellChildMap[row, col] != cellChildMap[row, col - 1]) + { + couldBeRemoved = false; + } + } + + if (couldBeRemoved) + { + _colInfo[col - 1 - arrayShift].Percent += _colInfo[col - arrayShift].Percent; + _colInfo.RemoveAt(col - arrayShift); + + _model.ColumnPercents[col - 1 - arrayShift] += _model.ColumnPercents[col - arrayShift]; + _model.ColumnPercents.RemoveAt(col - arrayShift); + + colsToRemove.Add(col); + arrayShift++; + } + } + + int rows = _model.Rows - rowsToRemove.Count; + int cols = _model.Columns - colsToRemove.Count; + + if (rowsToRemove.Count > 0 || colsToRemove.Count > 0) + { + int[,] newCellChildMap = new int[rows, cols]; + int dstRow = 0, dstCol = 0; + + int removableRowIndex = 0; + int removableRow = -1; + if (rowsToRemove.Count > 0) + { + removableRow = rowsToRemove[removableRowIndex]; + } + + for (int row = 0; row < _model.Rows; row++) + { + if (row != removableRow) + { + int removableColIndex = 0; + int removableCol = -1; + if (colsToRemove.Count > 0) + { + removableCol = colsToRemove[removableColIndex]; + } + + dstCol = 0; + for (int col = 0; col < _model.Columns; col++) + { + if (col != removableCol) + { + newCellChildMap[dstRow, dstCol] = cellChildMap[row, col]; + dstCol++; + } + else + { + removableColIndex++; + if (removableColIndex < colsToRemove.Count) + { + removableCol = colsToRemove[removableColIndex]; + } + } + } + + dstRow++; + } + else + { + removableRowIndex++; + if (removableRowIndex < rowsToRemove.Count) + { + removableRow = rowsToRemove[removableRowIndex]; + } + } + } + + _model.CellChildMap = newCellChildMap; + } + + _model.Rows = rows; + _model.Columns = cols; + } + + private void FixAccuracyError(List info, List percents) + { + int total = 0; + for (int i = 0; i < info.Count; i++) + { + total += info[i].Percent; + } + + int totalDiff = total - 10000; + if (totalDiff != 0) + { + int perLineDiff = totalDiff / percents.Count; + int lastLineDiff = totalDiff - (perLineDiff * (percents.Count - 1)); + + for (int i = 0; i < percents.Count - 1 && perLineDiff != 0; i++) + { + int percent = percents[i] - perLineDiff; + if (percent < 0) + { + percent = 0; + } + + percents[i] = percent; + info[i].Percent = percent; + } + + info[percents.Count - 1].Percent -= lastLineDiff; + percents[percents.Count - 1] -= lastLineDiff; + } + } + + private GridLayoutModel _model; + private List _rowInfo; + private List _colInfo; + } +} diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/GridDragHandles.cs b/src/modules/fancyzones/editor/FancyZonesEditor/GridDragHandles.cs new file mode 100644 index 0000000000..da0975933f --- /dev/null +++ b/src/modules/fancyzones/editor/FancyZonesEditor/GridDragHandles.cs @@ -0,0 +1,604 @@ +// Copyright (c) Microsoft Corporation +// The Microsoft Corporation licenses this file to you under the MIT license. +// See the LICENSE file in the project root for more information. + +using System; +using System.Collections.Generic; +using System.Windows.Controls; +using System.Windows.Controls.Primitives; +using FancyZonesEditor.Models; + +namespace FancyZonesEditor +{ + public class GridDragHandles + { + public GridDragHandles(UIElementCollection resizers, Action dragDelta, Action dragCompleted) + { + _resizers = resizers; + _dragDelta = dragDelta; + _dragCompleted = dragCompleted; + } + + public void InitDragHandles(GridLayoutModel model) + { + if (_resizers.Count == 0) + { + int[,] indices = model.CellChildMap; + + // horizontal resizers + for (int row = 0; row < model.Rows - 1; row++) + { + for (int col = 0; col < model.Columns; col++) + { + if (indices[row, col] != indices[row + 1, col]) + { + int endCol = col + 1; + while (endCol < model.Columns && indices[row, endCol] != indices[row + 1, endCol]) + { + endCol++; + } + + AddDragHandle(Orientation.Horizontal, row, row + 1, col, endCol, row); + col = endCol - 1; + } + } + } + + // vertical resizers + for (int col = 0; col < model.Columns - 1; col++) + { + for (int row = 0; row < model.Rows; row++) + { + if (indices[row, col] != indices[row, col + 1]) + { + int endRow = row + 1; + while (endRow < model.Rows && indices[endRow, col] != indices[endRow, col + 1]) + { + endRow++; + } + + AddDragHandle(Orientation.Vertical, row, endRow, col, col + 1, col + model.Rows - 1); + row = endRow - 1; + } + } + } + } + } + + public void AddDragHandle(Orientation orientation, int foundRow, int foundCol, GridLayoutModel model) + { + int[,] indices = model.CellChildMap; + + int endRow = foundRow + 1; + while (endRow < model.Rows && indices[endRow, foundCol] == indices[endRow - 1, foundCol]) + { + endRow++; + } + + int endCol = foundCol + 1; + while (endCol < model.Columns && indices[foundRow, endCol] == indices[foundRow, endCol - 1]) + { + endCol++; + } + + int index = (orientation == Orientation.Horizontal) ? foundRow : foundCol + model.Rows - 1; + AddDragHandle(orientation, foundRow, endRow, foundCol, endCol, index); + } + + public void AddDragHandle(Orientation orientation, int rowStart, int rowEnd, int colStart, int colEnd, int index) + { + GridResizer resizer = new GridResizer + { + Orientation = orientation, + StartRow = rowStart, + EndRow = rowEnd, + StartCol = colStart, + EndCol = colEnd, + }; + + resizer.DragDelta += (obj, eventArgs) => _dragDelta(obj, eventArgs); + resizer.DragCompleted += (obj, eventArgs) => _dragCompleted(obj, eventArgs); + + if (index > _resizers.Count) + { + index = _resizers.Count; + } + + _resizers.Insert(index, resizer); + } + + public void UpdateForExistingVerticalSplit(GridLayoutModel model, int foundRow, int splitCol) + { + Func cmpr = (GridResizer resizer) => + { + return resizer.Orientation == Orientation.Vertical && resizer.StartCol == splitCol; + }; + + Func endCmpr = (GridResizer resizer) => + { + return resizer.EndRow == foundRow; + }; + + Func startCmpr = (GridResizer resizer) => + { + return resizer.StartRow == foundRow + 1; + }; + + if (!UpdateDragHandlerForExistingSplit(Orientation.Vertical, cmpr, endCmpr, startCmpr)) + { + AddDragHandle(Orientation.Vertical, foundRow, splitCol, model); + } + } + + public void UpdateForExistingHorizontalSplit(GridLayoutModel model, int splitRow, int foundCol) + { + Func cmpr = (GridResizer resizer) => + { + return resizer.Orientation == Orientation.Horizontal && resizer.StartRow == splitRow; + }; + + Func endCmpr = (GridResizer resizer) => + { + return resizer.EndCol == foundCol; + }; + + Func startCmpr = (GridResizer resizer) => + { + return resizer.StartCol == foundCol + 1; + }; + + if (!UpdateDragHandlerForExistingSplit(Orientation.Horizontal, cmpr, endCmpr, startCmpr)) + { + AddDragHandle(Orientation.Horizontal, splitRow, foundCol, model); + } + } + + /** + * Has to be called on split before adding new drag handle + */ + public void UpdateAfterVerticalSplit(int foundCol) + { + foreach (GridResizer r in _resizers) + { + if (r.StartCol > foundCol || (r.StartCol == foundCol && r.Orientation == Orientation.Vertical)) + { + r.StartCol++; + } + + if (r.EndCol > foundCol) + { + r.EndCol++; + } + } + } + + /** + * Has to be called on split before adding new drag handle + */ + public void UpdateAfterHorizontalSplit(int foundRow) + { + foreach (GridResizer r in _resizers) + { + if (r.StartRow > foundRow || (r.StartRow == foundRow && r.Orientation == Orientation.Horizontal)) + { + r.StartRow++; + } + + if (r.EndRow > foundRow) + { + r.EndRow++; + } + } + } + + public void UpdateAfterSwap(GridResizer resizer, double delta) + { + Orientation orientation = resizer.Orientation; + bool isHorizontal = orientation == Orientation.Horizontal; + bool isDeltaNegative = delta < 0; + List swappedResizers = new List(); + + if (isDeltaNegative) + { + DecreaseResizerValues(resizer, orientation); + } + else + { + IncreaseResizerValues(resizer, orientation); + } + + // same orientation resizers update + foreach (GridResizer r in _resizers) + { + if (r.Orientation == orientation) + { + if ((isHorizontal && r.StartRow == resizer.StartRow && r.StartCol != resizer.StartCol) || + (!isHorizontal && r.StartCol == resizer.StartCol && r.StartRow != resizer.StartRow)) + { + if (isDeltaNegative) + { + IncreaseResizerValues(r, orientation); + } + else + { + DecreaseResizerValues(r, orientation); + } + + swappedResizers.Add(r); + } + } + } + + // different orientation resizers update + foreach (GridResizer r in _resizers) + { + if (r.Orientation != resizer.Orientation) + { + if (isHorizontal) + { + // vertical resizers corresponding to dragged resizer + if (r.StartCol >= resizer.StartCol && r.EndCol < resizer.EndCol) + { + if (r.StartRow == resizer.StartRow + 2 && isDeltaNegative) + { + r.StartRow--; + } + + if (r.EndRow == resizer.EndRow + 1 && isDeltaNegative) + { + r.EndRow--; + } + + if (r.StartRow == resizer.StartRow && !isDeltaNegative) + { + r.StartRow++; + } + + if (r.EndRow == resizer.EndRow - 1 && !isDeltaNegative) + { + r.EndRow++; + } + } + else + { + // vertical resizers corresponding to swapped resizers + foreach (GridResizer sr in swappedResizers) + { + if (r.StartCol >= sr.StartCol && r.EndCol <= sr.EndCol) + { + if (r.StartRow == resizer.StartRow + 1 && isDeltaNegative) + { + r.StartRow++; + } + + if (r.EndRow == resizer.EndRow && isDeltaNegative) + { + r.EndRow++; + } + + if (r.StartRow == resizer.StartRow + 1 && !isDeltaNegative) + { + r.StartRow--; + } + + if (r.EndRow == resizer.EndRow && !isDeltaNegative) + { + r.EndRow--; + } + } + } + } + } + else + { + // horizontal resizers corresponding to dragged resizer + if (r.StartRow >= resizer.StartRow && r.EndRow < resizer.EndRow) + { + if (r.StartCol == resizer.StartCol + 3 && isDeltaNegative) + { + r.StartCol--; + } + + if (r.EndCol == resizer.EndCol + 1 && isDeltaNegative) + { + r.EndCol--; + } + + if (r.StartCol == resizer.StartCol && !isDeltaNegative) + { + r.StartCol++; + } + + if (r.EndCol == resizer.EndCol - 1 && !isDeltaNegative) + { + r.EndCol++; + } + } + else + { + // horizontal resizers corresponding to swapped resizers + foreach (GridResizer sr in swappedResizers) + { + if (r.StartRow >= sr.StartRow && r.EndRow <= sr.EndRow) + { + if (r.StartCol == resizer.StartCol + 1 && isDeltaNegative) + { + r.StartCol++; + } + + if (r.EndCol == resizer.EndCol && isDeltaNegative) + { + r.EndCol++; + } + + if (r.StartCol == resizer.StartCol + 1 && !isDeltaNegative) + { + r.StartCol--; + } + + if (r.EndCol == resizer.EndCol && !isDeltaNegative) + { + r.EndCol--; + } + } + } + } + } + } + } + } + + public void UpdateAfterDetach(GridResizer resizer, double delta) + { + bool isDeltaNegative = delta < 0; + Orientation orientation = resizer.Orientation; + + foreach (GridResizer r in _resizers) + { + bool notEqual = r.StartRow != resizer.StartRow || r.EndRow != resizer.EndRow || r.StartCol != resizer.StartCol || r.EndCol != resizer.EndCol; + if (r.Orientation == orientation && notEqual) + { + if (orientation == Orientation.Horizontal) + { + if (r.StartRow > resizer.StartRow || (r.StartRow == resizer.StartRow && isDeltaNegative)) + { + r.StartRow++; + } + + if (r.EndRow > resizer.EndRow || (r.EndRow == resizer.EndRow && isDeltaNegative)) + { + r.EndRow++; + } + } + else + { + if (r.StartCol > resizer.StartCol || (r.StartCol == resizer.StartCol && isDeltaNegative)) + { + r.StartCol++; + } + + if (r.EndCol > resizer.EndCol || (r.EndCol == resizer.EndCol && isDeltaNegative)) + { + r.EndCol++; + } + } + } + } + + if (!isDeltaNegative) + { + IncreaseResizerValues(resizer, orientation); + } + + foreach (GridResizer r in _resizers) + { + if (r.Orientation != orientation) + { + if (orientation == Orientation.Vertical) + { + if (isDeltaNegative) + { + bool isRowNonAdjacent = r.EndRow < resizer.StartRow || r.StartRow > resizer.EndRow; + + if (r.StartCol > resizer.StartCol + 1 || (r.StartCol == resizer.StartCol + 1 && isRowNonAdjacent)) + { + r.StartCol++; + } + + if (r.EndCol > resizer.EndCol || (r.EndCol == resizer.EndCol && isRowNonAdjacent)) + { + r.EndCol++; + } + } + else + { + if (r.StartCol > resizer.StartCol || (r.StartCol == resizer.StartCol && r.StartRow >= resizer.StartRow && r.EndRow <= resizer.EndRow)) + { + r.StartCol++; + } + + if (r.EndCol > resizer.EndCol - 1 || (r.EndCol == resizer.EndCol - 1 && r.StartRow >= resizer.StartRow && r.EndRow <= resizer.EndRow)) + { + r.EndCol++; + } + } + } + else + { + if (isDeltaNegative) + { + bool isColNonAdjacent = r.EndCol < resizer.StartCol || r.StartCol > resizer.EndCol; + + if (r.StartRow > resizer.StartRow + 1 || (r.StartRow == resizer.StartRow + 1 && isColNonAdjacent)) + { + r.StartRow++; + } + + if (r.EndRow > resizer.EndRow || (r.EndRow == resizer.EndRow && isColNonAdjacent)) + { + r.EndRow++; + } + } + else + { + if (r.StartRow > resizer.StartRow || (r.StartRow == resizer.StartRow && r.StartCol >= resizer.StartCol && r.EndCol <= resizer.EndCol)) + { + r.StartRow++; + } + + if (r.EndRow > resizer.EndRow - 1 || (r.EndRow == resizer.EndRow - 1 && r.StartCol >= resizer.StartCol && r.EndCol <= resizer.EndCol)) + { + r.EndRow++; + } + } + } + } + } + } + + public void RemoveDragHandles() + { + _resizers.Clear(); + } + + public bool HasSnappedNonAdjascentResizers(GridResizer resizer) + { + /** + * Resizers between zones 0,1 and 4,5 are snapped to each other and not adjascent. + * ------------------------------ + * | 0 | 1 | + * ------------------------------ + * | 2 | 3 | + * ------------------------------ + * | 4 | 5 | + * ------------------------------ + * + * Resizers between zones 0,1 and 2,3 are snapped to each other and adjascent. + * ------------------------------ + * | 0 | 1 | + * ------------------------------ + * | 2 | 3 | + * ------------------------------ + * | 4 | 5 | + * ------------------------------ + * + * Vertical resizers should have same StartColumn and different StartRow. + * Horizontal resizers should have same StartRow and different StartColumn. + * Difference between rows or colums should be more than 1. + */ + foreach (GridResizer r in _resizers) + { + if (r.Orientation == resizer.Orientation) + { + bool isHorizontalSnapped = resizer.Orientation == Orientation.Horizontal && r.StartRow == resizer.StartRow && (Math.Abs(resizer.StartCol - r.StartCol) > 1); + bool isVerticalSnapped = resizer.Orientation == Orientation.Vertical && r.StartCol == resizer.StartCol && (Math.Abs(resizer.StartRow - r.StartRow) > 1); + if (isHorizontalSnapped || isVerticalSnapped) + { + return true; + } + } + } + + return false; + } + + private static void IncreaseResizerValues(GridResizer resizer, Orientation orientation) + { + if (orientation == Orientation.Vertical) + { + resizer.StartCol++; + resizer.EndCol++; + } + else + { + resizer.StartRow++; + resizer.EndRow++; + } + } + + private static void DecreaseResizerValues(GridResizer resizer, Orientation orientation) + { + if (orientation == Orientation.Vertical) + { + resizer.StartCol--; + resizer.EndCol--; + } + else + { + resizer.StartRow--; + resizer.EndRow--; + } + } + + private bool UpdateDragHandlerForExistingSplit(Orientation orientation, Func cmpr, Func endCmpr, Func startCmpr) + { + bool updCurrentResizers = false; + GridResizer leftNeighbour = null; + GridResizer rightNeighbour = null; + + for (int i = 0; i < _resizers.Count && (leftNeighbour == null || rightNeighbour == null); i++) + { + GridResizer resizer = (GridResizer)_resizers[i]; + if (cmpr(resizer)) + { + if (leftNeighbour == null && endCmpr(resizer)) + { + leftNeighbour = resizer; + updCurrentResizers = true; + } + + if (rightNeighbour == null && startCmpr(resizer)) + { + rightNeighbour = resizer; + updCurrentResizers = true; + } + } + } + + if (updCurrentResizers) + { + if (leftNeighbour != null && rightNeighbour != null) + { + if (orientation == Orientation.Vertical) + { + leftNeighbour.EndRow = rightNeighbour.EndRow; + } + else + { + leftNeighbour.EndCol = rightNeighbour.EndCol; + } + + _resizers.Remove(rightNeighbour); + } + else if (leftNeighbour != null) + { + if (orientation == Orientation.Vertical) + { + leftNeighbour.EndRow++; + } + else + { + leftNeighbour.EndCol++; + } + } + else if (rightNeighbour != null) + { + if (orientation == Orientation.Vertical) + { + rightNeighbour.StartRow--; + } + else + { + rightNeighbour.StartCol--; + } + } + } + + return updCurrentResizers; + } + + private readonly UIElementCollection _resizers; + private readonly Action _dragDelta; + private readonly Action _dragCompleted; + } +} diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/GridEditor.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/GridEditor.xaml.cs index 3a17166166..51abf3e64b 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/GridEditor.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/GridEditor.xaml.cs @@ -26,6 +26,7 @@ namespace FancyZonesEditor { InitializeComponent(); Loaded += GridEditor_Loaded; + Unloaded += GridEditor_Unloaded; ((App)Application.Current).ZoneSettings.PropertyChanged += ZoneSettings_PropertyChanged; gridEditorUniqueId = ++gridEditorUniqueIdCounter; } @@ -35,30 +36,11 @@ namespace FancyZonesEditor GridLayoutModel model = (GridLayoutModel)DataContext; if (model != null) { - int rows = model.Rows; - int cols = model.Columns; - _rowInfo = new RowColInfo[rows]; - for (int row = 0; row < rows; row++) - { - _rowInfo[row] = new RowColInfo(model.RowPercents[row]); - } + _data = new GridData(model); + _dragHandles = new GridDragHandles(AdornerLayer.Children, Resizer_DragDelta, Resizer_DragCompleted); - _colInfo = new RowColInfo[cols]; - for (int col = 0; col < cols; col++) - { - _colInfo[col] = new RowColInfo(model.ColumnPercents[col]); - } - - int maxIndex = 0; - for (int row = 0; row < rows; row++) - { - for (int col = 0; col < cols; col++) - { - maxIndex = Math.Max(maxIndex, model.CellChildMap[row, col]); - } - } - - for (int i = 0; i <= maxIndex; i++) + int zoneCount = _data.ZoneCount; + for (int i = 0; i <= zoneCount; i++) { AddZone(); } @@ -72,7 +54,12 @@ namespace FancyZonesEditor } Model.PropertyChanged += OnGridDimensionsChanged; - AddDragHandles(); + _dragHandles.InitDragHandles(model); + } + + private void GridEditor_Unloaded(object sender, RoutedEventArgs e) + { + gridEditorUniqueId = -1; } private void ZoneSettings_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) @@ -116,7 +103,7 @@ namespace FancyZonesEditor { if (model.CellChildMap[row, col] == spliteeIndex) { - RemoveDragHandles(); + _dragHandles.RemoveDragHandles(); _startRow = _endRow = row; _startCol = _endCol = col; ExtendRangeToHaveEvenCellEdges(); @@ -190,6 +177,8 @@ namespace FancyZonesEditor private void OnSplit(object o, SplitEventArgs e) { + MergeCancelClick(null, null); + UIElementCollection previewChildren = Preview.Children; GridZone splitee = (GridZone)o; @@ -198,26 +187,10 @@ namespace FancyZonesEditor int rows = model.Rows; int cols = model.Columns; - int foundRow = -1; - int foundCol = -1; - for (int row = 0; row < rows; row++) - { - for (int col = 0; col < cols; col++) - { - if (model.CellChildMap[row, col] == spliteeIndex) - { - foundRow = row; - foundCol = col; - break; - } - } - - if (foundRow != -1) - { - break; - } - } + Tuple rowCol = _data.RowColByIndex(spliteeIndex); + int foundRow = rowCol.Item1; + int foundCol = rowCol.Item2; int newChildIndex = AddZone(); @@ -231,98 +204,46 @@ namespace FancyZonesEditor offset += Canvas.GetLeft(splitee); int count = splitee.VerticalSnapPoints.Length; bool foundExistingSplit = false; + int splitCol = foundCol; for (int i = 0; i <= count; i++) { if (foundExistingSplit) { int walkRow = foundRow; - while ((walkRow < rows) && (model.CellChildMap[walkRow, foundCol + i] == spliteeIndex)) + while ((walkRow < rows) && (_data.GetIndex(walkRow, foundCol + i) == spliteeIndex)) { - model.CellChildMap[walkRow++, foundCol + i] = newChildIndex; + _data.SetIndex(walkRow++, foundCol + i, newChildIndex); } } - if (_colInfo[foundCol + i].End == offset) + if (_data.ColumnBottom(foundCol + i) == offset) { foundExistingSplit = true; - + splitCol = foundCol + i; // use existing division } } if (foundExistingSplit) { + _data.ReplaceIndicesToMaintainOrder(Preview.Children.Count); + _dragHandles.UpdateForExistingVerticalSplit(model, foundRow, splitCol); OnGridDimensionsChanged(); return; } - while (_colInfo[foundCol].End < offset) + while (_data.ColumnBottom(foundCol) < offset) { foundCol++; } - offset -= _colInfo[foundCol].Start; + offset -= _data.ColumnTop(foundCol); } - AddDragHandle(Orientation.Vertical, cols - 1); - cols++; - int[,] newCellChildMap = new int[rows, cols]; - int[] newColPercents = new int[cols]; - RowColInfo[] newColInfo = new RowColInfo[cols]; - - int sourceCol = 0; - for (int col = 0; col < cols; col++) - { - for (int row = 0; row < rows; row++) - { - if ((col > foundCol) && (model.CellChildMap[row, sourceCol] == spliteeIndex)) - { - newCellChildMap[row, col] = newChildIndex; - } - else - { - newCellChildMap[row, col] = model.CellChildMap[row, sourceCol]; - } - } - - if (col != foundCol) - { - sourceCol++; - } - } - - model.CellChildMap = newCellChildMap; - - sourceCol = 0; - double newTotalExtent = ActualWidth - (space * (cols + 1)); - for (int col = 0; col < cols; col++) - { - if (col == foundCol) - { - RowColInfo[] split = _colInfo[col].Split(offset, space); - newColInfo[col] = split[0]; - newColPercents[col] = split[0].Percent; - col++; - - newColInfo[col] = split[1]; - newColPercents[col] = split[1].Percent; - } - else - { - newColInfo[col] = _colInfo[sourceCol]; - newColInfo[col].RecalculatePercent(newTotalExtent); - - newColPercents[col] = model.ColumnPercents[sourceCol]; - } - - sourceCol++; - } - - _colInfo = newColInfo; - model.ColumnPercents = newColPercents; - - model.Columns++; + _dragHandles.UpdateAfterVerticalSplit(foundCol); + _data.SplitColumn(foundCol, spliteeIndex, newChildIndex, space, offset, ActualWidth); + _dragHandles.AddDragHandle(Orientation.Vertical, foundRow, foundCol, model); } else { @@ -332,158 +253,55 @@ namespace FancyZonesEditor offset += Canvas.GetTop(splitee); int count = splitee.HorizontalSnapPoints.Length; bool foundExistingSplit = false; + int splitRow = foundRow; for (int i = 0; i <= count; i++) { if (foundExistingSplit) { int walkCol = foundCol; - while ((walkCol < cols) && (model.CellChildMap[foundRow + i, walkCol] == spliteeIndex)) + while ((walkCol < cols) && (_data.GetIndex(foundRow + i, walkCol) == spliteeIndex)) { - model.CellChildMap[foundRow + i, walkCol] = newChildIndex; + _data.SetIndex(foundRow + i, walkCol++, newChildIndex); } } - if (_rowInfo[foundRow + i].End == offset) + if (_data.RowEnd(foundRow + i) == offset) { foundExistingSplit = true; - + splitRow = foundRow + i; // use existing division } } if (foundExistingSplit) { + _data.ReplaceIndicesToMaintainOrder(Preview.Children.Count); + _dragHandles.UpdateForExistingHorizontalSplit(model, splitRow, foundCol); OnGridDimensionsChanged(); return; } - while (_rowInfo[foundRow].End < offset) + while (_data.RowEnd(foundRow) < offset) { foundRow++; } - offset -= _rowInfo[foundRow].Start; + offset -= _data.RowStart(foundRow); } - AddDragHandle(Orientation.Horizontal, rows - 1); - rows++; - int[,] newCellChildMap = new int[rows, cols]; - int[] newRowPercents = new int[rows]; - RowColInfo[] newRowInfo = new RowColInfo[rows]; - - int sourceRow = 0; - for (int row = 0; row < rows; row++) - { - for (int col = 0; col < cols; col++) - { - if ((row > foundRow) && (model.CellChildMap[sourceRow, col] == spliteeIndex)) - { - newCellChildMap[row, col] = newChildIndex; - } - else - { - newCellChildMap[row, col] = model.CellChildMap[sourceRow, col]; - } - } - - if (row != foundRow) - { - sourceRow++; - } - } - - model.CellChildMap = newCellChildMap; - - sourceRow = 0; - double newTotalExtent = ActualHeight - (space * (rows + 1)); - for (int row = 0; row < rows; row++) - { - if (row == foundRow) - { - RowColInfo[] split = _rowInfo[row].Split(offset, space); - newRowInfo[row] = split[0]; - newRowPercents[row] = split[0].Percent; - row++; - - newRowInfo[row] = split[1]; - newRowPercents[row] = split[1].Percent; - } - else - { - newRowInfo[row] = _rowInfo[sourceRow]; - newRowInfo[row].RecalculatePercent(newTotalExtent); - - newRowPercents[row] = model.RowPercents[sourceRow]; - } - - sourceRow++; - } - - _rowInfo = newRowInfo; - model.RowPercents = newRowPercents; - - model.Rows++; - } - } - - private void RemoveDragHandles() - { - AdornerLayer.Children.Clear(); - } - - private void AddDragHandles() - { - if (AdornerLayer.Children.Count == 0) - { - int interiorRows = Model.Rows - 1; - int interiorCols = Model.Columns - 1; - - for (int row = 0; row < interiorRows; row++) - { - AddDragHandle(Orientation.Horizontal, row); - } - - for (int col = 0; col < interiorCols; col++) - { - AddDragHandle(Orientation.Vertical, col); - } - } - } - - private void AddDragHandle(Orientation orientation, int index) - { - GridResizer resizer = new GridResizer - { - Orientation = orientation, - Index = index, - Model = Model, - }; - resizer.DragDelta += Resizer_DragDelta; - - if (orientation == Orientation.Vertical) - { - index += Model.Rows - 1; + _dragHandles.UpdateAfterHorizontalSplit(foundRow); + _data.SplitRow(foundRow, spliteeIndex, newChildIndex, space, offset, ActualHeight); + _dragHandles.AddDragHandle(Orientation.Horizontal, foundRow, foundCol, model); } - AdornerLayer.Children.Insert(index, resizer); + Size actualSize = new Size(ActualWidth, ActualHeight); + ArrangeGridRects(actualSize); } private void DeleteZone(int index) { - IList freeZones = Model.FreeZones; - - if (freeZones.Contains(index)) - { - return; - } - - freeZones.Add(index); - - GridZone zone = (GridZone)Preview.Children[index]; - zone.Visibility = Visibility.Hidden; - zone.MinHeight = 0; - zone.MinWidth = 0; + Preview.Children.RemoveAt(index); } private int AddZone() @@ -544,184 +362,70 @@ namespace FancyZonesEditor return; } + if (model.Rows != model.RowPercents.Count || model.Columns != model.ColumnPercents.Count) + { + // Merge was not finished + return; + } + Settings settings = ((App)Application.Current).ZoneSettings; int spacing = settings.ShowSpacing ? settings.Spacing : 0; - int cols = model.Columns; - int rows = model.Rows; - - double totalWidth = arrangeSize.Width - (spacing * (cols + 1)); - double totalHeight = arrangeSize.Height - (spacing * (rows + 1)); - - double top = spacing; - for (int row = 0; row < rows; row++) - { - double cellHeight = _rowInfo[row].Recalculate(top, totalHeight); - top += cellHeight + spacing; - } - - double left = spacing; - for (int col = 0; col < cols; col++) - { - double cellWidth = _colInfo[col].Recalculate(left, totalWidth); - left += cellWidth + spacing; - } - - int zoneNumber = 1; - for (int row = 0; row < rows; row++) - { - for (int col = 0; col < cols; col++) - { - int i = model.CellChildMap[row, col]; - if (((row == 0) || (model.CellChildMap[row - 1, col] != i)) && - ((col == 0) || (model.CellChildMap[row, col - 1] != i))) - { - // this is not a continuation of a span - GridZone zone = (GridZone)Preview.Children[i]; - left = _colInfo[col].Start; - top = _rowInfo[row].Start; - Canvas.SetLeft(zone, left); - Canvas.SetTop(zone, top); - zone.LabelID.Content = zoneNumber++; - - int maxRow = row; - while (((maxRow + 1) < rows) && (model.CellChildMap[maxRow + 1, col] == i)) - { - maxRow++; - } - - zone.HorizontalSnapPoints = null; - if (maxRow > row) - { - zone.HorizontalSnapPoints = new double[maxRow - row]; - int pointsIndex = 0; - for (int walk = row; walk < maxRow; walk++) - { - zone.HorizontalSnapPoints[pointsIndex++] = _rowInfo[walk].End + (spacing / 2) - top; - } - } - - int maxCol = col; - while (((maxCol + 1) < cols) && (model.CellChildMap[row, maxCol + 1] == i)) - { - maxCol++; - } - - zone.VerticalSnapPoints = null; - if (maxCol > col) - { - zone.VerticalSnapPoints = new double[maxCol - col]; - int pointsIndex = 0; - for (int walk = col; walk < maxCol; walk++) - { - zone.VerticalSnapPoints[pointsIndex++] = _colInfo[walk].End + (spacing / 2) - left; - } - } - - zone.MinWidth = _colInfo[maxCol].End - left; - zone.MinHeight = _rowInfo[maxRow].End - top; - } - } - } - - AddDragHandles(); - int childIndex = 0; - UIElementCollection adornerChildren = AdornerLayer.Children; - for (int row = 0; row < rows - 1; row++) - { - GridResizer resizer = (GridResizer)adornerChildren[childIndex++]; - int startCol = -1; - int endCol = cols - 1; - for (int col = 0; col < cols; col++) - { - if ((startCol == -1) && (model.CellChildMap[row, col] != model.CellChildMap[row + 1, col])) - { - startCol = col; - } - else if ((startCol != -1) && (model.CellChildMap[row, col] == model.CellChildMap[row + 1, col])) - { - endCol = col - 1; - break; - } - } - - if (startCol != -1) - { - // hard coding this as (resizer.ActualHeight / 2) will still evaluate to 0 here ... a layout hasn't yet happened - Canvas.SetTop(resizer, _rowInfo[row].End + (spacing / 2) - 24); - Canvas.SetLeft(resizer, (_colInfo[endCol].End + _colInfo[startCol].Start) / 2); - } - else - { - resizer.Visibility = Visibility.Collapsed; - } - } - - for (int col = 0; col < cols - 1; col++) - { - GridResizer resizer = (GridResizer)adornerChildren[childIndex++]; - int startRow = -1; - int endRow = rows - 1; - for (int row = 0; row < rows; row++) - { - if ((startRow == -1) && (model.CellChildMap[row, col] != model.CellChildMap[row, col + 1])) - { - startRow = row; - } - else if ((startRow != -1) && (model.CellChildMap[row, col] == model.CellChildMap[row, col + 1])) - { - endRow = row - 1; - break; - } - } - - if (startRow != -1) - { - Canvas.SetLeft(resizer, _colInfo[col].End + (spacing / 2) - 24); // hard coding this as (resizer.ActualWidth / 2) will still evaluate to 0 here ... a layout hasn't yet happened - Canvas.SetTop(resizer, (_rowInfo[endRow].End + _rowInfo[startRow].Start) / 2); - resizer.Visibility = Visibility.Visible; - } - else - { - resizer.Visibility = Visibility.Collapsed; - } - } + _data.RecalculateZones(spacing, arrangeSize); + _data.ArrangeZones(Preview.Children, spacing); + _dragHandles.InitDragHandles(model); + _data.ArrangeResizers(AdornerLayer.Children, spacing); } private void Resizer_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e) { + MergeCancelClick(null, null); + GridResizer resizer = (GridResizer)sender; - int[] percents; - RowColInfo[] info; - int index = resizer.Index; - double delta; - if (resizer.Orientation == Orientation.Vertical) + double delta = (resizer.Orientation == Orientation.Vertical) ? e.HorizontalChange : e.VerticalChange; + if (delta == 0) { - percents = Model.ColumnPercents; - info = _colInfo; - delta = e.HorizontalChange; - } - else - { - percents = Model.RowPercents; - info = _rowInfo; - delta = e.VerticalChange; + return; } - double currentExtent = info[index].Extent; - double newExtent = currentExtent + delta; - int currentPercent = info[index].Percent; - int totalPercent = currentPercent + info[index + 1].Percent; + GridData.ResizeInfo resizeInfo = _data.CalculateResizeInfo(resizer, delta); + if (resizeInfo.IsResizeAllowed) + { + if (_dragHandles.HasSnappedNonAdjascentResizers(resizer)) + { + double spacing = 0; + Settings settings = ((App)Application.Current).ZoneSettings; + if (settings.ShowSpacing) + { + spacing = settings.Spacing; + } + + _data.SplitOnDrag(resizer, delta, spacing); + _dragHandles.UpdateAfterDetach(resizer, delta); + } + else + { + _data.DragResizer(resizer, resizeInfo); + if (_data.SwapNegativePercents(resizer.Orientation, resizer.StartRow, resizer.EndRow, resizer.StartCol, resizer.EndCol)) + { + _dragHandles.UpdateAfterSwap(resizer, delta); + } + } + } + + Size actualSize = new Size(ActualWidth, ActualHeight); + ArrangeGridRects(actualSize); + AdornerLayer.UpdateLayout(); + } - int newPercent = (int)(currentPercent * newExtent / currentExtent); - - if ((newPercent > 0) && (newPercent < totalPercent)) + private void Resizer_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e) + { + GridResizer resizer = (GridResizer)sender; + int index = _data.SwappedIndexAfterResize(resizer); + if (index != -1) { - percents[index] = info[index].Percent = newPercent; - percents[index + 1] = info[index + 1].Percent = totalPercent - newPercent; - Size actualSize = new Size(ActualWidth, ActualHeight); ArrangeGridRects(actualSize); } @@ -804,12 +508,12 @@ namespace FancyZonesEditor { if (_startRow == -1) { - if (_rowInfo[row].End > minY) + if (_data.RowEnd(row) > minY) { _startRow = row; } } - else if (_rowInfo[row].Start > maxY) + else if (_data.RowStart(row) > maxY) { _endRow = row - 1; break; @@ -825,12 +529,12 @@ namespace FancyZonesEditor { if (_startCol == -1) { - if (_colInfo[col].End > minX) + if (_data.ColumnBottom(col) > minX) { _startCol = col; } } - else if (_colInfo[col].Start > maxX) + else if (_data.ColumnTop(col) > maxX) { _endCol = col - 1; break; @@ -868,29 +572,21 @@ namespace FancyZonesEditor private void MergeClick(object sender, RoutedEventArgs e) { - GridLayoutModel model = Model; - MergePanel.Visibility = Visibility.Collapsed; - int mergedIndex = model.CellChildMap[_startRow, _startCol]; - for (int row = _startRow; row <= _endRow; row++) + Action deleteAction = (index) => { - for (int col = _startCol; col <= _endCol; col++) - { - int childIndex = model.CellChildMap[row, col]; - if (childIndex != mergedIndex) - { - model.CellChildMap[row, col] = mergedIndex; - DeleteZone(childIndex); - } - } - } + DeleteZone(index); + }; + _data.MergeZones(_startRow, _endRow, _startCol, _endCol, deleteAction, Preview.Children.Count); + _dragHandles.RemoveDragHandles(); + _dragHandles.InitDragHandles(Model); OnGridDimensionsChanged(); ClearSelection(); } - private void CancelClick(object sender, RoutedEventArgs e) + private void MergeCancelClick(object sender, RoutedEventArgs e) { MergePanel.Visibility = Visibility.Collapsed; ClearSelection(); @@ -898,7 +594,7 @@ namespace FancyZonesEditor private void MergePanelMouseUp(object sender, MouseButtonEventArgs e) { - CancelClick(null, null); + MergeCancelClick(null, null); } protected override Size ArrangeOverride(Size arrangeBounds) @@ -909,8 +605,8 @@ namespace FancyZonesEditor return returnSize; } - private RowColInfo[] _rowInfo; - private RowColInfo[] _colInfo; + private GridData _data; + private GridDragHandles _dragHandles; private int _startRow = -1; private int _endRow = -1; diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/GridResizer.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/GridResizer.xaml.cs index 291e2b84a8..787d921ef2 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/GridResizer.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/GridResizer.xaml.cs @@ -17,7 +17,13 @@ namespace FancyZonesEditor { private static readonly RotateTransform _rotateTransform = new RotateTransform(90, 24, 24); - public int Index { get; set; } + public int StartRow { get; set; } + + public int EndRow { get; set; } + + public int StartCol { get; set; } + + public int EndCol { get; set; } public LayoutModel Model { get; set; } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/GridLayoutModel.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/GridLayoutModel.cs index 806fd7c5af..6421115e61 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/GridLayoutModel.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/GridLayoutModel.cs @@ -60,10 +60,10 @@ namespace FancyZonesEditor.Models public int[,] CellChildMap { get; set; } // RowPercents - represents the %age height of each row in the grid - public int[] RowPercents { get; set; } + public List RowPercents { get; set; } // ColumnPercents - represents the %age width of each column in the grid - public int[] ColumnPercents { get; set; } + public List ColumnPercents { get; set; } // FreeZones (not persisted) - used to keep track of child indices that are no longer in use in the CellChildMap, // making them candidates for re-use when it's needed to add another child @@ -85,7 +85,7 @@ namespace FancyZonesEditor.Models { } - public GridLayoutModel(string uuid, string name, LayoutType type, int rows, int cols, int[] rowPercents, int[] colsPercents, int[,] cellChildMap) + public GridLayoutModel(string uuid, string name, LayoutType type, int rows, int cols, List rowPercents, List colsPercents, int[,] cellChildMap) : base(uuid, name, type) { _rows = rows; @@ -103,16 +103,16 @@ namespace FancyZonesEditor.Models Rows = data[i++]; Columns = data[i++]; - RowPercents = new int[Rows]; + RowPercents = new List(Rows); for (int row = 0; row < Rows; row++) { - RowPercents[row] = (data[i++] * 256) + data[i++]; + RowPercents.Add((data[i++] * 256) + data[i++]); } - ColumnPercents = new int[Columns]; + ColumnPercents = new List(Columns); for (int col = 0; col < Columns; col++) { - ColumnPercents[col] = (data[i++] * 256) + data[i++]; + ColumnPercents.Add((data[i++] * 256) + data[i++]); } CellChildMap = new int[Rows, Columns]; @@ -154,18 +154,18 @@ namespace FancyZonesEditor.Models layout.CellChildMap = cellChildMap; - int[] rowPercents = new int[rows]; + List rowPercents = new List(rows); for (int row = 0; row < rows; row++) { - rowPercents[row] = RowPercents[row]; + rowPercents.Add(RowPercents[row]); } layout.RowPercents = rowPercents; - int[] colPercents = new int[cols]; + List colPercents = new List(cols); for (int col = 0; col < cols; col++) { - colPercents[col] = ColumnPercents[col]; + colPercents.Add(ColumnPercents[col]); } layout.ColumnPercents = colPercents; @@ -177,9 +177,9 @@ namespace FancyZonesEditor.Models public int Columns { get; set; } - public int[] RowsPercentage { get; set; } + public List RowsPercentage { get; set; } - public int[] ColumnsPercentage { get; set; } + public List ColumnsPercentage { get; set; } public int[][] CellChildMap { get; set; } } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs index d12034a881..24a07cccb7 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/LayoutModel.cs @@ -184,23 +184,22 @@ namespace FancyZonesEditor.Models { int rows = info.GetProperty("rows").GetInt32(); int columns = info.GetProperty("columns").GetInt32(); - int[] rowsPercentage = new int[rows]; + + List rowsPercentage = new List(rows); JsonElement.ArrayEnumerator rowsPercentageEnumerator = info.GetProperty("rows-percentage").EnumerateArray(); - int i = 0; while (rowsPercentageEnumerator.MoveNext()) { - rowsPercentage[i++] = rowsPercentageEnumerator.Current.GetInt32(); + rowsPercentage.Add(rowsPercentageEnumerator.Current.GetInt32()); } - i = 0; - int[] columnsPercentage = new int[columns]; + List columnsPercentage = new List(columns); JsonElement.ArrayEnumerator columnsPercentageEnumerator = info.GetProperty("columns-percentage").EnumerateArray(); while (columnsPercentageEnumerator.MoveNext()) { - columnsPercentage[i++] = columnsPercentageEnumerator.Current.GetInt32(); + columnsPercentage.Add(columnsPercentageEnumerator.Current.GetInt32()); } - i = 0; + int i = 0; JsonElement.ArrayEnumerator cellChildMapRows = info.GetProperty("cell-child-map").EnumerateArray(); int[,] cellChildMap = new int[rows, columns]; while (cellChildMapRows.MoveNext()) diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs index 617f505245..1d73e01770 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/Settings.cs @@ -133,14 +133,14 @@ namespace FancyZonesEditor _columnsModel = new GridLayoutModel("Columns", LayoutType.Columns) { Rows = 1, - RowPercents = new int[1] { _multiplier }, + RowPercents = new List(1) { _multiplier }, }; DefaultModels.Add(_columnsModel); _rowsModel = new GridLayoutModel("Rows", LayoutType.Rows) { Columns = 1, - ColumnPercents = new int[1] { _multiplier }, + ColumnPercents = new List(1) { _multiplier }, }; DefaultModels.Add(_rowsModel); @@ -308,7 +308,7 @@ namespace FancyZonesEditor _rowsModel.CellChildMap = new int[ZoneCount, 1]; _columnsModel.CellChildMap = new int[1, ZoneCount]; _rowsModel.Rows = _columnsModel.Columns = ZoneCount; - _rowsModel.RowPercents = _columnsModel.ColumnPercents = new int[ZoneCount]; + _rowsModel.RowPercents = _columnsModel.ColumnPercents = new List(ZoneCount); for (int i = 0; i < ZoneCount; i++) { @@ -318,7 +318,7 @@ namespace FancyZonesEditor // Note: This is NOT equal to _multiplier / ZoneCount and is done like this to make // the sum of all RowPercents exactly (_multiplier). // _columnsModel is sharing the same array - _rowsModel.RowPercents[i] = ((_multiplier * (i + 1)) / ZoneCount) - ((_multiplier * i) / ZoneCount); + _rowsModel.RowPercents.Add(((_multiplier * (i + 1)) / ZoneCount) - ((_multiplier * i) / ZoneCount)); } // Update the "Grid" Default Layout @@ -341,20 +341,20 @@ namespace FancyZonesEditor _gridModel.Rows = rows; _gridModel.Columns = cols; - _gridModel.RowPercents = new int[rows]; - _gridModel.ColumnPercents = new int[cols]; + _gridModel.RowPercents = new List(rows); + _gridModel.ColumnPercents = new List(cols); _gridModel.CellChildMap = new int[rows, cols]; // Note: The following are NOT equal to _multiplier divided by rows or columns and is // done like this to make the sum of all RowPercents exactly (_multiplier). for (int row = 0; row < rows; row++) { - _gridModel.RowPercents[row] = ((_multiplier * (row + 1)) / rows) - ((_multiplier * row) / rows); + _gridModel.RowPercents.Add(((_multiplier * (row + 1)) / rows) - ((_multiplier * row) / rows)); } for (int col = 0; col < cols; col++) { - _gridModel.ColumnPercents[col] = ((_multiplier * (col + 1)) / cols) - ((_multiplier * col) / cols); + _gridModel.ColumnPercents.Add(((_multiplier * (col + 1)) / cols) - ((_multiplier * col) / cols)); } int index = ZoneCount - 1; diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/RowColInfo.cs b/src/modules/fancyzones/editor/FancyZonesEditor/RowColInfo.cs index 861bd0f53a..fd700f581c 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/RowColInfo.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/RowColInfo.cs @@ -21,6 +21,14 @@ namespace FancyZonesEditor Percent = percent; } + public RowColInfo(RowColInfo other) + { + Percent = other.Percent; + Extent = other.Extent; + Start = other.Start; + End = other.End; + } + public RowColInfo(int index, int count) { Percent = (_multiplier / count) + ((index == 0) ? (_multiplier % count) : 0);