mirror of
https://github.com/haven1433/HexManiacAdvance.git
synced 2026-10-02 13:16:35 -05:00
Add a search field to the text tool
The unit test and search logic implementation were actually pretty simple. However, WPF TextBox's don't natively support a way to show text highlight when the textbox doesn't have focus... so I had to implemnet my own focus. I did this by wrapping the TextBox in a Grid so I could overlay a Rectangle that uses the same highlight color. See TabView.xaml.cs for all the mathy bits.
This commit is contained in:
@@ -13,6 +13,7 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
|
||||
private readonly IToolTrayViewModel runner;
|
||||
private readonly StubCommand
|
||||
checkIsText = new StubCommand(),
|
||||
search = new StubCommand(),
|
||||
insertText = new StubCommand();
|
||||
|
||||
// if we're in the middle of updating ourselves, we may notify changes to other controls.
|
||||
@@ -37,16 +38,24 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
|
||||
}
|
||||
}
|
||||
|
||||
private string searchText;
|
||||
public string SearchText {
|
||||
get => searchText;
|
||||
set => TryUpdate(ref searchText, value);
|
||||
}
|
||||
|
||||
public ICommand CheckIsText => checkIsText;
|
||||
public ICommand Search => search;
|
||||
public ICommand InsertText => insertText;
|
||||
|
||||
private bool showMessage;
|
||||
private bool showMessage = true;
|
||||
public bool ShowMessage {
|
||||
get => showMessage;
|
||||
set {
|
||||
if (TryUpdate(ref showMessage, value)) {
|
||||
checkIsText.CanExecuteChanged?.Invoke(checkIsText, EventArgs.Empty);
|
||||
insertText.CanExecuteChanged?.Invoke(checkIsText, EventArgs.Empty);
|
||||
search.CanExecuteChanged?.Invoke(checkIsText, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -129,6 +138,21 @@ namespace HavenSoft.HexManiac.Core.ViewModels.Tools {
|
||||
Enabled = true;
|
||||
ShowMessage = false;
|
||||
};
|
||||
search.CanExecute = arg => !ShowMessage;
|
||||
search.Execute = arg => {
|
||||
var target = searchText.ToLower();
|
||||
if (target.Length == 0) return;
|
||||
var content = Content.ToLower();
|
||||
var start = Math.Min(Content.Length - target.Length, contentIndex);
|
||||
|
||||
var index = content.Substring(start + 1).IndexOf(target);
|
||||
if (index < 0) index = content.IndexOf(target);
|
||||
else index += start + 1;
|
||||
|
||||
if (index < 0) return;
|
||||
ContentIndex = index;
|
||||
ContentSelectionLength = target.Length;
|
||||
};
|
||||
}
|
||||
|
||||
public void RefreshContentAtAddress() {
|
||||
|
||||
@@ -494,6 +494,23 @@ namespace HavenSoft.HexManiac.Tests {
|
||||
Assert.Equal("000030", test.ViewPort.Headers[0]);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TextToolSearchTest() {
|
||||
var test = new BaseViewModelTestClass();
|
||||
test.ViewPort.Edit("FF @00 ^text\"\" Hello World Worble!");
|
||||
test.ViewPort.SelectionStart = new Point();
|
||||
var tool = test.ViewPort.Tools.StringTool;
|
||||
|
||||
tool.SearchText = "wor";
|
||||
tool.Search.Execute();
|
||||
Assert.Equal(6, tool.ContentIndex);
|
||||
Assert.Equal(3, tool.ContentSelectionLength);
|
||||
|
||||
tool.Search.Execute();
|
||||
Assert.Equal(12, tool.ContentIndex);
|
||||
Assert.Equal(3, tool.ContentSelectionLength);
|
||||
}
|
||||
|
||||
private static readonly ThumbParser parser;
|
||||
static ToolTests(){
|
||||
parser = new ThumbParser(File.ReadAllLines("Models/Code/armReference.txt"));
|
||||
|
||||
@@ -4,7 +4,8 @@
|
||||
xmlns:hsv="clr-namespace:HavenSoft.HexManiac.WPF.Resources"
|
||||
xmlns:hsg3hvmtr="clr-namespace:HavenSoft.HexManiac.Core.ViewModels.Tools;assembly=HexManiac.Core"
|
||||
xmlns:hsg3ww="clr-namespace:HavenSoft.HexManiac.WPF.Windows"
|
||||
xmlns:hsg3hv="clr-namespace:HavenSoft.HexManiac.WPF.Controls">
|
||||
xmlns:hsg3hv="clr-namespace:HavenSoft.HexManiac.WPF.Controls"
|
||||
DataContextChanged="HandleDataContextChanged">
|
||||
<UserControl.Resources>
|
||||
<BooleanToVisibilityConverter x:Key="BoolToVisibility" />
|
||||
<hsg3hv:IntegerToHexConverter x:Key="Hex"/>
|
||||
@@ -85,10 +86,26 @@
|
||||
<Button Margin="0,3" Command="{Binding StringTool.CheckIsText}" Content="Show As Text" HorizontalAlignment="Right" DockPanel.Dock="Top" Visibility="{Binding StringTool.ShowMessage, Converter={StaticResource BoolToVisibility}}"/>
|
||||
<Button Command="{Binding StringTool.InsertText}" Content="Insert New Text" HorizontalAlignment="Right" DockPanel.Dock="Top" Visibility="{Binding StringTool.ShowMessage, Converter={StaticResource BoolToVisibility}}"/>
|
||||
<TextBlock Text="Content:" Margin="5,30,0,0" DockPanel.Dock="Top"/>
|
||||
<TextBox Margin="5,5" AcceptsReturn="True" MinHeight="50" Width="255" TextWrapping="Wrap" FontFamily="Consolas" VerticalScrollBarVisibility="Auto" UndoLimit="0"
|
||||
SelectionChanged="StringToolContentSelectionChanged"
|
||||
IsEnabled="{Binding StringTool.Enabled}"
|
||||
Text="{Binding StringTool.Content, UpdateSourceTrigger=PropertyChanged}" />
|
||||
|
||||
<Button Command="{Binding StringTool.Search}" Margin="5,0" Content="Search" HorizontalAlignment="Right" DockPanel.Dock="Bottom" />
|
||||
<TextBox Margin="5,0" Width="255" FontFamily="Consolas" Text="{Binding StringTool.SearchText, UpdateSourceTrigger=PropertyChanged}" IsEnabled="{Binding StringTool.Enabled}" DockPanel.Dock="Bottom">
|
||||
<TextBox.InputBindings>
|
||||
<KeyBinding Key="Return" Command="{Binding StringTool.Search}"/>
|
||||
<KeyBinding Key="F3" Command="{Binding StringTool.Search}"/>
|
||||
</TextBox.InputBindings>
|
||||
</TextBox>
|
||||
<TextBlock Text="Search:" Margin="5,0" DockPanel.Dock="Bottom"/>
|
||||
|
||||
<Grid Margin="5,5" MinHeight="50" Width="255">
|
||||
<TextBox Name="StringToolTextBox" AcceptsReturn="True" TextWrapping="Wrap" FontFamily="Consolas" VerticalScrollBarVisibility="Auto" UndoLimit="0"
|
||||
SelectionChanged="StringToolContentSelectionChanged"
|
||||
LostFocus="ShowManualSelection"
|
||||
GotFocus="HideManualSelection"
|
||||
ScrollViewer.ScrollChanged="UpdateManualSelectionFromScroll"
|
||||
IsEnabled="{Binding StringTool.Enabled}"
|
||||
Text="{Binding StringTool.Content, UpdateSourceTrigger=PropertyChanged}" />
|
||||
<Rectangle Name="ManualHighlight" HorizontalAlignment="Stretch" VerticalAlignment="Top" Height="14" Fill="{DynamicResource Accent}" Opacity=".4"/>
|
||||
</Grid>
|
||||
</DockPanel>
|
||||
<DockPanel Width="265" Name="TableToolPanel">
|
||||
<DockPanel.Style>
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
using HavenSoft.HexManiac.Core.Models;
|
||||
using HavenSoft.HexManiac.Core;
|
||||
using HavenSoft.HexManiac.Core.Models;
|
||||
using HavenSoft.HexManiac.Core.ViewModels;
|
||||
using HavenSoft.HexManiac.Core.ViewModels.Tools;
|
||||
using System;
|
||||
using System.ComponentModel;
|
||||
using System.Linq;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
@@ -28,12 +30,80 @@ namespace HavenSoft.HexManiac.WPF.Controls {
|
||||
CodeModeSelector.ItemsSource = Enum.GetValues(typeof(CodeMode)).Cast<CodeMode>();
|
||||
}
|
||||
|
||||
#region Manual Selection Code
|
||||
|
||||
private void HideManualSelection(object sender, EventArgs e) => ManualHighlight.Visibility = Visibility.Collapsed;
|
||||
|
||||
private void ShowManualSelection(object sender, EventArgs e) {
|
||||
ManualHighlight.Visibility = Visibility.Visible;
|
||||
UpdateManualSelectionFromScroll(StringToolTextBox, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private void HandleDataContextChanged(object sender, DependencyPropertyChangedEventArgs e) {
|
||||
if (e.OldValue is ViewPort viewPortOld) {
|
||||
viewPortOld.Tools.StringTool.PropertyChanged -= HandleStringToolPropertyChanged;
|
||||
}
|
||||
if (e.NewValue is ViewPort viewPortNew) {
|
||||
viewPortNew.Tools.StringTool.PropertyChanged += HandleStringToolPropertyChanged;
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleStringToolPropertyChanged(object sender, PropertyChangedEventArgs e) {
|
||||
var tool = (PCSTool)sender;
|
||||
|
||||
StringToolTextBox.SelectionChanged -= StringToolContentSelectionChanged;
|
||||
if (e.PropertyName is nameof(PCSTool.ContentIndex)) {
|
||||
StringToolTextBox.SelectionStart = tool.ContentIndex;
|
||||
UpdateManualSelection(tool);
|
||||
}
|
||||
if (e.PropertyName is nameof(PCSTool.ContentSelectionLength)) {
|
||||
StringToolTextBox.SelectionLength = tool.ContentSelectionLength;
|
||||
UpdateManualSelection(tool);
|
||||
}
|
||||
StringToolTextBox.SelectionChanged += StringToolContentSelectionChanged;
|
||||
}
|
||||
|
||||
private void UpdateManualSelection(PCSTool tool) {
|
||||
var linesBeforeSelection = StringToolTextBox.Text.Substring(0, StringToolTextBox.SelectionStart).Split(Environment.NewLine).Length - 1;
|
||||
var totalLines = StringToolTextBox.Text.Split(Environment.NewLine).Length;
|
||||
var highestScroll = StringToolTextBox.ExtentHeight - StringToolTextBox.ViewportHeight;
|
||||
var verticalOffset = linesBeforeSelection * highestScroll / totalLines;
|
||||
StringToolTextBox.ScrollToVerticalOffset(verticalOffset);
|
||||
}
|
||||
|
||||
private void UpdateManualSelectionFromScroll(object sender, EventArgs e) {
|
||||
if (ManualHighlight.Visibility != Visibility.Visible) return;
|
||||
|
||||
var tools = StringToolTextBox.DataContext as ToolTray;
|
||||
if (tools == null || tools.StringTool == null) return;
|
||||
var tool = tools.StringTool;
|
||||
|
||||
var linesBeforeSelection = StringToolTextBox.Text.Substring(0, StringToolTextBox.SelectionStart).Split(Environment.NewLine).Length - 1;
|
||||
var totalLines = StringToolTextBox.Text.Split(Environment.NewLine).Length;
|
||||
var verticalOffset = StringToolTextBox.VerticalOffset;
|
||||
var lineHeight = StringToolTextBox.ExtentHeight / totalLines;
|
||||
var verticalStart = lineHeight * linesBeforeSelection - verticalOffset + 2;
|
||||
if (verticalStart < 0 || verticalStart > StringToolTextBox.ViewportHeight) return;
|
||||
|
||||
var selectionStart = StringToolTextBox.Text.Substring(0, StringToolTextBox.SelectionStart).Split(Environment.NewLine).Last().Length;
|
||||
const int fontWidth = 7; // FormattedText.Width gives more like 6.5, but 7 actually looks better.
|
||||
var horizontalStart = selectionStart * fontWidth + 2;
|
||||
var width = tool.ContentSelectionLength * fontWidth;
|
||||
ManualHighlight.Margin = new Thickness(horizontalStart, verticalStart, StringToolTextBox.ActualWidth - horizontalStart - width, 0);
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private void StringToolContentSelectionChanged(object sender, RoutedEventArgs e) {
|
||||
var textbox = (TextBox)sender;
|
||||
var tools = textbox.DataContext as ToolTray;
|
||||
if (tools == null || tools.StringTool == null) return;
|
||||
tools.StringTool.ContentIndex = textbox.SelectionStart;
|
||||
tools.StringTool.ContentSelectionLength = textbox.SelectionLength;
|
||||
var tool = tools.StringTool;
|
||||
|
||||
tool.PropertyChanged -= HandleStringToolPropertyChanged;
|
||||
tool.ContentIndex = textbox.SelectionStart;
|
||||
tool.ContentSelectionLength = textbox.SelectionLength;
|
||||
tool.PropertyChanged += HandleStringToolPropertyChanged;
|
||||
}
|
||||
|
||||
private void HeaderMouseDown(object sender, MouseButtonEventArgs e) {
|
||||
|
||||
Reference in New Issue
Block a user