diff --git a/src/HexManiac.Core/HexManiac.Core.csproj b/src/HexManiac.Core/HexManiac.Core.csproj
index 7732781a..7ad61814 100644
--- a/src/HexManiac.Core/HexManiac.Core.csproj
+++ b/src/HexManiac.Core/HexManiac.Core.csproj
@@ -53,6 +53,7 @@
+
diff --git a/src/HexManiac.Core/Models/AutoSearchModel.cs b/src/HexManiac.Core/Models/AutoSearchModel.cs
index b14de645..4e086a2a 100644
--- a/src/HexManiac.Core/Models/AutoSearchModel.cs
+++ b/src/HexManiac.Core/Models/AutoSearchModel.cs
@@ -25,7 +25,7 @@ namespace HavenSoft.HexManiac.Core.Models {
public override int EarliestAllowedAnchor => 0x200;
public AutoSearchModel(byte[] data, StoredMetadata metadata = null) : base(data, metadata) {
- if (metadata != null) return;
+ if (metadata != null && !metadata.IsEmpty) return;
gameCode = string.Concat(Enumerable.Range(0xAC, 4).Select(i => ((char)data[i]).ToString()));
diff --git a/src/HexManiac.Core/Models/StoredMetadata.cs b/src/HexManiac.Core/Models/StoredMetadata.cs
index 7adb18d5..13824e6c 100644
--- a/src/HexManiac.Core/Models/StoredMetadata.cs
+++ b/src/HexManiac.Core/Models/StoredMetadata.cs
@@ -8,6 +8,8 @@ namespace HavenSoft.HexManiac.Core.Models {
public IReadOnlyList NamedAnchors { get; }
public IReadOnlyList UnmappedPointers { get; }
+ public bool IsEmpty => NamedAnchors.Count == 0 && UnmappedPointers.Count == 0;
+
public StoredMetadata(IReadOnlyList anchors, IReadOnlyList unmappedPointers) {
NamedAnchors = anchors;
UnmappedPointers = unmappedPointers;
diff --git a/src/HexManiac.Core/ViewModels/AutoCompleteSelectionItem.cs b/src/HexManiac.Core/ViewModels/AutoCompleteSelectionItem.cs
new file mode 100644
index 00000000..ba591f89
--- /dev/null
+++ b/src/HexManiac.Core/ViewModels/AutoCompleteSelectionItem.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+
+namespace HavenSoft.HexManiac.Core.ViewModels {
+ public class AutoCompleteSelectionItem : IEquatable, INotifyPropertyChanged {
+ public string CompletionText { get; }
+ public bool IsSelected { get; }
+
+#pragma warning disable 0067 // it's ok if events are never used after implementing an interface
+ public event PropertyChangedEventHandler PropertyChanged;
+#pragma warning restore 0067
+
+ public AutoCompleteSelectionItem(string text, bool selection) => (CompletionText, IsSelected) = (text, selection);
+
+ public static IReadOnlyList Generate(IEnumerable options, int selectionIndex) {
+ var list = new List();
+
+ int i = 0;
+ foreach (var option in options) {
+ list.Add(new AutoCompleteSelectionItem(option, i == selectionIndex));
+ i++;
+ }
+
+ return list;
+ }
+
+ public bool Equals(AutoCompleteSelectionItem other) {
+ if (other == null) return false;
+ return IsSelected == other.IsSelected && CompletionText == other.CompletionText;
+ }
+ }
+}
diff --git a/src/HexManiac.Core/ViewModels/DataFormats.cs b/src/HexManiac.Core/ViewModels/DataFormats.cs
index d3bf1fe1..7dc38456 100644
--- a/src/HexManiac.Core/ViewModels/DataFormats.cs
+++ b/src/HexManiac.Core/ViewModels/DataFormats.cs
@@ -53,7 +53,13 @@ namespace HavenSoft.HexManiac.Core.ViewModels.DataFormats {
public IDataFormat OriginalFormat { get; }
public string CurrentText { get; }
public int EditWidth { get; }
- public UnderEdit(IDataFormat original, string text, int editWidth = 1) => (OriginalFormat, CurrentText, EditWidth) = (original, text, editWidth);
+ public IReadOnlyList AutocompleteOptions { get; }
+ public UnderEdit(IDataFormat original, string text, int editWidth = 1, IReadOnlyList autocompleteOptions = null) {
+ OriginalFormat = original;
+ CurrentText = text;
+ EditWidth = editWidth;
+ AutocompleteOptions = autocompleteOptions;
+ }
public void Visit(IDataFormatVisitor visitor, byte data) => visitor.Visit(this, data);
public bool Equals(IDataFormat format) {
@@ -61,6 +67,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels.DataFormats {
if (!OriginalFormat.Equals(that.OriginalFormat)) return false;
if (EditWidth != that.EditWidth) return false;
+ if (AutocompleteOptions != null ^ that.AutocompleteOptions != null) return false; // if only one is null, not equal
+ if (AutocompleteOptions != null && that.AutocompleteOptions != null && AutocompleteOptions.SequenceEqual(that.AutocompleteOptions)) return false;
return CurrentText == that.CurrentText;
}
}
@@ -190,7 +198,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.DataFormats {
public Integer(int source, int position, int value, int length) => (Source, Position, Value, Length) = (source, position, value, length);
- public bool Equals(IDataFormat other) {
+ public virtual bool Equals(IDataFormat other) {
if (!(other is Integer that)) return false;
return Source == that.Source && Position == that.Position && Value == that.Value && Length == that.Length;
}
@@ -206,7 +214,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.DataFormats {
public new string Value { get; }
public IntegerEnum(int source, int position, string value, int length) : base(source, position, -1, length) => Value = value;
- public bool Equals(IDataFormat other) {
+ public override bool Equals(IDataFormat other) {
if (!(other is IntegerEnum that)) return false;
return Value == that.Value && base.Equals(other);
}
diff --git a/src/HexManiac.Core/ViewModels/GotoControlViewModel.cs b/src/HexManiac.Core/ViewModels/GotoControlViewModel.cs
index 98b28821..5c3bd8ba 100644
--- a/src/HexManiac.Core/ViewModels/GotoControlViewModel.cs
+++ b/src/HexManiac.Core/ViewModels/GotoControlViewModel.cs
@@ -26,7 +26,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
if (viewPort == null) return;
if (TryUpdate(ref text, value)) {
var options = viewPort.Model?.GetAutoCompleteAnchorNameOptions(text) ?? new string[0];
- AutoCompleteOptions = CreateAutoCompleteOptions(options, options.Count);
+ AutoCompleteOptions = AutoCompleteSelectionItem.Generate(options, completionIndex);
ShowAutoCompleteOptions = AutoCompleteOptions.Count > 0;
}
}
@@ -37,7 +37,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
get => completionIndex;
set {
if (TryUpdate(ref completionIndex, value.LimitToRange(-1, autoCompleteOptions.Count - 1))) {
- AutoCompleteOptions = CreateAutoCompleteOptions(AutoCompleteOptions.Select(option => option.CompletionText), AutoCompleteOptions.Count);
+ AutoCompleteOptions = AutoCompleteSelectionItem.Generate(AutoCompleteOptions.Select(option => option.CompletionText), completionIndex);
}
}
}
@@ -98,27 +98,5 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
Execute = arg => ControlVisible = (bool)arg,
};
}
-
- private IReadOnlyList CreateAutoCompleteOptions(IEnumerable options, int length) {
- if (completionIndex >= length) {
- completionIndex = length - 1;
- NotifyPropertyChanged(nameof(CompletionIndex));
- }
- var list = new List(length);
-
- int i = 0;
- foreach (var option in options) {
- list.Add(new AutoCompleteSelectionItem(option, i == completionIndex));
- i++;
- }
-
- return list;
- }
- }
-
- public class AutoCompleteSelectionItem {
- public string CompletionText { get; }
- public bool IsSelected { get; }
- public AutoCompleteSelectionItem(string text, bool selection) => (CompletionText, IsSelected) = (text, selection);
}
}
diff --git a/src/HexManiac.Core/ViewModels/SearchResultsViewPort.cs b/src/HexManiac.Core/ViewModels/SearchResultsViewPort.cs
index f2a094a2..edae503e 100644
--- a/src/HexManiac.Core/ViewModels/SearchResultsViewPort.cs
+++ b/src/HexManiac.Core/ViewModels/SearchResultsViewPort.cs
@@ -241,8 +241,12 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
public TableTool TableTool => null;
public IDisposable DeferUpdates => new StubDisposable();
+
+#pragma warning disable 0067 // it's ok if events are never used after implementing an interface
public event EventHandler OnError;
public event PropertyChangedEventHandler PropertyChanged;
+#pragma warning restore 0067
+
public void Schedule(Action action) => action();
public void RefreshContent() { }
diff --git a/src/HexManiac.Core/ViewModels/Theme.cs b/src/HexManiac.Core/ViewModels/Theme.cs
index ad646aaa..9df632ec 100644
--- a/src/HexManiac.Core/ViewModels/Theme.cs
+++ b/src/HexManiac.Core/ViewModels/Theme.cs
@@ -6,7 +6,7 @@ using System.Linq;
namespace HavenSoft.HexManiac.Core.ViewModels {
public class Theme : ViewModelCore {
private string lightColor = "#DDDDDD", darkColor = "#080808";
- private double hueOffset = 0.3, accentSaturation = 0.4, accentValue = 0.7, highlightBrightness = 0.7;
+ private double hueOffset = 0.1, accentSaturation = 0.9, accentValue = 0.6, highlightBrightness = 0.7;
private bool lightVariant;
public bool LightVariant {
@@ -106,8 +106,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
var accent = new List<(double hue, double sat, double bright)>();
var saturation = accentSaturation * .8 + .2;
var accentBrightness = accentValue * .6 + .4;
- var brightness = hsbLight.bright * accentBrightness + (1 - accentBrightness) * hsbDark.bright;
- var prototype = (hue: (hueOffset - .5) / 12, sat: saturation, bright: brightness);
+ var prototype = (hue: (hueOffset - .5) / 12, sat: saturation, bright: accentBrightness);
for (int i = 0; i < 8; i++) {
accent.Add(prototype);
prototype.hue += 1 / 8.0;
diff --git a/src/HexManiac.Core/ViewModels/Tools/TableTool.cs b/src/HexManiac.Core/ViewModels/Tools/TableTool.cs
index 127c03d5..32c597db 100644
--- a/src/HexManiac.Core/ViewModels/Tools/TableTool.cs
+++ b/src/HexManiac.Core/ViewModels/Tools/TableTool.cs
@@ -59,7 +59,9 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
public event EventHandler ModelDataChanged;
+#pragma warning disable 0067 // it's ok if events are never used after implementing an interface
public event EventHandler<(int originalLocation, int newLocation)> ModelDataMoved; // invoke when a new item gets added and the table has to move
+#pragma warning restore 0067
public TableTool(IDataModel model, Selection selection, ChangeHistory history, IToolTrayViewModel toolTray) {
this.model = model;
diff --git a/src/HexManiac.Core/ViewModels/ViewPort.cs b/src/HexManiac.Core/ViewModels/ViewPort.cs
index dd23afde..b14bf108 100644
--- a/src/HexManiac.Core/ViewModels/ViewPort.cs
+++ b/src/HexManiac.Core/ViewModels/ViewPort.cs
@@ -867,7 +867,18 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
SelectionStart = point;
if (element == currentView[point.X, point.Y]) {
- var newFormat = element.Format.Edit(input.ToString());
+ UnderEdit newFormat;
+ if (element.Format is UnderEdit underEdit && underEdit.AutocompleteOptions != null) {
+ if (underEdit.CurrentText.StartsWith(PointerStart.ToString())) {
+ var newText = underEdit.CurrentText + input;
+ var autocompleteOptions = GetNewPointerAutocompleteOptions(newText);
+ newFormat = new UnderEdit(underEdit.OriginalFormat, newText, underEdit.EditWidth, autocompleteOptions);
+ } else {
+ throw new NotImplementedException();
+ }
+ } else {
+ newFormat = element.Format.Edit(input.ToString());
+ }
currentView[point.X, point.Y] = new HexElement(element.Value, newFormat);
} else {
// ShouldAcceptInput already did the work: nothing to change
@@ -969,7 +980,8 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
// if the user tries to edit the pointer but forgets the opening bracket, add it for them.
if (input != PointerStart) editText = PointerStart + editText;
var newFormat = element.Format.Edit(editText);
- newFormat = new UnderEdit(newFormat.OriginalFormat, newFormat.CurrentText, 4);
+ var autocompleteOptions = GetNewPointerAutocompleteOptions(editText);
+ newFormat = new UnderEdit(newFormat.OriginalFormat, newFormat.CurrentText, 4, autocompleteOptions);
currentView[point.X, point.Y] = new HexElement(element.Value, newFormat);
return true;
}
@@ -1027,6 +1039,12 @@ namespace HavenSoft.HexManiac.Core.ViewModels {
return false;
}
+ private IReadOnlyList GetNewPointerAutocompleteOptions(string text) {
+ if (text.StartsWith(PointerStart.ToString()))text = text.Substring(1);
+ var options = Model.GetAutoCompleteAnchorNameOptions(text);
+ return AutoCompleteSelectionItem.Generate(options, -1);
+ }
+
private (Point start, Point end) GetSelectionSpan(Point p) {
var index = scroll.ViewPointToDataIndex(p);
var run = Model.GetNextRun(index);
diff --git a/src/HexManiac.Tests/AutoSearchTests.cs b/src/HexManiac.Tests/AutoSearchTests.cs
index 08e3033e..7658a89e 100644
--- a/src/HexManiac.Tests/AutoSearchTests.cs
+++ b/src/HexManiac.Tests/AutoSearchTests.cs
@@ -148,7 +148,8 @@ namespace HavenSoft.HexManiac.Tests {
if (modelCache.TryGetValue(name, out var cachedModel)) return cachedModel;
Skip.IfNot(File.Exists(name));
var data = File.ReadAllBytes(name);
- var model = new AutoSearchModel(data);
+ var metadata = new StoredMetadata(new string[0]);
+ var model = new AutoSearchModel(data, metadata);
modelCache[name] = model;
return model;
}
diff --git a/src/HexManiac.Tests/ChangeHistoryTests.cs b/src/HexManiac.Tests/ChangeHistoryTests.cs
index de2a2172..76f0c59a 100644
--- a/src/HexManiac.Tests/ChangeHistoryTests.cs
+++ b/src/HexManiac.Tests/ChangeHistoryTests.cs
@@ -9,7 +9,9 @@ using Xunit;
namespace HavenSoft.HexManiac.Tests {
public class FakeChangeToken : List, IChangeToken {
+#pragma warning disable 0067 // it's ok if events are never used after implementing an interface
public event EventHandler OnNewDataChange;
+#pragma warning restore 0067
public bool HasDataChange => Count > 0;
public FakeChangeToken() { }
public FakeChangeToken(IEnumerable data) : base(data) { }
diff --git a/src/HexManiac.Tests/HexManiac.Tests.csproj b/src/HexManiac.Tests/HexManiac.Tests.csproj
index 4f53fbc5..c8cbb7e8 100644
--- a/src/HexManiac.Tests/HexManiac.Tests.csproj
+++ b/src/HexManiac.Tests/HexManiac.Tests.csproj
@@ -80,6 +80,7 @@
+
diff --git a/src/HexManiac.Tests/ViewPortAutocompleteEditTests.cs b/src/HexManiac.Tests/ViewPortAutocompleteEditTests.cs
new file mode 100644
index 00000000..82186b91
--- /dev/null
+++ b/src/HexManiac.Tests/ViewPortAutocompleteEditTests.cs
@@ -0,0 +1,77 @@
+using HavenSoft.HexManiac.Core.Models;
+using HavenSoft.HexManiac.Core.ViewModels;
+using HavenSoft.HexManiac.Core.ViewModels.DataFormats;
+using System;
+using Xunit;
+
+namespace HavenSoft.HexManiac.Tests {
+ public class ViewPortAutocompleteEditTests {
+ private readonly ViewPort viewPort;
+
+ public ViewPortAutocompleteEditTests() {
+ var model = new PokemonModel(new byte[0x200]);
+ viewPort = new ViewPort("name.txt", model) { Height = 0x10, Width = 0x10 };
+
+ viewPort.SelectionStart = new Point(0, 8);
+ viewPort.Edit("^label ");
+
+ viewPort.SelectionStart = new Point(4, 8);
+ viewPort.Edit("^labwork ");
+
+ viewPort.SelectionStart = new Point(8, 8);
+ viewPort.Edit("^othertext ");
+
+ viewPort.SelectionStart = new Point(12, 8);
+ viewPort.Edit("^sometext ");
+
+ viewPort.SelectionStart = new Point();
+ }
+
+ [SkippableFact]
+ public void UnderEditLoosePointerGetsAutoComplete() {
+ Skip.If(true);
+ viewPort.Edit("(viewPort[0, 0].Format);
+ }
+
+ [SkippableFact]
+ public void AutocompleteWorksForEnums() {
+ Skip.If(true);
+ throw new NotImplementedException();
+ }
+ }
+}
diff --git a/src/HexManiac.WPF/Windows/MainWindow.xaml b/src/HexManiac.WPF/Windows/MainWindow.xaml
index 4879c628..956f45a7 100644
--- a/src/HexManiac.WPF/Windows/MainWindow.xaml
+++ b/src/HexManiac.WPF/Windows/MainWindow.xaml
@@ -227,7 +227,8 @@
-
+
+