From e586a7ad64343d072d8af09471c7ad087dc3cbc9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ivan=20Sto=C5=A1i=C4=87?= Date: Wed, 10 Mar 2021 13:22:19 +0100 Subject: [PATCH] [FancyZones] Rework grid editor (#10116) * Started rewriting * Making progress * Fix resizers not moving around * Implemented splitting, fixed some bugs * Removed more code, renamed methods * Merging zones works * Fix Shift key behavior * Added spacing (has bugs) * Implement minimum size restriction * Match preview and editor visuals * Snapping works * Show when splitting is not possible * Fix spell checker complaining * Tweak FZ Lib function computing grid zones * Fix potential crash when loading old zone layouts * Fix dead objects talking * Fix splitters being shown when they shouldn't be * Fix index numbering * Fix small glitch with the shift key * Do not snap to borders outside the zone --- .../editor/FancyZonesEditor/GridData.cs | 1411 ++++++----------- .../FancyZonesEditor/GridDragHandles.cs | 604 ------- .../FancyZonesEditor/GridEditor.xaml.cs | 722 ++++----- .../FancyZonesEditor/GridResizer.xaml.cs | 8 +- .../editor/FancyZonesEditor/GridZone.xaml.cs | 158 +- .../FancyZonesEditor/LayoutPreview.xaml.cs | 58 +- .../editor/FancyZonesEditor/MagneticSnap.cs | 79 + .../Models/GridLayoutModel.cs | 5 - .../editor/FancyZonesEditor/Overlay.cs | 41 - .../editor/FancyZonesEditor/RowColInfo.cs | 66 - .../editor/FancyZonesEditor/SplitEventArgs.cs | 7 +- src/modules/fancyzones/lib/ZoneSet.cpp | 17 +- 12 files changed, 897 insertions(+), 2279 deletions(-) delete mode 100644 src/modules/fancyzones/editor/FancyZonesEditor/GridDragHandles.cs create mode 100644 src/modules/fancyzones/editor/FancyZonesEditor/MagneticSnap.cs delete mode 100644 src/modules/fancyzones/editor/FancyZonesEditor/RowColInfo.cs diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/GridData.cs b/src/modules/fancyzones/editor/FancyZonesEditor/GridData.cs index 2fed5c8276..4031d80384 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/GridData.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/GridData.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Windows; using System.Windows.Controls; using FancyZonesEditor.Models; @@ -12,1058 +13,562 @@ namespace FancyZonesEditor { public class GridData { - // The sum of row/column percents should be equal to this number - private const int _multiplier = 10000; - - public class ResizeInfo + // result[k] is the sum of the first k elements of the given list. + public static List PrefixSum(List list) { - public ResizeInfo() + var result = new List(list.Count + 1); + result.Add(0); + + int sum = 0; + for (int i = 0; i < list.Count; i++) { + sum += list[i]; + result.Add(sum); } - public int NewPercent { get; set; } + return result; + } - 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 + // Opposite of PrefixSum, returns the list containing differences of consecutive elements + private static List AdjacentDifference(List list) + { + if (list.Count <= 1) { - get + return new List(); + } + + var result = new List(list.Count - 1); + + for (int i = 0; i < list.Count - 1; i++) + { + result.Add(list[i + 1] - list[i]); + } + + return result; + } + + // IEnumerable.Distinct does not guarantee the items will be returned in the same order. + // In addition, here each element of the list will occupy a contiguous segment, simplifying + // the implementation. + private static List Unique(List list) + { + var result = new List(); + + if (list.Count == 0) + { + return result; + } + + int last = list[0]; + result.Add(last); + + for (int i = 1; i < list.Count; i++) + { + if (list[i] != last) { - return (NewPercent > 0) && (NewPercent < TotalPercent); + last = list[i]; + result.Add(last); } } - public void CalcNewPercent(double delta) + return result; + } + + public struct Zone + { + public int Index; + public int Left; + public int Top; + public int Right; + public int Bottom; + } + + public struct Resizer + { + public Orientation Orientation; + public List NegativeSideIndices; // all zones to the left/up, in order + public List PositiveSideIndices; // all zones to the right/down, in order + } + + private List _zones; + private List _resizers; + + public int MinZoneWidth { get; set; } + + public int MinZoneHeight { get; set; } + + private GridLayoutModel _model; + + // The sum of row/column percents should be equal to this number + public const int Multiplier = 10000; + + private void ModelToZones(GridLayoutModel model) + { + int rows = model.Rows; + int cols = model.Columns; + + int zoneCount = 0; + + for (int row = 0; row < rows; row++) { - double newExtent = CurrentExtent + _adjacentExtent + delta; - NewPercent = (int)((CurrentPercent + AdjacentPercent) * newExtent / (CurrentExtent + _adjacentExtent)); - } - - public void CalcAdjacentZones(int index, int size, List info, Func indexCmpr) - { - if (info == null) + for (int col = 0; col < cols; col++) { - throw new ArgumentNullException(nameof(info)); - } - - if (indexCmpr == null) - { - throw new ArgumentNullException(nameof(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++; + zoneCount = Math.Max(zoneCount, model.CellChildMap[row, col]); } } - public void FixAccuracyError(List info, List percents, int index) + zoneCount++; + + if (zoneCount > rows * cols) { - int total = 0; - for (int i = 0; i < info.Count; i++) + throw new ArgumentException("Invalid index found in model.CellChildMap"); + } + + var indexCount = Enumerable.Repeat(0, zoneCount).ToList(); + var indexRowLow = Enumerable.Repeat(int.MaxValue, zoneCount).ToList(); + var indexRowHigh = Enumerable.Repeat(0, zoneCount).ToList(); + var indexColLow = Enumerable.Repeat(int.MaxValue, zoneCount).ToList(); + var indexColHigh = Enumerable.Repeat(0, zoneCount).ToList(); + + for (int row = 0; row < rows; row++) + { + for (int col = 0; col < cols; col++) { - total += info[i].Percent; + int index = model.CellChildMap[row, col]; + indexCount[index]++; + indexRowLow[index] = Math.Min(indexRowLow[index], row); + indexColLow[index] = Math.Min(indexColLow[index], col); + indexRowHigh[index] = Math.Max(indexRowHigh[index], row); + indexColHigh[index] = Math.Max(indexColHigh[index], col); + } + } + + for (int index = 0; index < zoneCount; index++) + { + if (indexCount[index] == 0) + { + throw new ArgumentException("Indices in model.CellChildMap are not contiguous"); } - int diff = total - _multiplier; - if (diff != 0) + if (indexCount[index] != (indexRowHigh[index] - indexRowLow[index] + 1) * (indexColHigh[index] - indexColLow[index] + 1)) { - TotalPercent -= diff; + throw new ArgumentException("One or more indices in model.CellChildMap don't form a rectangle"); + } + } - while (index >= info.Count) + if (model.RowPercents.Count != rows) + { + throw new ArgumentException("model.RowPercents has invalid size"); + } + + if (model.ColumnPercents.Count != cols) + { + throw new ArgumentException("model.ColumnPercents has invalid size"); + } + + if (model.RowPercents.Exists((x) => (x < 1))) + { + throw new ArgumentException("Invalid value in model.RowPercents"); + } + + if (model.ColumnPercents.Exists((x) => (x < 1))) + { + throw new ArgumentException("Invalid value in model.ColumnPercents"); + } + + var rowPrefixSum = PrefixSum(model.RowPercents); + var colPrefixSum = PrefixSum(model.ColumnPercents); + + if (rowPrefixSum[rows] != Multiplier || colPrefixSum[cols] != Multiplier) + { + throw new ArgumentException(); + } + + _zones = new List(zoneCount); + + for (int index = 0; index < zoneCount; index++) + { + _zones.Add(new Zone + { + Index = index, + Left = colPrefixSum[indexColLow[index]], + Right = colPrefixSum[indexColHigh[index] + 1], + Top = rowPrefixSum[indexRowLow[index]], + Bottom = rowPrefixSum[indexRowHigh[index] + 1], + }); + } + } + + private void ModelToResizers(GridLayoutModel model) + { + // Short name, to avoid clutter + var grid = model.CellChildMap; + + int rows = model.Rows; + int cols = model.Columns; + + _resizers = new List(); + + // Horizontal + for (int row = 1; row < rows; row++) + { + for (int startCol = 0; startCol < cols;) + { + if (grid[row - 1, startCol] != grid[row, startCol]) { - index--; - } + int endCol = startCol; + while (endCol + 1 < cols && grid[row - 1, endCol + 1] != grid[row, endCol + 1]) + { + endCol++; + } - info[index].Percent -= diff; - percents[index] -= diff; + var resizer = default(Resizer); + resizer.Orientation = Orientation.Horizontal; + var positive = new List(); + var negative = new List(); + + for (int col = startCol; col <= endCol; col++) + { + negative.Add(grid[row - 1, col]); + positive.Add(grid[row, col]); + } + + resizer.PositiveSideIndices = Unique(positive); + resizer.NegativeSideIndices = Unique(negative); + + _resizers.Add(resizer); + + startCol = endCol + 1; + } + else + { + startCol++; + } } } - private double _adjacentExtent; + // Vertical + for (int col = 1; col < cols; col++) + { + for (int startRow = 0; startRow < rows;) + { + if (grid[startRow, col - 1] != grid[startRow, col]) + { + int endRow = startRow; + while (endRow + 1 < rows && grid[endRow + 1, col - 1] != grid[endRow + 1, col]) + { + endRow++; + } + + var resizer = default(Resizer); + resizer.Orientation = Orientation.Vertical; + var positive = new List(); + var negative = new List(); + + for (int row = startRow; row <= endRow; row++) + { + negative.Add(grid[row, col - 1]); + positive.Add(grid[row, col]); + } + + resizer.PositiveSideIndices = Unique(positive); + resizer.NegativeSideIndices = Unique(negative); + + _resizers.Add(resizer); + + startRow = endRow + 1; + } + else + { + startRow++; + } + } + } + } + + private void FromModel(GridLayoutModel model) + { + ModelToZones(model); + ModelToResizers(model); } public GridData(GridLayoutModel model) { _model = model; - int rows = model.Rows; - int cols = model.Columns; + MinZoneWidth = 1; + MinZoneHeight = 1; + FromModel(model); + } - _rowInfo = new List(rows); - for (int row = 0; row < rows; row++) - { - _rowInfo.Add(new RowColInfo(model.RowPercents[row])); - } + public IReadOnlyList Zones + { + get { return _zones; } + } - _colInfo = new List(cols); - for (int col = 0; col < cols; col++) - { - _colInfo.Add(new RowColInfo(model.ColumnPercents[col])); - } + public IReadOnlyList Resizers + { + get { return _resizers; } + } - int maxIndex = 0; - for (int row = 0; row < _model.Rows; row++) + // Converts the known list of zones from _zones to the given model. Ignores Zone.Index, so these indices can also be invalid. + private void ZonesToModel(GridLayoutModel model) + { + var xCoords = _zones.Select((zone) => zone.Right).Concat(_zones.Select((zone) => zone.Left)).Distinct().OrderBy((x) => x).ToList(); + var yCoords = _zones.Select((zone) => zone.Top).Concat(_zones.Select((zone) => zone.Bottom)).Distinct().OrderBy((x) => x).ToList(); + + model.Rows = yCoords.Count - 1; + model.Columns = xCoords.Count - 1; + model.RowPercents = AdjacentDifference(yCoords); + model.ColumnPercents = AdjacentDifference(xCoords); + model.CellChildMap = new int[model.Rows, model.Columns]; + + for (int index = 0; index < _zones.Count; index++) { - for (int col = 0; col < _model.Columns; col++) + Zone zone = _zones[index]; + int startRow = yCoords.IndexOf(zone.Top); + int endRow = yCoords.IndexOf(zone.Bottom); + int startCol = xCoords.IndexOf(zone.Left); + int endCol = xCoords.IndexOf(zone.Right); + + for (int row = startRow; row < endRow; row++) { - maxIndex = Math.Max(maxIndex, _model.CellChildMap[row, col]); + for (int col = startCol; col < endCol; col++) + { + model.CellChildMap[row, col] = index; + } } } - - _zoneCount = maxIndex + 1; } - public int ZoneCount + // Returns a tuple consisting of the list of indices and the Zone which should replace the zones to be merged. + private Tuple, Zone> ComputeClosure(List indices) { - get + // First, find the minimum bounding rectangle which doesn't intersect any zone + int left = int.MaxValue; + int right = int.MinValue; + int top = int.MaxValue; + int bottom = int.MinValue; + + if (indices.Count == 0) { - return _zoneCount; - } - } - - private int _zoneCount; - - 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++) + return new Tuple, Zone>(new List(), new Zone { - if (_model.CellChildMap[row, col] == index) + Index = -1, + Left = left, + Right = right, + Top = top, + Bottom = bottom, + }); + } + + Action extend = (zone) => + { + left = Math.Min(left, zone.Left); + right = Math.Max(right, zone.Right); + top = Math.Min(top, zone.Top); + bottom = Math.Max(bottom, zone.Bottom); + }; + + foreach (Index index in indices) + { + extend(_zones[index]); + } + + bool possiblyBroken = true; + while (possiblyBroken) + { + possiblyBroken = false; + foreach (Zone zone in _zones) + { + int area = (zone.Bottom - zone.Top) * (zone.Right - zone.Left); + + int cutLeft = Math.Max(left, zone.Left); + int cutRight = Math.Min(right, zone.Right); + int cutTop = Math.Max(top, zone.Top); + int cutBottom = Math.Min(bottom, zone.Bottom); + + int newArea = Math.Max(0, cutBottom - cutTop) * Math.Max(0, cutRight - cutLeft); + + if (newArea != 0 && newArea != area) { - foundRow = row; - foundCol = col; + // bad intersection found, extend + extend(zone); + possiblyBroken = true; } } } - 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++) + // Pick zones which are inside this area + var resultIndices = _zones.FindAll((zone) => { - 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]; - } - } + bool inside = true; + inside &= left <= zone.Left && zone.Right <= right; + inside &= top <= zone.Top && zone.Bottom <= bottom; + return inside; + }).Select((zone) => zone.Index).ToList(); - if (col != foundCol) - { - sourceCol++; - } + return new Tuple, Zone>(resultIndices, new Zone + { + Index = -1, + Left = left, + Right = right, + Top = top, + Bottom = bottom, + }); + } + + public List MergeClosureIndices(List indices) + { + return ComputeClosure(indices).Item1; + } + + public void DoMerge(List indices) + { + if (indices.Count == 0) + { + return; } - RowColInfo[] split = _colInfo[foundCol].Split(offset, space); + int lowestIndex = indices.Min(); - _colInfo[foundCol] = split[0]; - _colInfo.Insert(foundCol + 1, split[1]); + // make sure the set of indices is closed. + var closure = ComputeClosure(indices); + var closureIndices = closure.Item1.ToHashSet(); + Zone closureZone = closure.Item2; - _model.ColumnPercents[foundCol] = split[0].Percent; - _model.ColumnPercents.Insert(foundCol + 1, split[1].Percent); + // Erase zones with these indices + _zones = _zones.FindAll((zone) => !closureIndices.Contains(zone.Index)).ToList(); - double newTotalExtent = actualWidth - (space * (cols + 1)); - for (int col = 0; col < cols; col++) - { - if (col != foundCol && col != foundCol + 1) - { - _colInfo[col].RecalculatePercent(newTotalExtent); - } - } + _zones.Insert(lowestIndex, closureZone); - FixAccuracyError(_colInfo, _model.ColumnPercents); - _model.CellChildMap = newCellChildMap; - _model.Columns++; - _model.UpdatePreview(); + // Restore invariants + ZonesToModel(_model); + FromModel(_model); } - public void SplitRow(int foundRow, int spliteeIndex, int newChildIndex, double space, double offset, double actualHeight) + public bool CanSplit(int zoneIndex, int position, Orientation orientation) { - int rows = _model.Rows + 1; - int cols = _model.Columns; + Zone zone = _zones[zoneIndex]; - int[,] cellChildMap = _model.CellChildMap; - int[,] newCellChildMap = new int[rows, cols]; - - int sourceRow = 0; - for (int row = 0; row < rows; row++) + if (orientation == Orientation.Horizontal) { - 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++; - _model.UpdatePreview(); - } - - 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++; + return zone.Top + MinZoneHeight <= position && position <= zone.Bottom - MinZoneHeight; } 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 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++; - } - } - - 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++; - } - - _model.UpdatePreview(); - } - - 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; + return zone.Left + MinZoneWidth <= position && position <= zone.Right - MinZoneWidth; } } - public void ArrangeZones(UIElementCollection zones, int spacing) + public void Split(int zoneIndex, int position, Orientation orientation) { - if (zones.Count == 0) + if (!CanSplit(zoneIndex, position, orientation)) { return; } - int rows = _model.Rows; - int cols = _model.Columns; - int[,] cells = _model.CellChildMap; + Zone zone = _zones[zoneIndex]; + Zone zone1 = zone; + Zone zone2 = zone; - if (cells.Length < rows * cols) + _zones.RemoveAt(zoneIndex); + + if (orientation == Orientation.Horizontal) + { + zone1.Bottom = position; + zone2.Top = position; + } + else + { + zone1.Right = position; + zone2.Left = position; + } + + _zones.Insert(zoneIndex, zone1); + _zones.Insert(zoneIndex + 1, zone2); + + // Restore invariants + ZonesToModel(_model); + FromModel(_model); + } + + // Check if some zone becomes too small when the resizer is dragged by amount delta + public bool CanDrag(int resizerIndex, int delta) + { + var resizer = _resizers[resizerIndex]; + + Func getSize = (zoneIndex) => + { + Zone zone = _zones[zoneIndex]; + return resizer.Orientation == Orientation.Vertical ? zone.Right - zone.Left : zone.Bottom - zone.Top; + }; + + int minZoneSize = resizer.Orientation == Orientation.Vertical ? MinZoneWidth : MinZoneHeight; + + foreach (int zoneIndex in resizer.PositiveSideIndices) + { + if (getSize(zoneIndex) - delta < minZoneSize) + { + return false; + } + } + + foreach (int zoneIndex in resizer.NegativeSideIndices) + { + if (getSize(zoneIndex) + delta < minZoneSize) + { + return false; + } + } + + return true; + } + + public void Drag(int resizerIndex, int delta) + { + if (!CanDrag(resizerIndex, delta)) { - // Merge was not finished yet, rows and cols values are invalid return; } - double left, top; - for (int row = 0; row < rows; row++) + var resizer = _resizers[resizerIndex]; + + foreach (int zoneIndex in resizer.PositiveSideIndices) { - 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; + var zone = _zones[zoneIndex]; - 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); - } + zone.Top += delta; } 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); - } + zone.Left += delta; } - } - } - 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); + _zones[zoneIndex] = zone; } - 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) + foreach (int zoneIndex in resizer.NegativeSideIndices) { - info = _colInfo; - percents = _model.ColumnPercents; - index = resizer.StartCol; - } - else - { - info = _rowInfo; - percents = _model.RowPercents; - index = resizer.StartRow; - } + var zone = _zones[zoneIndex]; - 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 = () => + if (resizer.Orientation == Orientation.Horizontal) { - 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]; + zone.Bottom += delta; } else { - _rowInfo[index].Percent = percents[index]; - _rowInfo[index - 1].Percent = percents[index - 1]; + zone.Right += delta; } - return true; + _zones[zoneIndex] = zone; } - 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; + // Restore invariants + ZonesToModel(_model); + FromModel(_model); } - - 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; - _model.UpdatePreview(); - } - - private void FixAccuracyError(List info, List percents) - { - int total = 0; - for (int i = 0; i < info.Count; i++) - { - total += info[i].Percent; - } - - int prefixTotal = 0; - for (int i = 0; i < percents.Count; i++) - { - int first = prefixTotal * _multiplier / total; - prefixTotal += info[i].Percent; - int last = prefixTotal * _multiplier / total; - percents[i] = info[i].Percent = last - first; - } - } - - 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 deleted file mode 100644 index c126f6c1df..0000000000 --- a/src/modules/fancyzones/editor/FancyZonesEditor/GridDragHandles.cs +++ /dev/null @@ -1,604 +0,0 @@ -// 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 HasSnappedNonAdjacentResizers(GridResizer resizer) - { - /** - * Resizers between zones 0,1 and 4,5 are snapped to each other and not adjacent. - * ------------------------------ - * | 0 | 1 | - * ------------------------------ - * | 2 | 3 | - * ------------------------------ - * | 4 | 5 | - * ------------------------------ - * - * Resizers between zones 0,1 and 2,3 are snapped to each other and adjacent. - * ------------------------------ - * | 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 columns 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 6c5f6475a4..87c0b3e1e9 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/GridEditor.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/GridEditor.xaml.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; +using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Input; @@ -20,6 +21,9 @@ namespace FancyZonesEditor private const string PropertyRowsChangedID = "Rows"; private const string PropertyColumnsChangedID = "Columns"; private const string ObjectDependencyID = "Model"; + private const string PropertyIsShiftKeyPressedID = "IsShiftKeyPressed"; + + private const int MinZoneSize = 100; public static readonly DependencyProperty ModelProperty = DependencyProperty.Register(ObjectDependencyID, typeof(GridLayoutModel), typeof(GridEditor), new PropertyMetadata(null, OnGridDimensionsChanged)); @@ -27,13 +31,15 @@ namespace FancyZonesEditor private int gridEditorUniqueId; + private GridData _data; + public GridEditor() { InitializeComponent(); Loaded += GridEditor_Loaded; Unloaded += GridEditor_Unloaded; - ((App)Application.Current).MainWindowSettings.PropertyChanged += ZoneSettings_PropertyChanged; gridEditorUniqueId = ++gridEditorUniqueIdCounter; + ((App)Application.Current).MainWindowSettings.PropertyChanged += ZoneSettings_PropertyChanged; } private void GridEditor_Loaded(object sender, RoutedEventArgs e) @@ -45,21 +51,127 @@ namespace FancyZonesEditor } _data = new GridData(model); - _dragHandles = new GridDragHandles(AdornerLayer.Children, Resizer_DragDelta, Resizer_DragCompleted); - _dragHandles.InitDragHandles(model); Model = model; Model.PropertyChanged += OnGridDimensionsChanged; + SetupUI(); + } - int zoneCount = _data.ZoneCount; - for (int i = 0; i < zoneCount; i++) + private void PlaceResizer(GridResizer resizerThumb) + { + var leftZone = Preview.Children[resizerThumb.LeftReferenceZone]; + var rightZone = Preview.Children[resizerThumb.RightReferenceZone]; + var topZone = Preview.Children[resizerThumb.TopReferenceZone]; + var bottomZone = Preview.Children[resizerThumb.BottomReferenceZone]; + + double left = Canvas.GetLeft(leftZone); + double right = Canvas.GetLeft(rightZone) + (rightZone as GridZone).MinWidth; + + double top = Canvas.GetTop(topZone); + double bottom = Canvas.GetTop(bottomZone) + (bottomZone as GridZone).MinHeight; + + double x = (left + right) / 2.0; + double y = (top + bottom) / 2.0; + + Canvas.SetLeft(resizerThumb, x - 24); + Canvas.SetTop(resizerThumb, y - 24); + } + + private void SetZonePanelSize(GridZone panel, GridData.Zone zone) + { + Size actualSize = WorkAreaSize(); + double spacing = Model.ShowSpacing ? Model.Spacing : 0; + + double topSpacing = zone.Top == 0 ? spacing : spacing / 2; + double bottomSpacing = zone.Bottom == GridData.Multiplier ? spacing : spacing / 2; + double leftSpacing = zone.Left == 0 ? spacing : spacing / 2; + double rightSpacing = zone.Right == GridData.Multiplier ? spacing : spacing / 2; + + Canvas.SetTop(panel, (actualSize.Height * zone.Top / GridData.Multiplier) + topSpacing); + Canvas.SetLeft(panel, (actualSize.Width * zone.Left / GridData.Multiplier) + leftSpacing); + panel.MinWidth = Math.Max(1, (actualSize.Width * (zone.Right - zone.Left) / GridData.Multiplier) - leftSpacing - rightSpacing); + panel.MinHeight = Math.Max(1, (actualSize.Height * (zone.Bottom - zone.Top) / GridData.Multiplier) - topSpacing - bottomSpacing); + } + + private void SetupUI() + { + Size actualSize = WorkAreaSize(); + + if (actualSize.Width < 1 || _data == null || Model == null) { - AddZone(); + return; } - Rect workingArea = App.Overlay.WorkArea; - Size actualSize = new Size(workingArea.Width, workingArea.Height); - ArrangeGridRects(actualSize); + int spacing = Model.ShowSpacing ? Model.Spacing : 0; + + _data.MinZoneWidth = Convert.ToInt32(GridData.Multiplier / actualSize.Width * (MinZoneSize + (2 * spacing))); + _data.MinZoneHeight = Convert.ToInt32(GridData.Multiplier / actualSize.Height * (MinZoneSize + (2 * spacing))); + + Preview.Children.Clear(); + AdornerLayer.Children.Clear(); + + Preview.Width = actualSize.Width; + Preview.Height = actualSize.Height; + + MagneticSnap snapX = new MagneticSnap(GridData.PrefixSum(Model.ColumnPercents).GetRange(1, Model.ColumnPercents.Count - 1), actualSize.Width); + MagneticSnap snapY = new MagneticSnap(GridData.PrefixSum(Model.RowPercents).GetRange(1, Model.RowPercents.Count - 1), actualSize.Height); + + for (int zoneIndex = 0; zoneIndex < _data.Zones.Count(); zoneIndex++) + { + // this is needed for the lambda + int zoneIndexCopy = zoneIndex; + + var zone = _data.Zones[zoneIndex]; + var zonePanel = new GridZone(spacing, snapX, snapY, (orientation, offset) => _data.CanSplit(zoneIndexCopy, offset, orientation), zone); + zonePanel.UpdateShiftState(((App)Application.Current).MainWindowSettings.IsShiftKeyPressed); + Preview.Children.Add(zonePanel); + zonePanel.Split += OnSplit; + zonePanel.MergeDrag += OnMergeDrag; + zonePanel.MergeComplete += OnMergeComplete; + SetZonePanelSize(zonePanel, zone); + zonePanel.LabelID.Content = zoneIndex + 1; + } + + foreach (var resizer in _data.Resizers) + { + var resizerThumb = new GridResizer(); + resizerThumb.DragStarted += Resizer_DragStarted; + resizerThumb.DragDelta += Resizer_DragDelta; + resizerThumb.DragCompleted += Resizer_DragCompleted; + resizerThumb.Orientation = resizer.Orientation; + AdornerLayer.Children.Add(resizerThumb); + + if (resizer.Orientation == Orientation.Horizontal) + { + resizerThumb.LeftReferenceZone = resizer.PositiveSideIndices[0]; + resizerThumb.RightReferenceZone = resizer.PositiveSideIndices.Last(); + resizerThumb.TopReferenceZone = resizer.PositiveSideIndices[0]; + resizerThumb.BottomReferenceZone = resizer.NegativeSideIndices[0]; + } + else + { + resizerThumb.LeftReferenceZone = resizer.PositiveSideIndices[0]; + resizerThumb.RightReferenceZone = resizer.NegativeSideIndices[0]; + resizerThumb.TopReferenceZone = resizer.PositiveSideIndices[0]; + resizerThumb.BottomReferenceZone = resizer.PositiveSideIndices.Last(); + } + + PlaceResizer(resizerThumb); + } + } + + private void OnSplit(object sender, SplitEventArgs args) + { + MergeCancelClick(null, null); + + var zonePanel = sender as GridZone; + int zoneIndex = Preview.Children.IndexOf(zonePanel); + + if (_data.CanSplit(zoneIndex, args.Offset, args.Orientation)) + { + _data.Split(zoneIndex, args.Offset, args.Orientation); + SetupUI(); + } } private void GridEditor_Unloaded(object sender, RoutedEventArgs e) @@ -67,16 +179,10 @@ namespace FancyZonesEditor gridEditorUniqueId = -1; } - private void ZoneSettings_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + private Size WorkAreaSize() { Rect workingArea = App.Overlay.WorkArea; - Size actualSize = new Size(workingArea.Width, workingArea.Height); - - // Only enter if this is the newest instance - if (actualSize.Width > 0 && gridEditorUniqueId == gridEditorUniqueIdCounter) - { - ArrangeGridRects(actualSize); - } + return new Size(workingArea.Width, workingArea.Height); } public GridLayoutModel Model @@ -90,258 +196,6 @@ namespace FancyZonesEditor get { return Preview; } } - private void OnFullSplit(object o, SplitEventArgs e) - { - UIElementCollection previewChildren = Preview.Children; - UIElement splitee = (UIElement)o; - - GridLayoutModel model = Model; - int spliteeIndex = previewChildren.IndexOf(splitee); - - int rows = model.Rows; - int cols = model.Columns; - _startRow = -1; - _startCol = -1; - - for (int row = rows - 1; row >= 0; row--) - { - for (int col = cols - 1; col >= 0; col--) - { - if (model.CellChildMap[row, col] == spliteeIndex) - { - _dragHandles.RemoveDragHandles(); - _startRow = _endRow = row; - _startCol = _endCol = col; - ExtendRangeToHaveEvenCellEdges(); - - for (row = _startRow; row <= _endRow; row++) - { - for (col = _startCol; col <= _endCol; col++) - { - if ((row != _startRow) || (col != _startCol)) - { - model.CellChildMap[row, col] = AddZone(); - } - } - } - - OnGridDimensionsChanged(); - return; - } - } - } - } - - private void ExtendRangeToHaveEvenCellEdges() - { - // As long as there is an edge of the 2D range such that some zone crosses its boundary, extend - // that boundary. A single pass is not enough, a while loop is needed. This results in the unique - // smallest rectangle containing the initial range such that no zone is "broken", meaning that - // some part of it is inside the 2D range, and some part is outside. - GridLayoutModel model = Model; - bool possiblyBroken = true; - - while (possiblyBroken) - { - possiblyBroken = false; - - for (int col = _startCol; col <= _endCol; col++) - { - if (_startRow > 0 && model.CellChildMap[_startRow - 1, col] == model.CellChildMap[_startRow, col]) - { - _startRow--; - possiblyBroken = true; - break; - } - - if (_endRow < model.Rows - 1 && model.CellChildMap[_endRow + 1, col] == model.CellChildMap[_endRow, col]) - { - _endRow++; - possiblyBroken = true; - break; - } - } - - for (int row = _startRow; row <= _endRow; row++) - { - if (_startCol > 0 && model.CellChildMap[row, _startCol - 1] == model.CellChildMap[row, _startCol]) - { - _startCol--; - possiblyBroken = true; - break; - } - - if (_endCol < model.Columns - 1 && model.CellChildMap[row, _endCol + 1] == model.CellChildMap[row, _endCol]) - { - _endCol++; - possiblyBroken = true; - break; - } - } - } - } - - private void OnSplit(object o, SplitEventArgs e) - { - MergeCancelClick(null, null); - - UIElementCollection previewChildren = Preview.Children; - GridZone splitee = (GridZone)o; - - int spliteeIndex = previewChildren.IndexOf(splitee); - GridLayoutModel model = Model; - - int rows = model.Rows; - int cols = model.Columns; - - Tuple rowCol = _data.RowColByIndex(spliteeIndex); - int foundRow = rowCol.Item1; - int foundCol = rowCol.Item2; - - int newChildIndex = AddZone(); - - double offset = e.Offset; - double space = e.Space; - - if (e.Orientation == Orientation.Vertical) - { - if (splitee.VerticalSnapPoints != null) - { - 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) && (_data.GetIndex(walkRow, foundCol + i) == spliteeIndex)) - { - _data.SetIndex(walkRow++, foundCol + i, newChildIndex); - } - } - - 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 (_data.ColumnBottom(foundCol) < offset) - { - foundCol++; - } - - offset -= _data.ColumnTop(foundCol); - } - - _dragHandles.UpdateAfterVerticalSplit(foundCol); - _data.SplitColumn(foundCol, spliteeIndex, newChildIndex, space, offset, App.Overlay.WorkArea.Width); - _dragHandles.AddDragHandle(Orientation.Vertical, foundRow, foundCol, model); - } - else - { - // Horizontal - if (splitee.HorizontalSnapPoints != null) - { - 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) && (_data.GetIndex(foundRow + i, walkCol) == spliteeIndex)) - { - _data.SetIndex(foundRow + i, walkCol++, newChildIndex); - } - } - - 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 (_data.RowEnd(foundRow) < offset) - { - foundRow++; - } - - offset -= _data.RowStart(foundRow); - } - - _dragHandles.UpdateAfterHorizontalSplit(foundRow); - _data.SplitRow(foundRow, spliteeIndex, newChildIndex, space, offset, App.Overlay.WorkArea.Height); - _dragHandles.AddDragHandle(Orientation.Horizontal, foundRow, foundCol, model); - } - - var workArea = App.Overlay.WorkArea; - Size actualSize = new Size(workArea.Width, workArea.Height); - ArrangeGridRects(actualSize); - } - - private void DeleteZone(int index) - { - Preview.Children.RemoveAt(index); - } - - private int AddZone() - { - GridZone zone; - if (Model != null) - { - IList freeZones = Model.FreeZones; - - // first check free list - if (freeZones.Count > 0) - { - int freeIndex = freeZones[0]; - freeZones.RemoveAt(0); - zone = (GridZone)Preview.Children[freeIndex]; - zone.Visibility = Visibility.Visible; - return freeIndex; - } - - zone = new GridZone(Model.ShowSpacing ? Model.Spacing : 0); - zone.Split += OnSplit; - zone.MergeDrag += OnMergeDrag; - zone.MergeComplete += OnMergeComplete; - zone.FullSplit += OnFullSplit; - Preview.Children.Add(zone); - return Preview.Children.Count - 1; - } - - return 0; - } - private void OnGridDimensionsChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) { // Only enter if this is the newest instance @@ -351,230 +205,201 @@ namespace FancyZonesEditor } } + private void ZoneSettings_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + { + if ((e.PropertyName == PropertyIsShiftKeyPressedID) && gridEditorUniqueId == gridEditorUniqueIdCounter) + { + foreach (var child in Preview.Children) + { + var zone = child as GridZone; + zone.UpdateShiftState(((App)Application.Current).MainWindowSettings.IsShiftKeyPressed); + } + } + } + private static void OnGridDimensionsChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { - ((GridEditor)d).OnGridDimensionsChanged(); + ((GridEditor)d).SetupUI(); } private void OnGridDimensionsChanged() { - Rect workingArea = App.Overlay.WorkArea; - Size actualSize = new Size(workingArea.Width, workingArea.Height); - if (actualSize.Width > 0) - { - ArrangeGridRects(actualSize); - } + SetupUI(); } - private void ArrangeGridRects(Size arrangeSize) + private double _dragX = 0; + private double _dragY = 0; + + private void Resizer_DragStarted(object sender, System.Windows.Controls.Primitives.DragStartedEventArgs e) { - var workArea = App.Overlay.WorkArea; - Preview.Width = workArea.Width; - Preview.Height = workArea.Height; - - GridLayoutModel model = Model; - if (model == null || _data == null) - { - return; - } - - if (model.Rows != model.RowPercents.Count || model.Columns != model.ColumnPercents.Count) - { - // Merge was not finished - return; - } - - int spacing = model.ShowSpacing ? model.Spacing : 0; - - _data.RecalculateZones(spacing, arrangeSize); - _data.ArrangeZones(Preview.Children, spacing); - _dragHandles.InitDragHandles(model); - _data.ArrangeResizers(AdornerLayer.Children, spacing); + _dragX = 0; + _dragY = 0; } private void Resizer_DragDelta(object sender, System.Windows.Controls.Primitives.DragDeltaEventArgs e) { MergeCancelClick(null, null); - GridResizer resizer = (GridResizer)sender; + _dragX += e.HorizontalChange; + _dragY += e.VerticalChange; - double delta = (resizer.Orientation == Orientation.Vertical) ? e.HorizontalChange : e.VerticalChange; - if (delta == 0) + GridResizer resizer = (GridResizer)sender; + int resizerIndex = AdornerLayer.Children.IndexOf(resizer); + + Size actualSize = WorkAreaSize(); + + int delta; + + if (resizer.Orientation == Orientation.Vertical) { - return; + delta = Convert.ToInt32(_dragX / actualSize.Width * GridData.Multiplier); + } + else + { + delta = Convert.ToInt32(_dragY / actualSize.Height * GridData.Multiplier); } - GridData.ResizeInfo resizeInfo = _data.CalculateResizeInfo(resizer, delta); - if (resizeInfo.IsResizeAllowed) + if (_data.CanDrag(resizerIndex, delta)) { - if (_dragHandles.HasSnappedNonAdjacentResizers(resizer)) + // Just update the UI, don't tell _data + if (resizer.Orientation == Orientation.Vertical) { - double spacing = 0; - GridLayoutModel model = Model; - if (model.ShowSpacing) + _data.Resizers[resizerIndex].PositiveSideIndices.ForEach((zoneIndex) => { - spacing = model.Spacing; - } + var zone = Preview.Children[zoneIndex]; + Canvas.SetLeft(zone, Canvas.GetLeft(zone) + e.HorizontalChange); + (zone as GridZone).MinWidth -= e.HorizontalChange; + }); - _data.SplitOnDrag(resizer, delta, spacing); - _dragHandles.UpdateAfterDetach(resizer, delta); + _data.Resizers[resizerIndex].NegativeSideIndices.ForEach((zoneIndex) => + { + var zone = Preview.Children[zoneIndex]; + Canvas.SetRight(zone, Canvas.GetRight(zone) + e.HorizontalChange); + (zone as GridZone).MinWidth += e.HorizontalChange; + }); + + Canvas.SetLeft(resizer, Canvas.GetLeft(resizer) + e.HorizontalChange); } else { - _data.DragResizer(resizer, resizeInfo); - if (_data.SwapNegativePercents(resizer.Orientation, resizer.StartRow, resizer.EndRow, resizer.StartCol, resizer.EndCol)) + _data.Resizers[resizerIndex].PositiveSideIndices.ForEach((zoneIndex) => { - _dragHandles.UpdateAfterSwap(resizer, delta); + var zone = Preview.Children[zoneIndex]; + Canvas.SetTop(zone, Canvas.GetTop(zone) + e.VerticalChange); + (zone as GridZone).MinHeight -= e.VerticalChange; + }); + + _data.Resizers[resizerIndex].NegativeSideIndices.ForEach((zoneIndex) => + { + var zone = Preview.Children[zoneIndex]; + Canvas.SetBottom(zone, Canvas.GetBottom(zone) + e.VerticalChange); + (zone as GridZone).MinHeight += e.VerticalChange; + }); + + Canvas.SetTop(resizer, Canvas.GetTop(resizer) + e.VerticalChange); + } + + foreach (var child in AdornerLayer.Children) + { + GridResizer resizerThumb = child as GridResizer; + if (resizerThumb != resizer) + { + PlaceResizer(resizerThumb); } } } - - Rect workingArea = App.Overlay.WorkArea; - Size actualSize = new Size(workingArea.Width, workingArea.Height); - ArrangeGridRects(actualSize); - AdornerLayer.UpdateLayout(); + else + { + // Undo changes + _dragX -= e.HorizontalChange; + _dragY -= e.VerticalChange; + } } private void Resizer_DragCompleted(object sender, System.Windows.Controls.Primitives.DragCompletedEventArgs e) { GridResizer resizer = (GridResizer)sender; - int index = _data.SwappedIndexAfterResize(resizer); - if (index != -1) - { - Rect workingArea = App.Overlay.WorkArea; - Size actualSize = new Size(workingArea.Width, workingArea.Height); - ArrangeGridRects(actualSize); - } - } + int resizerIndex = AdornerLayer.Children.IndexOf(resizer); + Size actualSize = WorkAreaSize(); - private Point _startDragPos = new Point(-1, -1); + double pixelDelta = resizer.Orientation == Orientation.Vertical ? + _dragX / actualSize.Width * GridData.Multiplier : + _dragY / actualSize.Height * GridData.Multiplier; + + _data.Drag(resizerIndex, Convert.ToInt32(pixelDelta)); + + SetupUI(); + } private void OnMergeComplete(object o, MouseButtonEventArgs e) { - Point mousePoint = e.GetPosition(Preview); - _startDragPos = new Point(-1, -1); + _inMergeDrag = false; - int mergedIndex = Model.CellChildMap[_startRow, _startCol]; - - for (int row = _startRow; row <= _endRow; row++) + var selectedIndices = new List(); + for (int zoneIndex = 0; zoneIndex < _data.Zones.Count; zoneIndex++) { - for (int col = _startCol; col <= _endCol; col++) + if ((Preview.Children[zoneIndex] as GridZone).IsSelected) { - if (Model.CellChildMap[row, col] != mergedIndex) - { - // selection is more than one cell, merge is valid - MergePanel.Visibility = Visibility.Visible; - Canvas.SetTop(MergeButtons, mousePoint.Y); - Canvas.SetLeft(MergeButtons, mousePoint.X); - return; - } + selectedIndices.Add(zoneIndex); } } - // merge is only one zone. cancel merge; - ClearSelection(); + if (selectedIndices.Count <= 1) + { + ClearSelection(); + } + else + { + Point mousePoint = e.GetPosition(Preview); + MergePanel.Visibility = Visibility.Visible; + Canvas.SetLeft(MergeButtons, mousePoint.X); + Canvas.SetTop(MergeButtons, mousePoint.Y); + } } + private bool _inMergeDrag; + private Point _mergeDragStart; + private void OnMergeDrag(object o, MouseEventArgs e) { - if (_startDragPos.X == -1) + Point dragPosition = e.GetPosition(Preview); + Size actualSize = WorkAreaSize(); + + if (!_inMergeDrag) { - _startDragPos = e.GetPosition(Preview); + _inMergeDrag = true; + _mergeDragStart = dragPosition; } - GridLayoutModel model = Model; + // Find the new zone, if any + int dataLowX = Convert.ToInt32(Math.Min(_mergeDragStart.X, dragPosition.X) / actualSize.Width * GridData.Multiplier); + int dataHighX = Convert.ToInt32(Math.Max(_mergeDragStart.X, dragPosition.X) / actualSize.Width * GridData.Multiplier); + int dataLowY = Convert.ToInt32(Math.Min(_mergeDragStart.Y, dragPosition.Y) / actualSize.Height * GridData.Multiplier); + int dataHighY = Convert.ToInt32(Math.Max(_mergeDragStart.Y, dragPosition.Y) / actualSize.Height * GridData.Multiplier); - if (_startDragPos.X != -1) + var selectedIndices = new List(); + + for (int zoneIndex = 0; zoneIndex < _data.Zones.Count(); zoneIndex++) { - Point dragPos = e.GetPosition(Preview); - _startRow = -1; - _endRow = -1; - _startCol = -1; - _endCol = -1; + var zoneData = _data.Zones[zoneIndex]; - int rows = model.Rows; - int cols = model.Columns; + bool selected = Math.Max(zoneData.Left, dataLowX) <= Math.Min(zoneData.Right, dataHighX) && + Math.Max(zoneData.Top, dataLowY) <= Math.Min(zoneData.Bottom, dataHighY); - double minX, maxX; - if (dragPos.X < _startDragPos.X) + // Check whether the zone intersects the selected rectangle + (Preview.Children[zoneIndex] as GridZone).IsSelected = selected; + + if (selected) { - minX = dragPos.X; - maxX = _startDragPos.X; + selectedIndices.Add(zoneIndex); } - else - { - minX = _startDragPos.X; - maxX = dragPos.X; - } - - double minY, maxY; - if (dragPos.Y < _startDragPos.Y) - { - minY = dragPos.Y; - maxY = _startDragPos.Y; - } - else - { - minY = _startDragPos.Y; - maxY = dragPos.Y; - } - - for (int row = 0; row < rows; row++) - { - if (_startRow == -1) - { - if (_data.RowEnd(row) > minY) - { - _startRow = row; - } - } - else if (_data.RowStart(row) > maxY) - { - _endRow = row - 1; - break; - } - } - - if ((_startRow >= 0) && (_endRow == -1)) - { - _endRow = rows - 1; - } - - for (int col = 0; col < cols; col++) - { - if (_startCol == -1) - { - if (_data.ColumnBottom(col) > minX) - { - _startCol = col; - } - } - else if (_data.ColumnTop(col) > maxX) - { - _endCol = col - 1; - break; - } - } - - if ((_startCol >= 0) && (_endCol == -1)) - { - _endCol = cols - 1; - } - - ExtendRangeToHaveEvenCellEdges(); - - for (int row = 0; row < rows; row++) - { - for (int col = 0; col < cols; col++) - { - ((GridZone)Preview.Children[model.CellChildMap[row, col]]).IsSelected = (row >= _startRow) && (row <= _endRow) && (col >= _startCol) && (col <= _endCol); - } - } - - e.Handled = true; } - OnPreviewMouseMove(e); + // Compute the closure + _data.MergeClosureIndices(selectedIndices).ForEach((zoneIndex) => + { + (Preview.Children[zoneIndex] as GridZone).IsSelected = true; + }); } private void ClearSelection() @@ -583,22 +408,27 @@ namespace FancyZonesEditor { ((GridZone)zone).IsSelected = false; } + + _inMergeDrag = false; } private void MergeClick(object sender, RoutedEventArgs e) { MergePanel.Visibility = Visibility.Collapsed; - Action deleteAction = (index) => - { - DeleteZone(index); - }; - _data.MergeZones(_startRow, _endRow, _startCol, _endCol, deleteAction, Preview.Children.Count); - _dragHandles.RemoveDragHandles(); - _dragHandles.InitDragHandles(Model); + var selectedIndices = new List(); + + for (int zoneIndex = 0; zoneIndex < _data.Zones.Count(); zoneIndex++) + { + if ((Preview.Children[zoneIndex] as GridZone).IsSelected) + { + selectedIndices.Add(zoneIndex); + } + } - OnGridDimensionsChanged(); ClearSelection(); + _data.DoMerge(selectedIndices); + SetupUI(); } private void MergeCancelClick(object sender, RoutedEventArgs e) @@ -615,17 +445,9 @@ namespace FancyZonesEditor protected override Size ArrangeOverride(Size arrangeBounds) { Size returnSize = base.ArrangeOverride(arrangeBounds); - ArrangeGridRects(arrangeBounds); + SetupUI(); return returnSize; } - - private GridData _data; - private GridDragHandles _dragHandles; - - private int _startRow = -1; - private int _endRow = -1; - private int _startCol = -1; - private int _endCol = -1; } } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/GridResizer.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/GridResizer.xaml.cs index 333610070f..c3020464ae 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/GridResizer.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/GridResizer.xaml.cs @@ -17,13 +17,13 @@ namespace FancyZonesEditor { private static readonly RotateTransform _rotateTransform = new RotateTransform(90, 24, 24); - public int StartRow { get; set; } + public int LeftReferenceZone { get; set; } - public int EndRow { get; set; } + public int RightReferenceZone { get; set; } - public int StartCol { get; set; } + public int TopReferenceZone { get; set; } - public int EndCol { get; set; } + public int BottomReferenceZone { get; set; } public LayoutModel Model { get; set; } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/GridZone.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/GridZone.xaml.cs index 97cf91dbb4..c8eca9dbd8 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/GridZone.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/GridZone.xaml.cs @@ -20,28 +20,28 @@ namespace FancyZonesEditor // Non-localizable strings private const string ObjectDependencyID = "IsSelected"; private const string GridZoneBackgroundBrushID = "GridZoneBackgroundBrush"; - private const string PropertyIsShiftKeyPressedID = "IsShiftKeyPressed"; + private const string SecondaryForegroundBrushID = "SecondaryForegroundBrush"; public static readonly DependencyProperty IsSelectedProperty = DependencyProperty.Register(ObjectDependencyID, typeof(bool), typeof(GridZone), new PropertyMetadata(false, OnSelectionChanged)); public event SplitEventHandler Split; - public event SplitEventHandler FullSplit; - public event MouseEventHandler MergeDrag; public event MouseButtonEventHandler MergeComplete; - public double[] VerticalSnapPoints { get; set; } - - public double[] HorizontalSnapPoints { get; set; } - private readonly Rectangle _splitter; - private bool _switchOrientation; + private bool _switchOrientation = false; private Point _lastPos = new Point(-1, -1); + private int _snappedPositionX; + private int _snappedPositionY; private Point _mouseDownPos = new Point(-1, -1); private bool _inMergeDrag; - private Orientation _splitOrientation; + private MagneticSnap _snapX; + private MagneticSnap _snapY; + private Func _canSplit; + private bool _hovering; + private GridData.Zone _zone; private static void OnSelectionChanged(DependencyObject d, DependencyPropertyChangedEventArgs e) { @@ -59,7 +59,7 @@ namespace FancyZonesEditor set { SetValue(IsSelectedProperty, value); } } - public GridZone(int spacing) + public GridZone(int spacing, MagneticSnap snapX, MagneticSnap snapY, Func canSplit, GridData.Zone zone) { InitializeComponent(); OnSelectionChanged(); @@ -69,11 +69,14 @@ namespace FancyZonesEditor }; Body.Children.Add(_splitter); - Spacing = spacing; SplitterThickness = Math.Max(spacing, 1); - ((App)Application.Current).MainWindowSettings.PropertyChanged += ZoneSettings_PropertyChanged; SizeChanged += GridZone_SizeChanged; + + _snapX = snapX; + _snapY = snapY; + _canSplit = canSplit; + _zone = zone; } private void GridZone_SizeChanged(object sender, SizeChangedEventArgs e) @@ -82,91 +85,76 @@ namespace FancyZonesEditor HeightLabel.Text = Math.Round(ActualHeight).ToString(); } - private void ZoneSettings_PropertyChanged(object sender, System.ComponentModel.PropertyChangedEventArgs e) + public void UpdateShiftState(bool shiftState) { - if (e.PropertyName == PropertyIsShiftKeyPressedID) - { - _switchOrientation = ((App)Application.Current).MainWindowSettings.IsShiftKeyPressed; - if (_lastPos.X != -1) - { - UpdateSplitter(); - } - } - } + _switchOrientation = shiftState; - protected override Size ArrangeOverride(Size size) - { - _splitOrientation = (size.Width > size.Height) ? Orientation.Vertical : Orientation.Horizontal; - return base.ArrangeOverride(size); + if (_lastPos.X != -1) + { + UpdateSplitter(); + } } private bool IsVerticalSplit { - get - { - bool isVertical = _splitOrientation == Orientation.Vertical; - if (_switchOrientation) - { - isVertical = !isVertical; - } - - return isVertical; - } + get => (ActualWidth > ActualHeight) ^ _switchOrientation; } - private int Spacing { get; set; } - private int SplitterThickness { get; set; } private void UpdateSplitter() { + if (!_hovering) + { + _splitter.Fill = Brushes.Transparent; + return; + } + + bool enabled; + if (IsVerticalSplit) { double bodyWidth = Body.ActualWidth; - double pos = _lastPos.X - (SplitterThickness / 2); - if (pos < 0) - { - pos = 0; - } - else if (pos > (bodyWidth - SplitterThickness)) - { - pos = bodyWidth - SplitterThickness; - } + double pos = _snapX.DataToPixelWithoutSnapping(_snappedPositionX) - Canvas.GetLeft(this) - (SplitterThickness / 2); + pos = Math.Clamp(pos, 0, bodyWidth - SplitterThickness); Canvas.SetLeft(_splitter, pos); Canvas.SetTop(_splitter, 0); _splitter.MinWidth = SplitterThickness; _splitter.MinHeight = Body.ActualHeight; + + enabled = _canSplit(Orientation.Vertical, _snappedPositionX); } else { double bodyHeight = Body.ActualHeight; - double pos = _lastPos.Y - (SplitterThickness / 2); - if (pos < 0) - { - pos = 0; - } - else if (pos > (bodyHeight - SplitterThickness)) - { - pos = bodyHeight - SplitterThickness; - } + double pos = _snapY.DataToPixelWithoutSnapping(_snappedPositionY) - Canvas.GetTop(this) - (SplitterThickness / 2); + pos = Math.Clamp(pos, 0, bodyHeight - SplitterThickness); Canvas.SetLeft(_splitter, 0); Canvas.SetTop(_splitter, pos); _splitter.MinWidth = Body.ActualWidth; _splitter.MinHeight = SplitterThickness; + + enabled = _canSplit(Orientation.Horizontal, _snappedPositionY); } + + Brush disabledBrush = App.Current.Resources[SecondaryForegroundBrushID] as SolidColorBrush; + Brush enabledBrush = SystemParameters.WindowGlassBrush; // Active Accent color + _splitter.Fill = enabled ? enabledBrush : disabledBrush; } protected override void OnMouseEnter(MouseEventArgs e) { - _splitter.Fill = SystemParameters.WindowGlassBrush; // Active Accent color - base.OnMouseEnter(e); + _hovering = true; + UpdateSplitter(); + _splitter.Fill = SystemParameters.WindowGlassBrush; } protected override void OnMouseLeave(MouseEventArgs e) { - _splitter.Fill = Brushes.Transparent; + _hovering = false; + UpdateSplitter(); base.OnMouseLeave(e); } @@ -185,38 +173,8 @@ namespace FancyZonesEditor else { _lastPos = e.GetPosition(Body); - - if (IsVerticalSplit) - { - if (VerticalSnapPoints != null) - { - int thickness = SplitterThickness; - foreach (double snapPoint in VerticalSnapPoints) - { - if (Math.Abs(_lastPos.X - snapPoint) <= (thickness * 2)) - { - _lastPos.X = snapPoint; - break; - } - } - } - } - else - { - // horizontal split - if (HorizontalSnapPoints != null) - { - int thickness = SplitterThickness; - foreach (double snapPoint in HorizontalSnapPoints) - { - if (Math.Abs(_lastPos.Y - snapPoint) <= (thickness * 2)) - { - _lastPos.Y = snapPoint; - break; - } - } - } - } + _snappedPositionX = _snapX.PixelToDataWithSnapping(e.GetPosition(Parent as GridEditor).X, _zone.Left, _zone.Right); + _snappedPositionY = _snapY.PixelToDataWithSnapping(e.GetPosition(Parent as GridEditor).Y, _zone.Top, _zone.Bottom); if (_mouseDownPos.X == -1) { @@ -257,11 +215,11 @@ namespace FancyZonesEditor { if (IsVerticalSplit) { - DoSplit(Orientation.Vertical, _lastPos.X - (thickness / 2)); + DoSplit(Orientation.Vertical, _snappedPositionX); } else { - DoSplit(Orientation.Horizontal, _lastPos.Y - (thickness / 2)); + DoSplit(Orientation.Horizontal, _snappedPositionY); } } } @@ -280,19 +238,9 @@ namespace FancyZonesEditor MergeComplete?.Invoke(this, e); } - private void DoSplit(Orientation orientation, double offset) + private void DoSplit(Orientation orientation, int offset) { - Split?.Invoke(this, new SplitEventArgs(orientation, offset, Spacing)); - } - - private void FullSplit_Click(object sender, RoutedEventArgs e) - { - DoFullSplit(); - } - - private void DoFullSplit() - { - FullSplit?.Invoke(this, new SplitEventArgs()); + Split?.Invoke(this, new SplitEventArgs(orientation, offset)); } } } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/LayoutPreview.xaml.cs b/src/modules/fancyzones/editor/FancyZonesEditor/LayoutPreview.xaml.cs index 93099eedaf..a34ac77bf4 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/LayoutPreview.xaml.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/LayoutPreview.xaml.cs @@ -4,7 +4,6 @@ using System; using System.Collections.Generic; -using System.Linq; using System.Windows; using System.Windows.Controls; using System.Windows.Media; @@ -27,7 +26,6 @@ namespace FancyZonesEditor public static readonly DependencyProperty IsActualSizeProperty = DependencyProperty.Register(ObjectDependencyID, typeof(bool), typeof(LayoutPreview), new PropertyMetadata(false)); private LayoutModel _model; - private List _zones = new List(); public bool IsActualSize { @@ -82,11 +80,6 @@ namespace FancyZonesEditor RenderPreview(); } - public Int32Rect[] GetZoneRects() - { - return _zones.ToArray(); - } - private void OnLoaded(object sender, RoutedEventArgs e) { _model = (LayoutModel)DataContext; @@ -105,8 +98,6 @@ namespace FancyZonesEditor Body.RowDefinitions.Clear(); Body.ColumnDefinitions.Clear(); - _zones.Clear(); - if (_model is GridLayoutModel gridModel) { RenderGridPreview(gridModel); @@ -121,32 +112,12 @@ namespace FancyZonesEditor { int rows = grid.Rows; int cols = grid.Columns; + double spacing = grid.ShowSpacing ? grid.Spacing : 0; - RowColInfo[] rowInfo = (from percent in grid.RowPercents - select new RowColInfo(percent)).ToArray(); - - RowColInfo[] colInfo = (from percent in grid.ColumnPercents - select new RowColInfo(percent)).ToArray(); - - int spacing = grid.ShowSpacing ? grid.Spacing : 0; + var rowData = GridData.PrefixSum(grid.RowPercents); + var columnData = GridData.PrefixSum(grid.ColumnPercents); var workArea = App.Overlay.WorkArea; - double width = workArea.Width - (spacing * (cols + 1)); - double height = workArea.Height - (spacing * (rows + 1)); - - double top = spacing; - for (int row = 0; row < rows; row++) - { - double cellHeight = rowInfo[row].Recalculate(top, height); - top += cellHeight + spacing; - } - - double left = spacing; - for (int col = 0; col < cols; col++) - { - double cellWidth = colInfo[col].Recalculate(left, width); - left += cellWidth + spacing; - } Viewbox viewbox = new Viewbox { @@ -170,10 +141,8 @@ namespace FancyZonesEditor { // this is not a continuation of a span Border rect = new Border(); - left = colInfo[col].Start; - top = rowInfo[row].Start; - Canvas.SetTop(rect, top); - Canvas.SetLeft(rect, left); + double left = columnData[col] * workArea.Width / GridData.Multiplier; + double top = rowData[row] * workArea.Height / GridData.Multiplier; int maxRow = row; while (((maxRow + 1) < rows) && (grid.CellChildMap[maxRow + 1, col] == childIndex)) @@ -187,12 +156,21 @@ namespace FancyZonesEditor maxCol++; } - rect.Width = Math.Max(0, colInfo[maxCol].End - left); - rect.Height = Math.Max(0, rowInfo[maxRow].End - top); + double right = columnData[maxCol + 1] * workArea.Width / GridData.Multiplier; + double bottom = rowData[maxRow + 1] * workArea.Height / GridData.Multiplier; + + left += col == 0 ? spacing : spacing / 2; + right -= maxCol == cols - 1 ? spacing : spacing / 2; + top += row == 0 ? spacing : spacing / 2; + bottom -= maxRow == rows - 1 ? spacing : spacing / 2; + + Canvas.SetTop(rect, top); + Canvas.SetLeft(rect, left); + rect.Width = Math.Max(1, right - left); + rect.Height = Math.Max(1, bottom - top); + rect.Style = (Style)FindResource("GridLayoutActualScalePreviewStyle"); frame.Children.Add(rect); - _zones.Add(new Int32Rect( - (int)left, (int)top, (int)rect.Width, (int)rect.Height)); } } } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/MagneticSnap.cs b/src/modules/fancyzones/editor/FancyZonesEditor/MagneticSnap.cs new file mode 100644 index 0000000000..4dedb34d48 --- /dev/null +++ b/src/modules/fancyzones/editor/FancyZonesEditor/MagneticSnap.cs @@ -0,0 +1,79 @@ +// 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.Linq; + +namespace FancyZonesEditor +{ + public class MagneticSnap + { + private List _keyPoints; + private double _workAreaSize; + + private const int MagnetZoneMaxSize = GridData.Multiplier / 12; + + public MagneticSnap(List keyPoints, double workAreaSize) + { + _keyPoints = keyPoints; + _workAreaSize = workAreaSize; + } + + public int PixelToDataWithSnapping(double pixel, int low, int high) + { + var keyPoints = _keyPoints.Where(x => low < x && x < high).ToList(); + var magnetZoneSizes = new List(); + + for (int i = 0; i < keyPoints.Count; i++) + { + int previous = i == 0 ? low : keyPoints[i - 1]; + int next = i == keyPoints.Count - 1 ? high : keyPoints[i + 1]; + magnetZoneSizes.Add(Math.Min(keyPoints[i] - previous, Math.Min(next - keyPoints[i], MagnetZoneMaxSize)) / 2); + } + + int data = Convert.ToInt32(pixel / _workAreaSize * GridData.Multiplier); + data = Math.Clamp(data, low, high); + int result; + int snapId = -1; + + for (int i = 0; i < keyPoints.Count; ++i) + { + if (Math.Abs(data - keyPoints[i]) <= magnetZoneSizes[i]) + { + snapId = i; + break; + } + } + + if (snapId == -1) + { + result = data; + } + else + { + int deadZoneWidth = (magnetZoneSizes[snapId] + 1) / 2; + if (Math.Abs(data - keyPoints[snapId]) <= deadZoneWidth) + { + result = keyPoints[snapId]; + } + else if (data < keyPoints[snapId]) + { + result = data + (data - (keyPoints[snapId] - magnetZoneSizes[snapId])); + } + else + { + result = data - ((keyPoints[snapId] + magnetZoneSizes[snapId]) - data); + } + } + + return Math.Clamp(result, low, high); + } + + public double DataToPixelWithoutSnapping(int data) + { + return _workAreaSize * data / GridData.Multiplier; + } + } +} diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Models/GridLayoutModel.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Models/GridLayoutModel.cs index 495e8b481a..aae78af093 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Models/GridLayoutModel.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Models/GridLayoutModel.cs @@ -120,11 +120,6 @@ namespace FancyZonesEditor.Models private int _spacing = LayoutSettings.DefaultSpacing; - // 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 - // TODO: do I need FreeZones on the data model? - I think I do - public IList FreeZones { get; } = new List(); - public GridLayoutModel() : base() { diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/Overlay.cs b/src/modules/fancyzones/editor/FancyZonesEditor/Overlay.cs index ec0f2d2ab0..7588bfdaed 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/Overlay.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/Overlay.cs @@ -363,46 +363,5 @@ namespace FancyZonesEditor Monitors.Add(monitor); } } - - public Int32Rect[] GetZoneRects() - { - if (_editor != null) - { - if (_editor is GridEditor gridEditor) - { - return ZoneRectsFromPanel(gridEditor.PreviewPanel); - } - else - { - // CanvasEditor - return ZoneRectsFromPanel(((CanvasEditor)_editor).Preview); - } - } - else - { - // One of the predefined zones (neither grid or canvas editor used). - return _layoutPreview.GetZoneRects(); - } - } - - private Int32Rect[] ZoneRectsFromPanel(Panel previewPanel) - { - // TODO: the ideal here is that the ArrangeRects logic is entirely inside the model, so we don't have to walk the UIElement children to get the rect info - int count = previewPanel.Children.Count; - Int32Rect[] zones = new Int32Rect[count]; - - for (int i = 0; i < count; i++) - { - FrameworkElement child = (FrameworkElement)previewPanel.Children[i]; - Point topLeft = child.TransformToAncestor(previewPanel).Transform(default); - - zones[i].X = (int)topLeft.X; - zones[i].Y = (int)topLeft.Y; - zones[i].Width = (int)child.ActualWidth; - zones[i].Height = (int)child.ActualHeight; - } - - return zones; - } } } diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/RowColInfo.cs b/src/modules/fancyzones/editor/FancyZonesEditor/RowColInfo.cs deleted file mode 100644 index 32dbeb486a..0000000000 --- a/src/modules/fancyzones/editor/FancyZonesEditor/RowColInfo.cs +++ /dev/null @@ -1,66 +0,0 @@ -// 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. - -namespace FancyZonesEditor -{ - public class RowColInfo - { - private const int _multiplier = 10000; - - public double Extent { get; set; } - - public double Start { get; set; } - - public double End { get; set; } - - public int Percent { get; set; } - - public RowColInfo(int percent) - { - 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); - } - - public double Recalculate(double start, double totalExtent) - { - Start = start; - Extent = totalExtent * Percent / _multiplier; - End = Start + Extent; - return Extent; - } - - public void RecalculatePercent(double newTotalExtent) - { - Percent = (int)(Extent * _multiplier / newTotalExtent); - } - - public RowColInfo[] Split(double offset, double space) - { - RowColInfo[] info = new RowColInfo[2]; - - double totalExtent = Extent * _multiplier / Percent; - totalExtent -= space; - - int percent0 = (int)(offset * _multiplier / totalExtent); - int percent1 = (int)((Extent - space - offset) * _multiplier / totalExtent); - - info[0] = new RowColInfo(percent0); - info[1] = new RowColInfo(percent1); - - return info; - } - } -} diff --git a/src/modules/fancyzones/editor/FancyZonesEditor/SplitEventArgs.cs b/src/modules/fancyzones/editor/FancyZonesEditor/SplitEventArgs.cs index 239a1b92d7..192be3300a 100644 --- a/src/modules/fancyzones/editor/FancyZonesEditor/SplitEventArgs.cs +++ b/src/modules/fancyzones/editor/FancyZonesEditor/SplitEventArgs.cs @@ -13,18 +13,15 @@ namespace FancyZonesEditor { } - public SplitEventArgs(Orientation orientation, double offset, double thickness) + public SplitEventArgs(Orientation orientation, int offset) { Orientation = orientation; Offset = offset; - Space = thickness; } public Orientation Orientation { get; } - public double Offset { get; } - - public double Space { get; } + public int Offset { get; } } public delegate void SplitEventHandler(object sender, SplitEventArgs args); diff --git a/src/modules/fancyzones/lib/ZoneSet.cpp b/src/modules/fancyzones/lib/ZoneSet.cpp index 7893e80d6c..4ce1f28fe8 100644 --- a/src/modules/fancyzones/lib/ZoneSet.cpp +++ b/src/modules/fancyzones/lib/ZoneSet.cpp @@ -825,8 +825,8 @@ bool ZoneSet::CalculateCustomLayout(Rect workArea, int spacing) noexcept bool ZoneSet::CalculateGridZones(Rect workArea, FancyZonesDataTypes::GridLayoutInfo gridLayoutInfo, int spacing) { - long totalWidth = workArea.width() - (spacing * (gridLayoutInfo.columns() + 1)); - long totalHeight = workArea.height() - (spacing * (gridLayoutInfo.rows() + 1)); + long totalWidth = workArea.width(); + long totalHeight = workArea.height(); struct Info { long Extent; @@ -841,18 +841,18 @@ bool ZoneSet::CalculateGridZones(Rect workArea, FancyZonesDataTypes::GridLayoutI int totalPercents = 0; for (int row = 0; row < gridLayoutInfo.rows(); row++) { - rowInfo[row].Start = totalPercents * totalHeight / C_MULTIPLIER + (row + 1) * spacing; + rowInfo[row].Start = totalPercents * totalHeight / C_MULTIPLIER; totalPercents += gridLayoutInfo.rowsPercents()[row]; - rowInfo[row].End = totalPercents * totalHeight / C_MULTIPLIER + (row + 1) * spacing; + rowInfo[row].End = totalPercents * totalHeight / C_MULTIPLIER; rowInfo[row].Extent = rowInfo[row].End - rowInfo[row].Start; } totalPercents = 0; for (int col = 0; col < gridLayoutInfo.columns(); col++) { - columnInfo[col].Start = totalPercents * totalWidth / C_MULTIPLIER + (col + 1) * spacing; + columnInfo[col].Start = totalPercents * totalWidth / C_MULTIPLIER; totalPercents += gridLayoutInfo.columnsPercents()[col]; - columnInfo[col].End = totalPercents * totalWidth / C_MULTIPLIER + (col + 1) * spacing; + columnInfo[col].End = totalPercents * totalWidth / C_MULTIPLIER; columnInfo[col].Extent = columnInfo[col].End - columnInfo[col].Start; } @@ -881,6 +881,11 @@ bool ZoneSet::CalculateGridZones(Rect workArea, FancyZonesDataTypes::GridLayoutI long right = columnInfo[maxCol].End; long bottom = rowInfo[maxRow].End; + top += row == 0 ? spacing : spacing / 2; + bottom -= row == gridLayoutInfo.rows() - 1 ? spacing : spacing / 2; + left += col == 0 ? spacing : spacing / 2; + right -= col == gridLayoutInfo.columns() - 1 ? spacing : spacing / 2; + auto zone = MakeZone(RECT{ left, top, right, bottom }, i); if (zone) {