[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
This commit is contained in:
Ivan Stošić 2021-03-10 13:22:19 +01:00 committed by GitHub
parent 9a2c195f5f
commit e586a7ad64
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
12 changed files with 897 additions and 2279 deletions

File diff suppressed because it is too large Load diff

View file

@ -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<object, DragDeltaEventArgs> dragDelta, Action<object, DragCompletedEventArgs> 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<GridResizer, bool> cmpr = (GridResizer resizer) =>
{
return resizer.Orientation == Orientation.Vertical && resizer.StartCol == splitCol;
};
Func<GridResizer, bool> endCmpr = (GridResizer resizer) =>
{
return resizer.EndRow == foundRow;
};
Func<GridResizer, bool> 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<GridResizer, bool> cmpr = (GridResizer resizer) =>
{
return resizer.Orientation == Orientation.Horizontal && resizer.StartRow == splitRow;
};
Func<GridResizer, bool> endCmpr = (GridResizer resizer) =>
{
return resizer.EndCol == foundCol;
};
Func<GridResizer, bool> 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<GridResizer> swappedResizers = new List<GridResizer>();
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<GridResizer, bool> cmpr, Func<GridResizer, bool> endCmpr, Func<GridResizer, bool> 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<object, DragDeltaEventArgs> _dragDelta;
private readonly Action<object, DragCompletedEventArgs> _dragCompleted;
}
}

View file

@ -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<int, int> 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<int> 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<int>();
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<int>();
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<int> deleteAction = (index) =>
{
DeleteZone(index);
};
_data.MergeZones(_startRow, _endRow, _startCol, _endCol, deleteAction, Preview.Children.Count);
_dragHandles.RemoveDragHandles();
_dragHandles.InitDragHandles(Model);
var selectedIndices = new List<int>();
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;
}
}

View file

@ -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; }

View file

@ -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<Orientation, int, bool> _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<Orientation, int, bool> 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));
}
}
}

View file

@ -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<Int32Rect> _zones = new List<Int32Rect>();
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));
}
}
}

View file

@ -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<int> _keyPoints;
private double _workAreaSize;
private const int MagnetZoneMaxSize = GridData.Multiplier / 12;
public MagneticSnap(List<int> 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<int>();
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;
}
}
}

View file

@ -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<int> FreeZones { get; } = new List<int>();
public GridLayoutModel()
: base()
{

View file

@ -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;
}
}
}

View file

@ -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;
}
}
}

View file

@ -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);

View file

@ -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)
{