mirror of
https://github.com/haven1433/HexManiacAdvance.git
synced 2026-05-25 06:49:03 -05:00
first part of autocomplete is in
mostly the tests need to make the pointers actually fill the model need to make the UI still need to handle enums still
This commit is contained in:
parent
32020c62da
commit
ebc9d1f33d
|
|
@ -53,6 +53,7 @@
|
|||
<Compile Include="Models\Runs\ArrayRunElementSegment.cs" />
|
||||
<Compile Include="Models\Runs\AsciiRun.cs" />
|
||||
<Compile Include="Models\Runs\HeaderRow.cs" />
|
||||
<Compile Include="ViewModels\AutoCompleteSelectionItem.cs" />
|
||||
<Compile Include="ViewModels\Theme.cs" />
|
||||
<Compile Include="ViewModels\Tools\IArrayElementViewModel.cs" />
|
||||
<Compile Include="Models\Runs\IFormattedRun.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()));
|
||||
|
||||
|
|
|
|||
|
|
@ -8,6 +8,8 @@ namespace HavenSoft.HexManiac.Core.Models {
|
|||
public IReadOnlyList<StoredAnchor> NamedAnchors { get; }
|
||||
public IReadOnlyList<StoredUnmappedPointers> UnmappedPointers { get; }
|
||||
|
||||
public bool IsEmpty => NamedAnchors.Count == 0 && UnmappedPointers.Count == 0;
|
||||
|
||||
public StoredMetadata(IReadOnlyList<StoredAnchor> anchors, IReadOnlyList<StoredUnmappedPointers> unmappedPointers) {
|
||||
NamedAnchors = anchors;
|
||||
UnmappedPointers = unmappedPointers;
|
||||
|
|
|
|||
33
src/HexManiac.Core/ViewModels/AutoCompleteSelectionItem.cs
Normal file
33
src/HexManiac.Core/ViewModels/AutoCompleteSelectionItem.cs
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.ComponentModel;
|
||||
|
||||
namespace HavenSoft.HexManiac.Core.ViewModels {
|
||||
public class AutoCompleteSelectionItem : IEquatable<AutoCompleteSelectionItem>, 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<AutoCompleteSelectionItem> Generate(IEnumerable<string> options, int selectionIndex) {
|
||||
var list = new List<AutoCompleteSelectionItem>();
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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<AutoCompleteSelectionItem> AutocompleteOptions { get; }
|
||||
public UnderEdit(IDataFormat original, string text, int editWidth = 1, IReadOnlyList<AutoCompleteSelectionItem> 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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<AutoCompleteSelectionItem> CreateAutoCompleteOptions(IEnumerable<string> options, int length) {
|
||||
if (completionIndex >= length) {
|
||||
completionIndex = length - 1;
|
||||
NotifyPropertyChanged(nameof(CompletionIndex));
|
||||
}
|
||||
var list = new List<AutoCompleteSelectionItem>(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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<string> OnError;
|
||||
public event PropertyChangedEventHandler PropertyChanged;
|
||||
#pragma warning restore 0067
|
||||
|
||||
public void Schedule(Action action) => action();
|
||||
public void RefreshContent() { }
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -59,7 +59,9 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
|
|||
|
||||
public event EventHandler<IFormattedRun> 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<ModelDelta> history, IToolTrayViewModel toolTray) {
|
||||
this.model = model;
|
||||
|
|
|
|||
|
|
@ -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<AutoCompleteSelectionItem> 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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,9 @@ using Xunit;
|
|||
|
||||
namespace HavenSoft.HexManiac.Tests {
|
||||
public class FakeChangeToken : List<int>, 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<int> data) : base(data) { }
|
||||
|
|
|
|||
|
|
@ -80,6 +80,7 @@
|
|||
<Compile Include="NavigationTests.cs" />
|
||||
<Compile Include="StringModelTests.cs" />
|
||||
<Compile Include="ToolTests.cs" />
|
||||
<Compile Include="ViewPortAutocompleteEditTests.cs" />
|
||||
<Compile Include="ViewPortCursorTests.cs" />
|
||||
<Compile Include="ViewPortEditTests.cs" />
|
||||
<Compile Include="ViewPortSaveTests.cs" />
|
||||
|
|
|
|||
77
src/HexManiac.Tests/ViewPortAutocompleteEditTests.cs
Normal file
77
src/HexManiac.Tests/ViewPortAutocompleteEditTests.cs
Normal file
|
|
@ -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("<labe");
|
||||
|
||||
var format = (UnderEdit)viewPort[0, 0].Format;
|
||||
Assert.Single(format.AutocompleteOptions);
|
||||
|
||||
format = (UnderEdit)viewPort[1, 0].Format;
|
||||
Assert.Null(format.AutocompleteOptions);
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public void BackspaceWidensAutocompleteResults() {
|
||||
Skip.If(true);
|
||||
viewPort.Edit("<labe");
|
||||
viewPort.Edit(ConsoleKey.Backspace);
|
||||
|
||||
var format = (UnderEdit)viewPort[0, 0].Format;
|
||||
Assert.Equal(2, format.AutocompleteOptions.Count);
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public void UpDownDuringAutoCompleteSelectsResults() {
|
||||
Skip.If(true);
|
||||
viewPort.Edit("<lab");
|
||||
viewPort.MoveSelectionStart.Execute(Direction.Down);
|
||||
|
||||
var format = (UnderEdit)viewPort[0, 0].Format;
|
||||
Assert.True(format.AutocompleteOptions[0].IsSelected);
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public void EmptyAutoCompleteArrowsActNormally() {
|
||||
Skip.If(true);
|
||||
viewPort.Edit("<xyz");
|
||||
viewPort.MoveSelectionStart.Execute(Direction.Down);
|
||||
|
||||
Assert.IsNotType<UnderEdit>(viewPort[0, 0].Format);
|
||||
}
|
||||
|
||||
[SkippableFact]
|
||||
public void AutocompleteWorksForEnums() {
|
||||
Skip.If(true);
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -227,7 +227,8 @@
|
|||
</Button>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
<Grid>
|
||||
<Border DockPanel.Dock="Top" Height="1" Background="{DynamicResource Backlight}"/>
|
||||
<Grid Background="{DynamicResource Background}">
|
||||
<hsg3hv:StartScreen>
|
||||
<hsg3hv:StartScreen.Style>
|
||||
<Style TargetType="FrameworkElement">
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
</Grid.ColumnDefinitions>
|
||||
<StackPanel HorizontalAlignment="Left" VerticalAlignment="Top" Margin="0,0,0,10">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<TextBlock Text="White:" Foreground="{Binding DarkColor}"/>
|
||||
<TextBlock Text="Light Background / Text:" Foreground="{Binding DarkColor}"/>
|
||||
<TextBox Foreground="{Binding DarkColor}" Background="{Binding HighlightLight}" Text="{Binding LightColor, UpdateSourceTrigger=PropertyChanged}" Width="150" Margin="5,0"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10">
|
||||
|
|
@ -23,7 +23,7 @@
|
|||
</StackPanel>
|
||||
<StackPanel HorizontalAlignment="Right" VerticalAlignment="Top" Grid.Column="1">
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right">
|
||||
<TextBlock Text="Black:" Foreground="{Binding LightColor}"/>
|
||||
<TextBlock Text="Dark Background / Text:" Foreground="{Binding LightColor}"/>
|
||||
<TextBox Foreground="{Binding LightColor}" Background="{Binding HighlightDark}" Text="{Binding DarkColor, UpdateSourceTrigger=PropertyChanged}" Width="150" Margin="5,0"/>
|
||||
</StackPanel>
|
||||
<StackPanel Orientation="Horizontal" HorizontalAlignment="Right" Margin="0,10">
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user