mirror of
https://github.com/haven1433/HexManiacAdvance.git
synced 2026-10-02 05:06:35 -05:00
update: make it impossible to select a single cell of a pointer/int/enum.
all the cells come together as a single package. this clears up some confusion as to which byte you're editing... hopefully.
This commit is contained in:
@@ -8,11 +8,13 @@ using System.Linq;
|
||||
using System.Windows.Input;
|
||||
|
||||
namespace HavenSoft.HexManiac.Core.ViewModels {
|
||||
public delegate (Point start, Point end) GetSelectionSpan(Point p);
|
||||
|
||||
public class Selection : ViewModelCore {
|
||||
private const int DefaultPreferredWidth = 0x10;
|
||||
|
||||
private readonly IDataModel model;
|
||||
|
||||
private readonly GetSelectionSpan getSpan;
|
||||
private readonly StubCommand
|
||||
moveSelectionStart = new StubCommand(),
|
||||
moveSelectionEnd = new StubCommand(),
|
||||
@@ -26,7 +28,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
|
||||
|
||||
private int preferredWidth = DefaultPreferredWidth, maxWidth = 4;
|
||||
|
||||
private Point selectionStart, selectionEnd;
|
||||
private Point rawSelectionStart; // the actual click point
|
||||
private Point selectionStart; // the calculated selection start, which may differ depending on the SelectionSpan
|
||||
private Point rawSelectionEnd; // the actual release point
|
||||
private Point selectionEnd; // the calculated selection end, which may differ depending on the SelectionSpan
|
||||
|
||||
public Point SelectionStart {
|
||||
get => selectionStart;
|
||||
@@ -37,12 +42,14 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
|
||||
if (selectionStart.Equals(value)) return;
|
||||
|
||||
if (!Scroll.ScrollToPoint(ref value)) {
|
||||
PreviewSelectionStartChanged?.Invoke(this, selectionStart);
|
||||
PreviewSelectionStartChanged?.Invoke(this, rawSelectionStart);
|
||||
}
|
||||
|
||||
if (TryUpdate(ref selectionStart, value)) {
|
||||
SelectionEnd = selectionStart;
|
||||
}
|
||||
rawSelectionStart = value;
|
||||
rawSelectionEnd = value;
|
||||
var (start, end) = getSpan(rawSelectionStart);
|
||||
TryUpdate(ref selectionStart, start);
|
||||
TryUpdate(ref selectionEnd, end, nameof(SelectionEnd));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -52,8 +59,35 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
|
||||
var index = Scroll.ViewPointToDataIndex(value);
|
||||
value = Scroll.DataIndexToViewPoint(index.LimitToRange(0, Scroll.DataLength));
|
||||
|
||||
if (selectionEnd.Equals(value)) return;
|
||||
|
||||
Scroll.ScrollToPoint(ref value);
|
||||
TryUpdate(ref selectionEnd, value);
|
||||
|
||||
rawSelectionEnd = value;
|
||||
var startIndex = Scroll.ViewPointToDataIndex(rawSelectionStart);
|
||||
var endIndex = Scroll.ViewPointToDataIndex(rawSelectionEnd);
|
||||
|
||||
// case 1: start/end are the same
|
||||
if (startIndex == endIndex) {
|
||||
var (start, end) = getSpan(rawSelectionStart);
|
||||
TryUpdate(ref selectionStart, start, nameof(SelectionStart));
|
||||
TryUpdate(ref selectionEnd, end);
|
||||
return;
|
||||
}
|
||||
|
||||
// case 2: start < end
|
||||
if (startIndex < endIndex) {
|
||||
TryUpdate(ref selectionStart, getSpan(rawSelectionStart).start, nameof(SelectionStart));
|
||||
TryUpdate(ref selectionEnd, getSpan(rawSelectionEnd).end);
|
||||
return;
|
||||
}
|
||||
|
||||
// case 3: start > end
|
||||
if (startIndex > endIndex) {
|
||||
TryUpdate(ref selectionEnd, getSpan(rawSelectionEnd).start);
|
||||
TryUpdate(ref selectionStart, getSpan(rawSelectionStart).end, nameof(SelectionStart));
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,8 +114,9 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
|
||||
/// </summary>
|
||||
public event EventHandler<Point> PreviewSelectionStartChanged;
|
||||
|
||||
public Selection(ScrollRegion scrollRegion, IDataModel model) {
|
||||
public Selection(ScrollRegion scrollRegion, IDataModel model, GetSelectionSpan getSpan = null) {
|
||||
this.model = model;
|
||||
this.getSpan = getSpan ?? GetDefaultSelectionSpan;
|
||||
Scroll = scrollRegion;
|
||||
Scroll.ScrollChanged += (sender, e) => ShiftSelectionFromScroll(e);
|
||||
|
||||
@@ -152,13 +187,17 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
|
||||
/// </summary>
|
||||
public void ChangeWidth(int newWidth) {
|
||||
maxWidth = newWidth;
|
||||
var rawStart = Scroll.ViewPointToDataIndex(rawSelectionStart);
|
||||
var rawEnd = Scroll.ViewPointToDataIndex(rawSelectionEnd);
|
||||
var start = Scroll.ViewPointToDataIndex(selectionStart);
|
||||
var end = Scroll.ViewPointToDataIndex(selectionEnd);
|
||||
|
||||
Scroll.Width = CoerceWidth(newWidth);
|
||||
|
||||
TryUpdate(ref selectionStart, Scroll.DataIndexToViewPoint(start));
|
||||
TryUpdate(ref selectionEnd, Scroll.DataIndexToViewPoint(end));
|
||||
rawSelectionStart = Scroll.DataIndexToViewPoint(rawStart);
|
||||
rawSelectionEnd = Scroll.DataIndexToViewPoint(rawEnd);
|
||||
selectionStart = Scroll.DataIndexToViewPoint(start);
|
||||
selectionEnd = Scroll.DataIndexToViewPoint(end);
|
||||
}
|
||||
|
||||
public void GotoAddress(int address) {
|
||||
@@ -171,6 +210,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
|
||||
GotoAddressHelper(address);
|
||||
}
|
||||
|
||||
private static (Point start, Point end) GetDefaultSelectionSpan(Point p) => (p, p);
|
||||
|
||||
private void GotoAddressHelper(int address) {
|
||||
var destinationRun = model.GetNextRun(address) as ArrayRun;
|
||||
var destinationIsArray = destinationRun != null;
|
||||
@@ -201,16 +242,23 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
|
||||
/// Nothing in this method notifies because any amount of scrolling means we already need a complete redraw.
|
||||
/// </summary>
|
||||
private void ShiftSelectionFromScroll(int distance) {
|
||||
var rawStart = Scroll.ViewPointToDataIndex(rawSelectionStart);
|
||||
var rawEnd = Scroll.ViewPointToDataIndex(rawSelectionEnd);
|
||||
var start = Scroll.ViewPointToDataIndex(selectionStart);
|
||||
var end = Scroll.ViewPointToDataIndex(selectionEnd);
|
||||
|
||||
rawStart -= distance;
|
||||
rawEnd -= distance;
|
||||
start -= distance;
|
||||
end -= distance;
|
||||
|
||||
rawSelectionStart = Scroll.DataIndexToViewPoint(rawStart);
|
||||
rawSelectionEnd = Scroll.DataIndexToViewPoint(rawEnd);
|
||||
selectionStart = Scroll.DataIndexToViewPoint(start);
|
||||
selectionEnd = Scroll.DataIndexToViewPoint(end);
|
||||
}
|
||||
|
||||
// TODO update these two methods to work with rawSelectionStart / rawSelectionEnd
|
||||
private void MoveSelectionStartExecuted(Direction direction) {
|
||||
Point dif;
|
||||
if (direction == Direction.PageUp) {
|
||||
@@ -220,7 +268,15 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
|
||||
} else {
|
||||
dif = ScrollRegion.DirectionToDif[direction];
|
||||
}
|
||||
SelectionStart = SelectionEnd + dif;
|
||||
|
||||
var (start, end) = getSpan(rawSelectionEnd);
|
||||
if (dif.X < 0 || dif.Y < 0) {
|
||||
// start from the _front_ of selectionEnd
|
||||
SelectionStart = start + dif;
|
||||
} else {
|
||||
// start from the _back_ of selectionEnd
|
||||
SelectionStart = end + dif;
|
||||
}
|
||||
}
|
||||
|
||||
private void MoveSelectionEndExecuted(Direction direction) {
|
||||
@@ -232,7 +288,15 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
|
||||
} else {
|
||||
dif = ScrollRegion.DirectionToDif[direction];
|
||||
}
|
||||
SelectionEnd += dif;
|
||||
|
||||
var (start, end) = getSpan(rawSelectionEnd);
|
||||
if (dif.X < 0 || dif.Y < 0) {
|
||||
// start from the _front_ of selectionEnd
|
||||
SelectionEnd = start + dif;
|
||||
} else {
|
||||
// start from the _back_ of selectionEnd
|
||||
SelectionEnd = end + dif;
|
||||
}
|
||||
}
|
||||
|
||||
private int CoerceWidth(int width) {
|
||||
|
||||
@@ -353,7 +353,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
|
||||
scroll = new ScrollRegion(model.TryGetUsefulHeader) { DataLength = Model.Count };
|
||||
scroll.PropertyChanged += ScrollPropertyChanged;
|
||||
|
||||
selection = new Selection(scroll, Model);
|
||||
selection = new Selection(scroll, Model, GetSelectionSpan);
|
||||
selection.PropertyChanged += SelectionPropertyChanged;
|
||||
selection.PreviewSelectionStartChanged += ClearActiveEditBeforeSelectionChanges;
|
||||
selection.OnError += (sender, e) => OnError?.Invoke(this, e);
|
||||
@@ -505,6 +505,10 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
|
||||
RefreshBackingData();
|
||||
}
|
||||
|
||||
// TODO rethink backspace handling
|
||||
// should it be changing just _before_ the selection,
|
||||
// or should it be changing the _current_ selection?
|
||||
|
||||
run = Model.GetNextRun(index - 1);
|
||||
if (run is PCSRun pcs) {
|
||||
for (int i = index - 1; i < run.Start + run.Length; i++) history.CurrentChange.ChangeData(Model, i, 0xFF);
|
||||
@@ -520,6 +524,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
|
||||
RefreshBackingData();
|
||||
SilentScroll(index - 1);
|
||||
} else {
|
||||
// TODO
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
} else if (run.Start <= index - 1 && run.Start + run.Length > index - 1) {
|
||||
@@ -529,11 +534,17 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
|
||||
element = currentView[SelectionStart.X, SelectionStart.Y];
|
||||
element.Format.Visit(cellToText, element.Value);
|
||||
var text = cellToText.Result;
|
||||
|
||||
var editLength = 1;
|
||||
if (element.Format is Pointer pointer) editLength = 4;
|
||||
if (element.Format is Integer integer) editLength = integer.Length;
|
||||
|
||||
for (int i = 0; i < run.Length; i++) {
|
||||
var p = scroll.DataIndexToViewPoint(run.Start + i);
|
||||
string editString = i == 0 ? text.Substring(0, text.Length - 1) : string.Empty;
|
||||
currentView[p.X, p.Y] = new HexElement(currentView[p.X, p.Y].Value, currentView[p.X, p.Y].Format.Edit(editString));
|
||||
currentView[p.X, p.Y] = new HexElement(currentView[p.X, p.Y].Value, new UnderEdit(currentView[p.X, p.Y].Format, editString, editLength));
|
||||
}
|
||||
|
||||
} else {
|
||||
SelectionStart = scroll.DataIndexToViewPoint(index - 1);
|
||||
element = currentView[SelectionStart.X, SelectionStart.Y];
|
||||
@@ -978,6 +989,22 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
|
||||
return false;
|
||||
}
|
||||
|
||||
private (Point start, Point end) GetSelectionSpan(Point p) {
|
||||
var index = scroll.ViewPointToDataIndex(p);
|
||||
var run = Model.GetNextRun(index);
|
||||
if (run.Start > index) return (p, p);
|
||||
|
||||
if (run is PointerRun) return (scroll.DataIndexToViewPoint(run.Start), scroll.DataIndexToViewPoint(run.Start + run.Length - 1));
|
||||
if (!(run is ArrayRun array)) return (p, p);
|
||||
|
||||
var offset = array.ConvertByteOffsetToArrayOffset(index);
|
||||
if (array.ElementContent[offset.SegmentIndex].Type == ElementContentType.Pointer || array.ElementContent[offset.SegmentIndex].Type == ElementContentType.Integer) {
|
||||
return (scroll.DataIndexToViewPoint(offset.SegmentStart), scroll.DataIndexToViewPoint(offset.SegmentStart + array.ElementContent[offset.SegmentIndex].Length - 1));
|
||||
}
|
||||
|
||||
return (p, p);
|
||||
}
|
||||
|
||||
private bool TryCoerceSelectionToStartOfElement(ref Point point, ref HexElement element) {
|
||||
var format = element.Format;
|
||||
var (position, length) = (-1, -1);
|
||||
|
||||
@@ -308,7 +308,7 @@ namespace HavenSoft.HexManiac.Tests {
|
||||
viewPort.Edit("01 02 03 04"); // 2x4 characters to clear
|
||||
viewPort.Edit("<000020>"); // 8 characters to clear
|
||||
viewPort.Edit("<000030>"); // 8 characters to clear
|
||||
viewPort.SelectionStart = new Point(10, 1);
|
||||
viewPort.SelectionStart = new Point(12, 1); // just after <000030>
|
||||
|
||||
for (int i = 0; i < 21; i++) viewPort.Edit(ConsoleKey.Backspace); // should clear both pointers (16) and 2 bytes (4)
|
||||
viewPort.MoveSelectionStart.Execute(Direction.Up);
|
||||
@@ -330,8 +330,9 @@ namespace HavenSoft.HexManiac.Tests {
|
||||
viewPort.SelectionStart = new Point(0, 1);
|
||||
viewPort.Edit("<000020>");
|
||||
viewPort.Edit("<000030>");
|
||||
viewPort.SelectionStart = new Point(2, 1);
|
||||
viewPort.SelectionStart = new Point(2, 1); // note that this will select all four bytes of <000020>
|
||||
viewPort.Clear.Execute(); // this should clear the data and formatting of the first pointer
|
||||
viewPort.SelectionStart = new Point(2, 1); // this selects just the one byte
|
||||
viewPort.Edit("<000040>"); // this should remove the second pointer
|
||||
|
||||
Assert.Equal(0xFF, viewPort[0, 1].Value);
|
||||
|
||||
@@ -226,5 +226,126 @@ namespace HavenSoft.HexManiac.Tests {
|
||||
Assert.True(viewPort.IsSelected(new Point(3, 0)));
|
||||
Assert.False(viewPort.IsSelected(new Point(4, 0)));
|
||||
}
|
||||
|
||||
[Theory]
|
||||
[InlineData(0)]
|
||||
[InlineData(1)]
|
||||
[InlineData(2)]
|
||||
[InlineData(3)]
|
||||
public void SelectingAnyOfAPointerSelectsAllOfAPointer(int index) {
|
||||
var data = new byte[0x200];
|
||||
var model = new PokemonModel(data);
|
||||
var viewPort = new ViewPort("file.txt", model) { Width = 0x10, Height = 0x10 };
|
||||
|
||||
viewPort.Edit("<000100>");
|
||||
viewPort.SelectionStart = new Point(index, 0);
|
||||
|
||||
Assert.True(viewPort.IsSelected(new Point(0, 0)));
|
||||
Assert.True(viewPort.IsSelected(new Point(1, 0)));
|
||||
Assert.True(viewPort.IsSelected(new Point(2, 0)));
|
||||
Assert.True(viewPort.IsSelected(new Point(3, 0)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectLeftSelectsWholePointer() {
|
||||
var data = new byte[0x200];
|
||||
var model = new PokemonModel(data);
|
||||
var viewPort = new ViewPort("file.txt", model) { Width = 0x10, Height = 0x10 };
|
||||
|
||||
viewPort.SelectionStart = new Point(4, 0);
|
||||
viewPort.Edit("<000100>");
|
||||
viewPort.SelectionStart = new Point(8, 0);
|
||||
viewPort.MoveSelectionStart.Execute(Direction.Left);
|
||||
|
||||
Assert.True(viewPort.IsSelected(new Point(4, 0)));
|
||||
Assert.True(viewPort.IsSelected(new Point(5, 0)));
|
||||
Assert.True(viewPort.IsSelected(new Point(6, 0)));
|
||||
Assert.True(viewPort.IsSelected(new Point(7, 0)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectRightSelectsWholePointer() {
|
||||
var data = new byte[0x200];
|
||||
var model = new PokemonModel(data);
|
||||
var viewPort = new ViewPort("file.txt", model) { Width = 0x10, Height = 0x10 };
|
||||
|
||||
viewPort.SelectionStart = new Point(4, 0);
|
||||
viewPort.Edit("<000100>");
|
||||
viewPort.SelectionStart = new Point(3, 0);
|
||||
viewPort.MoveSelectionStart.Execute(Direction.Right);
|
||||
|
||||
Assert.True(viewPort.IsSelected(new Point(4, 0)));
|
||||
Assert.True(viewPort.IsSelected(new Point(5, 0)));
|
||||
Assert.True(viewPort.IsSelected(new Point(6, 0)));
|
||||
Assert.True(viewPort.IsSelected(new Point(7, 0)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectUpSelectsWholePointer() {
|
||||
var data = new byte[0x200];
|
||||
var model = new PokemonModel(data);
|
||||
var viewPort = new ViewPort("file.txt", model) { Width = 0x10, Height = 0x10 };
|
||||
|
||||
viewPort.SelectionStart = new Point(4, 0);
|
||||
viewPort.Edit("<000100>");
|
||||
viewPort.SelectionStart = new Point(5, 1);
|
||||
viewPort.MoveSelectionStart.Execute(Direction.Up);
|
||||
|
||||
Assert.True(viewPort.IsSelected(new Point(4, 0)));
|
||||
Assert.True(viewPort.IsSelected(new Point(5, 0)));
|
||||
Assert.True(viewPort.IsSelected(new Point(6, 0)));
|
||||
Assert.True(viewPort.IsSelected(new Point(7, 0)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void SelectDownSelectsWholePointer() {
|
||||
var data = new byte[0x200];
|
||||
var model = new PokemonModel(data);
|
||||
var viewPort = new ViewPort("file.txt", model) { Width = 0x10, Height = 0x10 };
|
||||
|
||||
viewPort.SelectionStart = new Point(4, 1);
|
||||
viewPort.Edit("<000100>");
|
||||
viewPort.SelectionStart = new Point(5, 0);
|
||||
viewPort.MoveSelectionStart.Execute(Direction.Down);
|
||||
|
||||
Assert.True(viewPort.IsSelected(new Point(4, 1)));
|
||||
Assert.True(viewPort.IsSelected(new Point(5, 1)));
|
||||
Assert.True(viewPort.IsSelected(new Point(6, 1)));
|
||||
Assert.True(viewPort.IsSelected(new Point(7, 1)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HighlightLeftSelectsWholePointer() {
|
||||
var data = new byte[0x200];
|
||||
var model = new PokemonModel(data);
|
||||
var viewPort = new ViewPort("file.txt", model) { Width = 0x10, Height = 0x10 };
|
||||
|
||||
viewPort.SelectionStart = new Point(4, 1);
|
||||
viewPort.Edit("<000100>");
|
||||
viewPort.SelectionStart = new Point(6, 1);
|
||||
viewPort.SelectionEnd = new Point(2, 1);
|
||||
|
||||
Assert.True(viewPort.IsSelected(new Point(4, 1)));
|
||||
Assert.True(viewPort.IsSelected(new Point(5, 1)));
|
||||
Assert.True(viewPort.IsSelected(new Point(6, 1)));
|
||||
Assert.True(viewPort.IsSelected(new Point(7, 1)));
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void HighlightRightSelectsWholePointer() {
|
||||
var data = new byte[0x200];
|
||||
var model = new PokemonModel(data);
|
||||
var viewPort = new ViewPort("file.txt", model) { Width = 0x10, Height = 0x10 };
|
||||
|
||||
viewPort.SelectionStart = new Point(4, 1);
|
||||
viewPort.Edit("<000100>");
|
||||
viewPort.SelectionStart = new Point(6, 1);
|
||||
viewPort.SelectionEnd = new Point(10, 1);
|
||||
|
||||
Assert.True(viewPort.IsSelected(new Point(4, 1)));
|
||||
Assert.True(viewPort.IsSelected(new Point(5, 1)));
|
||||
Assert.True(viewPort.IsSelected(new Point(6, 1)));
|
||||
Assert.True(viewPort.IsSelected(new Point(7, 1)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user