Skip to content
Open
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using BlazorDatasheet.DataStructures.Geometry;

namespace BlazorDatasheet.Core.Events.Selection;

public class ActiveRegionChangedEvent
{
public IRegion? OldRegion { get; }
public IRegion? NewRegion { get; }

public ActiveRegionChangedEvent(IRegion? oldRegion, IRegion? newRegion)
{
OldRegion = oldRegion;
NewRegion = newRegion;
}
}
144 changes: 108 additions & 36 deletions src/BlazorDatasheet.Core/Selecting/Selection.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ public class Selection
/// <summary>
/// The region that is active for accepting user input, usually the most recent region added
/// </summary>
public IRegion? ActiveRegion { get; private set; }
public IRegion? ActiveRegion => _activeRegionIndex >= 0 ? _regions[_activeRegionIndex] : null;

private int _activeRegionIndex = -1;

private readonly List<IRegion> _regions = new();

Expand Down Expand Up @@ -42,7 +44,7 @@ public class Selection
/// <summary>
/// The position that the selecting process was started at
/// </summary>
public CellPosition SelectingStartPosition { get; private set; }
private CellPosition _selectingStartPosition;

public bool IsSelecting => SelectingRegion != null;

Expand Down Expand Up @@ -71,6 +73,11 @@ public class Selection
/// </summary>
public EventHandler<ActiveCellPositionChangedEventArgs>? ActiveCellPositionChanged;

/// <summary>
/// Fired when the active region changes
/// </summary>
public EventHandler<ActiveRegionChangedEvent>? ActiveRegionChanged;

public Selection(Sheet sheet)
{
_sheet = sheet;
Expand All @@ -84,7 +91,7 @@ public void BeginSelectingCell(int row, int col)
return;

this.SelectingRegion = new Region(row, col);
this.SelectingStartPosition = new CellPosition(row, col);
this._selectingStartPosition = new CellPosition(row, col);
this._selectingMode = SelectionMode.Cell;
this.SelectingRegion = _sheet.ExpandRegionOverMerges(SelectingRegion);
EmitSelectingChanged();
Expand All @@ -95,7 +102,7 @@ public void BeginSelectingCol(int col)
if (_sheet.Area == 0)
return;
this.SelectingRegion = new ColumnRegion(col, col);
this.SelectingStartPosition = new CellPosition(0, col);
this._selectingStartPosition = new CellPosition(0, col);
this._selectingMode = SelectionMode.Column;
this.SelectingRegion = _sheet.ExpandRegionOverMerges(SelectingRegion);
EmitSelectingChanged();
Expand All @@ -106,7 +113,7 @@ public void BeginSelectingRow(int row)
if (_sheet.Area == 0)
return;
this.SelectingRegion = new RowRegion(row, row);
this.SelectingStartPosition = new CellPosition(row, 0);
this._selectingStartPosition = new CellPosition(row, 0);
this._selectingMode = SelectionMode.Row;
this.SelectingRegion = _sheet.ExpandRegionOverMerges(SelectingRegion);
EmitSelectingChanged();
Expand All @@ -120,13 +127,13 @@ public void UpdateSelectingEndPosition(int row, int col)
switch (_selectingMode)
{
case SelectionMode.Column:
SelectingRegion = new ColumnRegion(SelectingStartPosition.col, col);
SelectingRegion = new ColumnRegion(_selectingStartPosition.col, col);
break;
case SelectionMode.Row:
SelectingRegion = new RowRegion(SelectingStartPosition.row, row);
SelectingRegion = new RowRegion(_selectingStartPosition.row, row);
break;
case SelectionMode.Cell:
SelectingRegion = new Region(SelectingStartPosition.row, row, SelectingStartPosition.col, col);
SelectingRegion = new Region(_selectingStartPosition.row, row, _selectingStartPosition.col, col);
break;
}

Expand All @@ -144,7 +151,7 @@ public void EndSelecting()
{
if (SelectingRegion == null)
return;
SetActiveCellPosition(SelectingStartPosition.row, SelectingStartPosition.col);
SetActiveCellPosition(_selectingStartPosition.row, _selectingStartPosition.col);
var oldRegions = Regions.Select(x => x.Clone()).ToList();
this.Add(SelectingRegion);
SelectingRegion = null;
Expand All @@ -167,8 +174,8 @@ private void EmitSelectingChanged()
public void ClearSelections()
{
var oldRegions = CloneRegions();
SetActiveRegionIndex(-1);
_regions.Clear();
ActiveRegion = null;
EmitSelectionChange(oldRegions);
}

Expand Down Expand Up @@ -196,10 +203,37 @@ private void Add(List<IRegion> regions)
_regions.Add(expandedRegion);
}

ActiveRegion = _regions.LastOrDefault();
SetActiveRegionIndex(_regions.Count - 1);
EmitSelectionChange(oldRegions);
}

/// <summary>
/// Sets the active region out with the updated region
/// </summary>
/// <param name="newRegion"></param>
private void SwapActiveRegion(IRegion newRegion)
{
if (newRegion == ActiveRegion)
return;

var oldRegion = ActiveRegion;

if (_activeRegionIndex >= 0 && _activeRegionIndex < _regions.Count)
_regions[_activeRegionIndex] = newRegion;

ActiveRegionChanged?.Invoke(this, new ActiveRegionChangedEvent(oldRegion, ActiveRegion));
}

private void SetActiveRegionIndex(int index)
{
if (index == _activeRegionIndex)
return;

var oldRegion = ActiveRegion;
_activeRegionIndex = index;
ActiveRegionChanged?.Invoke(this, new ActiveRegionChangedEvent(oldRegion, ActiveRegion));
}

/// <summary>
/// Extends the active position to the row/col specified
/// </summary>
Expand All @@ -224,7 +258,8 @@ public void ExtendTo(int row, int col)
var expanded = _sheet.ExpandRegionOverMerges(newRegion);

if (expanded != null)
ActiveRegion.Set(expanded);
SwapActiveRegion(expanded);

EmitSelectionChange(oldRegions);
}

Expand Down Expand Up @@ -252,7 +287,7 @@ public void ExpandEdge(Edge edge, int amount)
var expanded = _sheet.ExpandRegionOverMerges(newRegion);
expanded = expanded?.GetIntersection(_sheet.Region);
if (expanded != null)
ActiveRegion.Set(expanded);
SwapActiveRegion(expanded);

EmitSelectionChange(oldRegions);
}
Expand Down Expand Up @@ -292,7 +327,7 @@ public void ContractEdge(Edge edge, int amount)

if (contracted != null &&
(contracted.Contains(activeCellRegion) || contracted is RowRegion or ColumnRegion))
ActiveRegion.Set(contracted);
SwapActiveRegion(contracted);
else
{
ExpandEdge(edge.GetOpposite(), amount);
Expand Down Expand Up @@ -320,6 +355,8 @@ public void Set(int row, int col)
public void Set(List<IRegion> regions)
{
_regions.Clear();
_activeRegionIndex = -1;

if (_sheet.Area == 0 || regions.Count == 0)
return;

Expand All @@ -343,7 +380,7 @@ public void ConstrainSelectionToSheet()
constrainedRegions.Add(intersection);
}

ActiveRegion = null;
SetActiveRegionIndex(-1);
_regions.Clear();
Set(constrainedRegions);
}
Expand Down Expand Up @@ -432,10 +469,11 @@ public void GrowActiveSelection(Offset offset)
/// <param name="rowDir"></param>
public void MoveActivePositionByRow(int rowDir)
{
var oldRegions = CloneRegions();
if (ActiveRegion == null || rowDir == 0)
return;

var oldRegions = CloneRegions();

rowDir = Math.Sign(rowDir);

var currRow = ActiveCellPosition.row;
Expand Down Expand Up @@ -489,14 +527,16 @@ public void MoveActivePositionByRow(int rowDir)
newRow = activeRegionFixed.Top;
if (newCol > activeRegionFixed.Right)
{
var newActiveRegion = GetRegionAfterActive();
var newActiveRegionIndex = GetRegionIndexAfterActive();
var newActiveRegion = _regions[newActiveRegionIndex];
var newActiveRegionFixed = newActiveRegion.GetIntersection(_sheet.Region);
if (newActiveRegionFixed == null)
return;

newCol = newActiveRegionFixed.Left;
newRow = newActiveRegionFixed.Top;
ActiveRegion = newActiveRegion;

SetActiveRegionIndex(newActiveRegionIndex);
}
}
else if (newRow < activeRegionFixed.Top)
Expand All @@ -505,13 +545,14 @@ public void MoveActivePositionByRow(int rowDir)
newRow = activeRegionFixed.Bottom;
if (newCol < activeRegionFixed.Left)
{
var newActiveRegion = GetRegionAfterActive();
var newActiveRegionIndex = GetRegionIndexAfterActive();
var newActiveRegion = _regions[newActiveRegionIndex];
var newActiveRegionFixed = newActiveRegion.GetIntersection(_sheet.Region);
if (newActiveRegionFixed == null)
return;
newCol = newActiveRegionFixed.Right;
newRow = newActiveRegionFixed.Bottom;
ActiveRegion = newActiveRegion;
SetActiveRegionIndex(newActiveRegionIndex);
}
}

Expand All @@ -527,10 +568,11 @@ public void MoveActivePositionByRow(int rowDir)
/// <param name="colDir"></param>
public void MoveActivePositionByCol(int colDir)
{
var oldRegions = CloneRegions();
if (ActiveRegion == null || colDir == 0)
return;

var oldRegions = CloneRegions();

colDir = Math.Sign(colDir);

var currRow = ActiveCellPosition.row;
Expand Down Expand Up @@ -585,13 +627,14 @@ public void MoveActivePositionByCol(int colDir)
newRow++;
if (newRow > activeRegionFixed.Bottom)
{
var newActiveRegion = GetRegionAfterActive();
var newActiveRegionIndex = GetRegionIndexAfterActive();
var newActiveRegion = _regions[newActiveRegionIndex];
var newActiveRegionFixed = newActiveRegion.GetIntersection(_sheet.Region);
if (newActiveRegionFixed == null)
return;
newCol = newActiveRegionFixed.Left;
newRow = newActiveRegionFixed.Top;
ActiveRegion = newActiveRegion;
SetActiveRegionIndex(newActiveRegionIndex);
}
}
else if (newCol < activeRegionFixed.Left)
Expand All @@ -600,29 +643,30 @@ public void MoveActivePositionByCol(int colDir)
newRow--;
if (newRow < activeRegionFixed.Top)
{
var newActiveRegion = GetRegionAfterActive();
var newActiveRegionIndex = GetRegionIndexAfterActive();
var newActiveRegion = _regions[newActiveRegionIndex];
var newActiveRegionFixed = newActiveRegion.GetIntersection(_sheet.Region);
if (newActiveRegionFixed == null)
return;
newCol = newActiveRegionFixed.Right;
newRow = newActiveRegionFixed.Bottom;
ActiveRegion = newActiveRegion;
SetActiveRegionIndex(newActiveRegionIndex);
}
}

SetActiveCellPosition(newRow, newCol);
EmitSelectionChange(oldRegions);
}

private IRegion GetRegionAfterActive()
private int GetRegionIndexAfterActive()
{
var activeRegionIndex = _regions.IndexOf(ActiveRegion!);
if (activeRegionIndex == -1)
var index = _activeRegionIndex;
if (index == -1)
throw new Exception("No range is active?");
activeRegionIndex++;
if (activeRegionIndex >= _regions.Count)
activeRegionIndex = 0;
return _regions[activeRegionIndex];
index++;
if (index >= _regions.Count)
index = 0;
return index;
}

private IRegion GetRegionBeforeActive()
Expand Down Expand Up @@ -694,18 +738,46 @@ internal void SetActiveCellPosition(int row, int col)
ActiveCellPositionChanged?.Invoke(this, setEventArgs);
}

/// <summary>
/// Makes the active cell position <paramref name="row"/>, <paramref name="col"/>
/// </summary>
/// <param name="row"></param>
/// <param name="col"></param>
public void Activate(int row, int col)
{
if (ActiveRegion == null || !_regions.Any())
return;

if (ActiveRegion.Contains(row, col))
{
SetActiveCellPosition(row, col);
return;
}

for (int i = 0; i < _regions.Count; i++)
{
var region = _regions[i];
if (region.Contains(row, col))
{
SetActiveRegionIndex(i);
SetActiveCellPosition(row, col);
return;
}
}

Set(row, col);
}

internal SelectionSnapshot GetSelectionSnapshot()
{
var activeRegionIndex = ActiveRegion != null ? _regions.IndexOf(ActiveRegion!) : -1;
var regions = _regions.Select(x => x.Clone()).ToList();
var activeRegionClone = activeRegionIndex != -1 ? regions[activeRegionIndex] : null;
return new SelectionSnapshot(activeRegionClone, regions, ActiveCellPosition);
return new SelectionSnapshot(_activeRegionIndex, regions, ActiveCellPosition);
}

internal void Restore(SelectionSnapshot selectionSnapshot)
{
var oldRegions = CloneRegions();
this.ActiveRegion = selectionSnapshot.ActiveRegion;
SetActiveRegionIndex(selectionSnapshot.ActiveRegionIndex);
_regions.Clear();
_regions.AddRange(selectionSnapshot.Regions);
ActiveCellPosition = selectionSnapshot.ActiveCellPosition;
Expand Down
6 changes: 3 additions & 3 deletions src/BlazorDatasheet.Core/Selecting/SelectionSnapshot.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ namespace BlazorDatasheet.Core.Selecting;

internal class SelectionSnapshot
{
public IRegion? ActiveRegion { get; }
public int ActiveRegionIndex { get; }
public IReadOnlyList<IRegion> Regions { get; }
public CellPosition ActiveCellPosition { get; private set; }

public SelectionSnapshot(IRegion? activeRegion, IReadOnlyList<IRegion> regions, CellPosition activeCellPosition)
public SelectionSnapshot(int activeRegionIndex, IReadOnlyList<IRegion> regions, CellPosition activeCellPosition)
{
ActiveRegion = activeRegion;
ActiveRegionIndex = activeRegionIndex;
Regions = regions;
ActiveCellPosition = activeCellPosition;
}
Expand Down
6 changes: 0 additions & 6 deletions src/BlazorDatasheet.DataStructures/Geometry/IRegion.cs
Original file line number Diff line number Diff line change
Expand Up @@ -250,10 +250,4 @@ public interface IRegion : IEquatable<IRegion>
/// Shift the entire region by the amount specified
/// </summary>
void Shift(int dRowStart, int dRowEnd, int dColStart, int dColEnd);

/// <summary>
/// Sets the region's start and end to those of <paramref name="region"/>
/// </summary>
/// <param name="region"></param>
void Set(IRegion region);
}
Loading