diff --git a/src/HexManiac.Core/ViewModels/Tools/IArrayElementViewModel.cs b/src/HexManiac.Core/ViewModels/Tools/IArrayElementViewModel.cs index 9882146a..3dcb9c97 100644 --- a/src/HexManiac.Core/ViewModels/Tools/IArrayElementViewModel.cs +++ b/src/HexManiac.Core/ViewModels/Tools/IArrayElementViewModel.cs @@ -2,7 +2,10 @@ using HavenSoft.HexManiac.Core.Models.Runs; using HavenSoft.HexManiac.Core.ViewModels.DataFormats; using System; -using System.Globalization; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Windows.Input; namespace HavenSoft.HexManiac.Core.ViewModels.Tools { public enum ElementContentViewModelType { @@ -46,7 +49,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { private string theme; public string Theme { get => theme; set => Set(ref theme, value); } public bool IsInError => errorText != string.Empty; - string errorText; + private string errorText; public string ErrorText { get => errorText; set { @@ -318,4 +321,78 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { return text; } } + + public class MultiFieldArrayElementViewModel : ViewModelCore, IArrayElementViewModel { + private string theme, content; + private bool visible = true; + private List fields = new(); + + public string Theme { get => theme; set => Set(ref theme, value); } + public bool Visible { get => visible; set => Set(ref visible, value); } + public bool IsInError => false; + public string ErrorText => string.Empty; + public int ZIndex => 0; + + public event EventHandler DataChanged; + public event EventHandler DataSelected; + + public ICommand Undo { get; } + public ICommand Redo { get; } + + public MultiFieldArrayElementViewModel(ViewPort port) => (Undo, Redo) = (port.Undo, port.Redo); + + public void Add(FieldArrayElementViewModel field) { + fields.Add(field); + RecalculateBody(); + } + + public string Content { + get => content; + set => Set(ref content, value, oldValue => { + var lines = content.SplitLines(); + for (int i = 0; i < fields.Count; i++) { + if (i >= lines.Length) break; + var parts = lines[i].Split(':', 2); + if (parts.Length == 1) continue; + fields[i].Content = parts[1].Trim(); + } + DataChanged?.Invoke(this, EventArgs.Empty); + }); + } + + public bool TryCopy(IArrayElementViewModel other) { + if (other is not MultiFieldArrayElementViewModel that) return false; + return content == that.content && fields.Count == that.fields.Count && fields.Count.Range().All(i => fields[i].TryCopy(that.fields[i])); + } + + private void RecalculateBody() { + var longestName = fields.Select(f => f.Name.Length).Max(); + var content = new StringBuilder(); + bool first = true; + foreach (var field in fields) { + if (!first) content.AppendLine(); + else first = false; + + content.Append(field.Name.PadRight(longestName, ' ')); + content.Append(": "); + content.Append(field.Content); + } + this.content = content.ToString(); + NotifyPropertyChanged(nameof(Content)); + } + + public IReadOnlyList GetAutoComplete(string line, int caretLineIndex, int caretCharacterIndex) { + var parts = line.Split(':', 2); + if (parts.Length != 2) return Array.Empty(); + var name = parts[0].Trim(); + var field = fields.FirstOrDefault(f => f.Name == name); + if (field == null) return Array.Empty(); + + // right now, the field is always something like free text, free number, or free color + // no auto-complete makes sense + // but this method is here as a stub in case we add combobox content into the MultiField. + + return Array.Empty(); + } + } } diff --git a/src/HexManiac.Core/ViewModels/Tools/TableGroupViewModel.cs b/src/HexManiac.Core/ViewModels/Tools/TableGroupViewModel.cs index 9e165c7e..c91cd9e9 100644 --- a/src/HexManiac.Core/ViewModels/Tools/TableGroupViewModel.cs +++ b/src/HexManiac.Core/ViewModels/Tools/TableGroupViewModel.cs @@ -36,12 +36,14 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { public bool DisplayHeader => GroupName != DefaultName; public string GroupName { get => groupName; set => Set(ref groupName, value, old => NotifyPropertyChanged(nameof(DisplayHeader))); } + private readonly ViewPort viewPort; + public ObservableCollection Members { get; } = new(); public Action ForwardModelChanged { get; init; } public Action ForwardModelDataMoved { get; init; } - public TableGroupViewModel() { GroupName = DefaultName; } + public TableGroupViewModel(ViewPort viewPort) { GroupName = DefaultName; this.viewPort = viewPort; } public bool IsOpen => isOpen; @@ -51,26 +53,48 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { isOpen = true; } + const bool UseMultiFieldFeature = false; + public void Add(IArrayElementViewModel child, string theme = null) { child.Theme = theme; if (currentMember == Members.Count) { - Members.Add(child); + if (child is FieldArrayElementViewModel newField) { + if (Members[Members.Count - 1] is FieldArrayElementViewModel field && UseMultiFieldFeature) { + var multi = new MultiFieldArrayElementViewModel(viewPort); + multi.Add(field); + multi.Add(newField); + Members[Members.Count - 1] = multi; + } else if (Members[Members.Count - 1] is MultiFieldArrayElementViewModel multi) { + multi.Add(newField); + } else { + Members.Add(child); + currentMember++; + } + } else { + Members.Add(child); + currentMember++; + } + } else if (UseMultiFieldFeature && currentMember > 0 && Members[currentMember - 1] is FieldArrayElementViewModel existingField && child is FieldArrayElementViewModel newField) { + var multi = new MultiFieldArrayElementViewModel(viewPort); + multi.Add(existingField); + multi.Add(newField); + Members[currentMember - 1] = multi; + } else if (currentMember > 0 && Members[currentMember-1] is MultiFieldArrayElementViewModel multi && child is FieldArrayElementViewModel newField1) { + multi.Add(newField1); } else { - // using var scope = Members[currentMember].SilencePropertyNotifications(); if (!Members[currentMember].TryCopy(child)) { Members[currentMember] = child; } else { Members[currentMember].Theme = child.Theme; } + currentMember += 1; } - currentMember += 1; } public void Close() { if (!isOpen) return; while (Members.Count > currentMember) Members.RemoveAt(Members.Count - 1); isOpen = false; - // Members.RaiseRefresh(); } public void AddChildrenFromTable(ViewPort viewPort, Selection selection, ITableRun table, int index, string theme, SplitterArrayElementViewModel header, TableGroupViewModel helperGroup, int splitPortion = -1) { diff --git a/src/HexManiac.Core/ViewModels/Tools/TableTool.cs b/src/HexManiac.Core/ViewModels/Tools/TableTool.cs index ec298b77..da3ea06f 100644 --- a/src/HexManiac.Core/ViewModels/Tools/TableTool.cs +++ b/src/HexManiac.Core/ViewModels/Tools/TableTool.cs @@ -188,7 +188,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { this.toolTray = toolTray; this.dispatcher = dispatcher; loadMapUsageTimer = dispatcher.CreateDelayTimer(); - dataChangedTimer = dispatcher.CreateDelayTimer(); + dataChangedTimer = new ImmediateWorkTimer(); // dispatcher.CreateDelayTimer(); CurrentElementSelector = new FilteringComboOptions(); CurrentElementSelector.Bind(nameof(FilteringComboOptions.ModelValue), UpdateViewPortSelectionFromTableComboBoxIndex); Groups = new(); @@ -286,7 +286,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { public IList Children => Groups.SelectMany(group => group.Members).ToList(); private void AddGroup() { - Groups.Add(new TableGroupViewModel { + Groups.Add(new TableGroupViewModel(viewPort) { ForwardModelChanged = element => element.DataChanged += ForwardModelChanged, ForwardModelDataMoved = element => element.DataMoved += ForwardModelDataMoved, }); @@ -404,7 +404,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { streamGroup = Groups[1]; streamGroup.Open(); } else { - streamGroup = new TableGroupViewModel { + streamGroup = new TableGroupViewModel(viewPort) { ForwardModelChanged = element => element.DataChanged += ForwardModelChanged, ForwardModelDataMoved = element => element.DataMoved += ForwardModelDataMoved, }; @@ -429,7 +429,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools { streamGroup.GroupName = TableGroupViewModel.DefaultName; streamGroup.Open(); } else { - streamGroup = new TableGroupViewModel { + streamGroup = new TableGroupViewModel(viewPort) { ForwardModelChanged = element => element.DataChanged += ForwardModelChanged, ForwardModelDataMoved = element => element.DataMoved += ForwardModelDataMoved, }; diff --git a/src/HexManiac.WPF/Controls/AutocompleteOverlay.xaml.cs b/src/HexManiac.WPF/Controls/AutocompleteOverlay.xaml.cs index fc612f15..0414b2bf 100644 --- a/src/HexManiac.WPF/Controls/AutocompleteOverlay.xaml.cs +++ b/src/HexManiac.WPF/Controls/AutocompleteOverlay.xaml.cs @@ -92,6 +92,8 @@ namespace HavenSoft.HexManiac.WPF.Controls { } else { throw new NotImplementedException(); } + } else if (DataContext is MultiFieldArrayElementViewModel multi) { + getAutocomplete = multi.GetAutoComplete; } else if (DataContext is TrainerTeamViewModel team) { getAutocomplete = team.GetTrainerAutocomplete; } else if (DataContext is CodeBody body) { diff --git a/src/HexManiac.WPF/Controls/TabView.xaml b/src/HexManiac.WPF/Controls/TabView.xaml index 34bc4181..267898db 100644 --- a/src/HexManiac.WPF/Controls/TabView.xaml +++ b/src/HexManiac.WPF/Controls/TabView.xaml @@ -388,7 +388,6 @@ - @@ -414,7 +413,6 @@ - @@ -433,7 +431,6 @@ - @@ -466,7 +463,6 @@ - @@ -524,29 +520,27 @@ - - - - - - - - - - - - - + + + + + + + + + + + + - @@ -596,29 +590,24 @@ - - - - + - - - - - - - - - - - + + + + + + + + + + - @@ -667,7 +656,6 @@ - @@ -711,7 +699,6 @@ - @@ -750,15 +737,29 @@ - - - - + + + + + + + + + + + + + +