prototype multifield in table

* remove 'Theme' elements from Xaml, we're not using them and the table tool is too slow, bloated with too many elements
* add capability for "multi-field" in table tool: multiple fields get gathered together into a single textbox for performance. It doesn't look as nice, but it's faster. Currently turned off
* Turn off delayed table loading while prototyping this feature. Easy to turn back on later.
This commit is contained in:
haven1433
2024-10-06 11:30:00 -05:00
parent a8716acd53
commit ffb94c2dc3
5 changed files with 161 additions and 57 deletions

View File

@@ -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<FieldArrayElementViewModel> 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<AutocompleteItem> GetAutoComplete(string line, int caretLineIndex, int caretCharacterIndex) {
var parts = line.Split(':', 2);
if (parts.Length != 2) return Array.Empty<AutocompleteItem>();
var name = parts[0].Trim();
var field = fields.FirstOrDefault(f => f.Name == name);
if (field == null) return Array.Empty<AutocompleteItem>();
// 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<AutocompleteItem>();
}
}
}

View File

@@ -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<IArrayElementViewModel> Members { get; } = new();
public Action<IStreamArrayElementViewModel> ForwardModelChanged { get; init; }
public Action<IStreamArrayElementViewModel> 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) {

View File

@@ -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<IArrayElementViewModel> 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,
};

View File

@@ -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) {

View File

@@ -388,7 +388,6 @@
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Rectangle Width="1" Margin="-2,-2,0,-2" HorizontalAlignment="Left" Fill="{Binding Theme, Converter={StaticResource Theme}}" />
<hsg3hv:AngleBorder Grid.Column="1" Direction="Out">
<TextBlock Text="{Binding Name}" />
</hsg3hv:AngleBorder>
@@ -414,7 +413,6 @@
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Rectangle HorizontalAlignment="Left" Width="1" Fill="{Binding Theme, Converter={StaticResource Theme}}" />
<hsg3hv:AngleBorder Grid.Column="1" Direction="Left">
<TextBlock Text="{Binding Name}"/>
</hsg3hv:AngleBorder>
@@ -433,7 +431,6 @@
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<Rectangle HorizontalAlignment="Left" Width="1" Margin="-2,-2,0,-2" Fill="{Binding Theme, Converter={StaticResource Theme}}" />
<hsg3hv:AngleBorder Grid.Column="1" Direction="Left">
<TextBlock Text="{Binding Name}" />
</hsg3hv:AngleBorder>
@@ -466,7 +463,6 @@
</DataTemplate>
<DataTemplate DataType="{x:Type hsg3hvmtr:BitListArrayElementViewModel}">
<Grid HorizontalAlignment="Stretch" Visibility="{Binding Visible, Converter={StaticResource BoolToVisibility}}">
<Rectangle HorizontalAlignment="Left" Width="1" Fill="{Binding Theme, Converter={StaticResource Theme}}" />
<StackPanel>
<Grid HorizontalAlignment="Stretch" Margin="2">
<Grid.ColumnDefinitions>
@@ -524,29 +520,27 @@
</DataTemplate>
<DataTemplate DataType="{x:Type hsg3hvmtr:StreamElementViewModel}">
<Grid HorizontalAlignment="Stretch" Visibility="{Binding Visible, Converter={StaticResource BoolToVisibility}}">
<Rectangle Width="1" HorizontalAlignment="Left" Fill="{Binding Theme, Converter={StaticResource Theme}}" />
<StackPanel ToolTip="{Binding ParentName}">
<hsg3hv:CommonTableStreamControl />
<Grid Margin="20,2,2,2" Background="{DynamicResource Backlight}">
<TextBox Name="StreamTextBox" UndoLimit="0" Text="{Binding Content, UpdateSourceTrigger=PropertyChanged}"
Background="Transparent"
VerticalAlignment="Top"
CaretBrush="{DynamicResource Secondary}"
AcceptsReturn="True" FontFamily="Consolas"
Visibility="{Binding ShowContent, Converter={StaticResource BoolToVisibility}}">
<TextBox.InputBindings>
<KeyBinding Modifiers="Ctrl" Key="Z" Command="{Binding Undo}"/>
<KeyBinding Modifiers="Ctrl" Key="Y" Command="{Binding Redo}"/>
</TextBox.InputBindings>
</TextBox>
<hsg3hv:AutocompleteOverlay Target="{Binding ElementName=StreamTextBox}"/>
</Grid>
</StackPanel>
<StackPanel ToolTip="{Binding ParentName}">
<hsg3hv:CommonTableStreamControl />
<Grid Margin="20,2,2,2" Background="{DynamicResource Backlight}">
<TextBox Name="StreamTextBox" UndoLimit="0" Text="{Binding Content, UpdateSourceTrigger=PropertyChanged}"
Background="Transparent"
VerticalAlignment="Top"
CaretBrush="{DynamicResource Secondary}"
AcceptsReturn="True" FontFamily="Consolas"
Visibility="{Binding ShowContent, Converter={StaticResource BoolToVisibility}}">
<TextBox.InputBindings>
<KeyBinding Modifiers="Ctrl" Key="Z" Command="{Binding Undo}"/>
<KeyBinding Modifiers="Ctrl" Key="Y" Command="{Binding Redo}"/>
</TextBox.InputBindings>
</TextBox>
<hsg3hv:AutocompleteOverlay Target="{Binding ElementName=StreamTextBox}"/>
</Grid>
</StackPanel>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type hsg3hvmtr:SpriteElementViewModel}">
<Grid Visibility="{Binding Visible, Converter={StaticResource BoolToVisibility}}">
<Rectangle Width="1" HorizontalAlignment="Left" Fill="{Binding Theme, Converter={StaticResource Theme}}" />
<StackPanel>
<hsg3hv:CommonTableStreamControl />
<hsg3hv:AngleBorder Content="Palette Selection" FontSize="10" Direction="Out" Visibility="{Binding HasMultiplePalettes, Converter={StaticResource BoolToVisibility}}" HorizontalAlignment="Center" />
@@ -596,29 +590,24 @@
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type hsg3hvmtr:SpriteIndicatorElementViewModel}">
<Grid HorizontalAlignment="Stretch" Visibility="{Binding Visible, Converter={StaticResource BoolToVisibility}}">
<Rectangle Width="1" HorizontalAlignment="Left" Fill="{Binding Theme, Converter={StaticResource Theme}}" />
<hsg3hv:PixelImage SnapsToDevicePixels="True" DataContext="{Binding Image}" />
</Grid>
<hsg3hv:PixelImage SnapsToDevicePixels="True" DataContext="{Binding Image}" Visibility="{Binding Visible, Converter={StaticResource BoolToVisibility}}" />
</DataTemplate>
<DataTemplate DataType="{x:Type hsg3hvmtr:PaletteElementViewModel}">
<Grid HorizontalAlignment="Stretch" Visibility="{Binding Visible, Converter={StaticResource BoolToVisibility}}">
<Rectangle Width="1" HorizontalAlignment="Left" Fill="{Binding Theme, Converter={StaticResource Theme}}" />
<StackPanel PreviewMouseDown="ActivatePalette">
<hsg3hv:CommonTableStreamControl />
<hsg3hv:PaletteControl DataContext="{Binding Colors}" />
<DockPanel Visibility="{Binding ShowPageControls, Converter={StaticResource BoolToVisibility}}" LastChildFill="False">
<hsg3hv:AngleButton MinWidth="20" Content="-" Direction="Left" Command="{Binding DeletePage}" HorizontalAlignment="Center" ToolTip="Delete the current page of this sprite and palette" />
<hsg3hv:AngleButton MinWidth="20" Content="+" Direction="Right" Command="{Binding AddPage}" DockPanel.Dock="Right" ToolTip="Add a new page to this sprite and palette" />
<hsg3hv:AngleButton MinWidth="60" Content="Previous" Direction="Left" DockPanel.Dock="Left" Click="PageMovePrevious" IsEnabled="{Binding CanMovePrevious}" />
<hsg3hv:AngleButton MinWidth="60" Content="Next" Direction="Right" DockPanel.Dock="Right" Click="PageMoveNext" IsEnabled="{Binding CanMoveNext}" />
</DockPanel>
</StackPanel>
<StackPanel PreviewMouseDown="ActivatePalette">
<hsg3hv:CommonTableStreamControl />
<hsg3hv:PaletteControl DataContext="{Binding Colors}" />
<DockPanel Visibility="{Binding ShowPageControls, Converter={StaticResource BoolToVisibility}}" LastChildFill="False">
<hsg3hv:AngleButton MinWidth="20" Content="-" Direction="Left" Command="{Binding DeletePage}" HorizontalAlignment="Center" ToolTip="Delete the current page of this sprite and palette" />
<hsg3hv:AngleButton MinWidth="20" Content="+" Direction="Right" Command="{Binding AddPage}" DockPanel.Dock="Right" ToolTip="Add a new page to this sprite and palette" />
<hsg3hv:AngleButton MinWidth="60" Content="Previous" Direction="Left" DockPanel.Dock="Left" Click="PageMovePrevious" IsEnabled="{Binding CanMovePrevious}" />
<hsg3hv:AngleButton MinWidth="60" Content="Next" Direction="Right" DockPanel.Dock="Right" Click="PageMoveNext" IsEnabled="{Binding CanMoveNext}" />
</DockPanel>
</StackPanel>
</Grid>
</DataTemplate>
<DataTemplate DataType="{x:Type hsg3hvmtr:TrainerPokemonTeamElementViewModel}">
<Grid HorizontalAlignment="Stretch" Visibility="{Binding Visible, Converter={StaticResource BoolToVisibility}}">
<Rectangle Width="1" HorizontalAlignment="Left" Fill="{Binding Theme, Converter={StaticResource Theme}}" />
<StackPanel ToolTip="{Binding ParentName}">
<hsg3hv:CommonTableStreamControl />
<Grid Margin="20,2,2,2" Background="{DynamicResource Backlight}">
@@ -667,7 +656,6 @@
</DataTemplate>
<DataTemplate DataType="{x:Type hsg3hvmtr:TupleArrayElementViewModel}">
<Grid Visibility="{Binding Visible, Converter={StaticResource BoolToVisibility}}">
<Rectangle Width="1" HorizontalAlignment="Left" Fill="{Binding Theme, Converter={StaticResource Theme}}" />
<StackPanel>
<TextBlock Text="{Binding Name}" Margin="5,0"/>
<ItemsControl ItemsSource="{Binding Children}">
@@ -711,7 +699,6 @@
</DataTemplate>
<DataTemplate DataType="{x:Type hsg3hvmtr:CalculatedElementViewModel}">
<DockPanel HorizontalAlignment="Stretch" Visibility="{Binding Visible, Converter={StaticResource BoolToVisibility}}">
<Rectangle Width="1" DockPanel.Dock="Left" Fill="{Binding Theme, Converter={StaticResource Theme}}" />
<Grid Margin="1,2,2,2" DockPanel.Dock="Top">
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto" SharedSizeGroup="LeftColumn"/>
@@ -750,15 +737,29 @@
</DockPanel>
</DataTemplate>
<DataTemplate DataType="{x:Type hsg3hvmtr:OffsetRenderViewModel}">
<Grid HorizontalAlignment="Stretch" Visibility="{Binding Visible, Converter={StaticResource BoolToVisibility}}">
<Rectangle Width="1" HorizontalAlignment="Left" Fill="{Binding Theme, Converter={StaticResource Theme}}" />
<hsg3hv:PixelImage SnapsToDevicePixels="True" Margin="1,0,0,0"
MouseDown="OffsetRenderMouseDown" MouseMove="OffsetRenderMouseMove" MouseUp="OffsetRenderMouseUp" />
</Grid>
<hsg3hv:PixelImage SnapsToDevicePixels="True" Margin="1,0,0,0" Visibility="{Binding Visible, Converter={StaticResource BoolToVisibility}}"
MouseDown="OffsetRenderMouseDown" MouseMove="OffsetRenderMouseMove" MouseUp="OffsetRenderMouseUp" />
</DataTemplate>
<DataTemplate DataType="{x:Type hsg3hvmtr:PythonButtonElementViewModel}">
<hsg3hv:AngleButton Direction="Out" Content="{Binding Name}" Margin="4" Command="{hsv:MethodCommand Execute}" ToolTip="{Binding Tooltip}" />
</DataTemplate>
<DataTemplate DataType="{x:Type hsg3hvmtr:MultiFieldArrayElementViewModel}">
<StackPanel Visibility="{Binding Visible, Converter={StaticResource BoolToVisibility}}">
<Grid Margin="20,2,2,2" Background="{DynamicResource Backlight}">
<TextBox Name="FieldsTextBox" UndoLimit="0" Text="{Binding Content, UpdateSourceTrigger=PropertyChanged}"
Background="Transparent"
VerticalAlignment="Top"
CaretBrush="{DynamicResource Secondary}"
AcceptsReturn="True" FontFamily="Consolas">
<TextBox.InputBindings>
<KeyBinding Modifiers="Ctrl" Key="Z" Command="{Binding Undo}"/>
<KeyBinding Modifiers="Ctrl" Key="Y" Command="{Binding Redo}"/>
</TextBox.InputBindings>
</TextBox>
<!--<hsg3hv:AutocompleteOverlay Target="{Binding ElementName=FieldsTextBox}"/>-->
</Grid>
</StackPanel>
</DataTemplate>
</ItemsControl.Resources>
</ItemsControl>
</StackPanel>