Skip to content

Commit

Permalink
config_tool: add support for searching
Browse files Browse the repository at this point in the history
This adds a search box to the config tool to allow filtering properties
by keywords.

Resolves #1889.
  • Loading branch information
lahm86 committed Nov 16, 2024
1 parent 36eec3d commit 1cbded1
Show file tree
Hide file tree
Showing 15 changed files with 396 additions and 52 deletions.
1 change: 1 addition & 0 deletions docs/tr1/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
- added support for key/puzzle/pickup descriptions, allowing players to examine said items in the inventory (#1821)
- added an option to fix inventory item usage duplication (#1586)
- added optional automatic key/puzzle inventory item pre-selection (#1884)
- added a search feature to the config tool (#1889)
- changed OpenGL backend to use version 3.3, with fallback to 2.1 if initialization fails (#1738)
- changed text backend to accept named sequences. Currently supported sequences (limited by the sprites available in OG):
- `\{umlaut}`
Expand Down
1 change: 1 addition & 0 deletions docs/tr2/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
- added support for custom levels to enforce values for any config setting (#1846)
- added an option to fix inventory item usage duplication (#1586)
- added optional automatic key/puzzle inventory item pre-selection (#1884)
- added a search feature to the config tool (#1889)
- fixed depth problems when drawing certain rooms (#1853, regression from 0.6)
- fixed Lara getting stuck in her hit animation if she is hit while mounting the boat or skidoo (#1606)
- fixed being unable to go from surface swimming to underwater swimming without first stopping (#1863, regression from 0.6)
Expand Down
198 changes: 151 additions & 47 deletions tools/config/TRX_ConfigToolLib/Controls/TRXConfigWindow.xaml
Original file line number Diff line number Diff line change
Expand Up @@ -58,55 +58,130 @@
<utils:RelayKeyBinding
CommandBinding="{Binding GitHubCommand}"
Key="F11" />
<utils:RelayKeyBinding
CommandBinding="{Binding BeginSearchCommand}"
Modifiers="Ctrl"
Key="F" />
</Window.InputBindings>

<DockPanel>
<Menu
DockPanel.Dock="Top"
Background="{DynamicResource {x:Static SystemColors.WindowBrush}}">
<MenuItem Header="{Binding ViewText[menu_file]}">
<MenuItem
Command="{Binding OpenCommand}"
Header="{Binding ViewText[command_open]}"
InputGestureText="Ctrl+O"/>
<MenuItem
Command="{Binding ReloadCommand}"
Header="{Binding ViewText[command_reload]}"
InputGestureText="F5"/>
<Separator/>
<MenuItem
Command="{Binding SaveCommand}"
Header="{Binding ViewText[command_save]}"
InputGestureText="Ctrl+S"/>
<MenuItem
Command="{Binding SaveAsCommand}"
Header="{Binding ViewText[command_save_as]}"
InputGestureText="Ctrl+Alt+S"/>
<Separator/>
<MenuItem
Command="{Binding ExitCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}"
Header="{Binding ViewText[command_exit]}"
InputGestureText="Alt+F4"/>
</MenuItem>

<MenuItem Header="{Binding ViewText[menu_tools]}">
<MenuItem
Command="{Binding RestoreDefaultsCommand}"
Header="{Binding ViewText[command_restore]}"/>
</MenuItem>

<MenuItem Header="{Binding ViewText[menu_help]}">
<MenuItem
Command="{Binding GitHubCommand}"
Header="{Binding ViewText[command_github]}"
InputGestureText="F11"/>
<Separator/>
<MenuItem
Command="{Binding AboutCommand}"
Header="{Binding ViewText[command_about]}"/>
</MenuItem>
</Menu>
<DockPanel DockPanel.Dock="Top">
<Menu
Background="{DynamicResource {x:Static SystemColors.WindowBrush}}">
<MenuItem Header="{Binding ViewText[menu_file]}">
<MenuItem
Command="{Binding OpenCommand}"
Header="{Binding ViewText[command_open]}"
InputGestureText="Ctrl+O"/>
<MenuItem
Command="{Binding ReloadCommand}"
Header="{Binding ViewText[command_reload]}"
InputGestureText="F5"/>
<Separator/>
<MenuItem
Command="{Binding SaveCommand}"
Header="{Binding ViewText[command_save]}"
InputGestureText="Ctrl+S"/>
<MenuItem
Command="{Binding SaveAsCommand}"
Header="{Binding ViewText[command_save_as]}"
InputGestureText="Ctrl+Alt+S"/>
<Separator/>
<MenuItem
Command="{Binding ExitCommand}"
CommandParameter="{Binding RelativeSource={RelativeSource AncestorType=Window}}"
Header="{Binding ViewText[command_exit]}"
InputGestureText="Alt+F4"/>
</MenuItem>

<MenuItem Header="{Binding ViewText[menu_tools]}">
<MenuItem
Command="{Binding RestoreDefaultsCommand}"
Header="{Binding ViewText[command_restore]}"/>
</MenuItem>

<MenuItem Header="{Binding ViewText[menu_help]}">
<MenuItem
Command="{Binding GitHubCommand}"
Header="{Binding ViewText[command_github]}"
InputGestureText="F11"/>
<Separator/>
<MenuItem
Command="{Binding AboutCommand}"
Header="{Binding ViewText[command_about]}"/>
</MenuItem>
</Menu>

<Grid
DockPanel.Dock="Right"
Margin="0,7,7,0"
IsEnabled="{Binding IsEditorActive}">

<Grid.ColumnDefinitions>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>

<Border
Grid.Column="1"
BorderBrush="#666"
BorderThickness="1"
Background="{Binding SearchFailStatus, Converter={utils:ConditionalMarkupConverter TrueValue='#FFC7CE', FalseValue='White'}}">
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="300"/>
<ColumnDefinition Width="24"/>
</Grid.ColumnDefinitions>

<TextBox
HorizontalAlignment="Stretch"
VerticalAlignment="Center"
BorderThickness="0"
Background="Transparent"
Margin="0,1"
x:Name="SearchTermTextBox"
Text="{Binding Path=SearchText, UpdateSourceTrigger=PropertyChanged}">
<TextBox.Style>
<Style TargetType="{x:Type TextBox}">
<Style.Triggers>
<DataTrigger Binding="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.BeginSearch}" Value="True">
<Setter Property="FocusManager.FocusedElement" Value="{Binding ElementName=SearchTermTextBox}" />
</DataTrigger>
</Style.Triggers>
</Style>
</TextBox.Style>
</TextBox>

<TextBlock
IsHitTestVisible="False"
Text="{Binding ViewText[label_search]}"
Padding="4,0,0,0"
VerticalAlignment="Center"
HorizontalAlignment="Stretch"
Foreground="#666">
<TextBlock.Style>
<Style TargetType="{x:Type TextBlock}">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Text, ElementName=SearchTermTextBox}" Value="">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>

<Button
Grid.Column="2"
Command="{Binding CloseSearchCommand}"
Style="{StaticResource SmallButtonStyle}"
IsEnabled="{Binding IsSearchTextDefined}"
Content="../Resources/close.png"
ToolTip="{Binding ViewText[command_close_search]}"/>
</Grid>
</Border>
</Grid>
</DockPanel>

<StatusBar
DockPanel.Dock="Bottom">
Expand Down Expand Up @@ -163,7 +238,8 @@
IsEnabled="{Binding IsEditorActive}"
ItemsSource="{Binding Categories}"
SelectedItem="{Binding SelectedCategory, Mode=TwoWay}"
SelectionChanged="TabControl_SelectionChanged">
SelectionChanged="TabControl_SelectionChanged"
Visibility="{Binding IsSearchActive, Converter={StaticResource InverseBoolToCollapsedConverter}}">
<TabControl.Resources>
<ResourceDictionary Source="/TRX_ConfigToolLib;component/Resources/styles.xaml" />
</TabControl.Resources>
Expand All @@ -181,6 +257,34 @@
</TabControl.ContentTemplate>
</TabControl>

<TabControl
Grid.Row="1"
Margin="0,0,0,7"
Padding="0"
IsEnabled="{Binding IsEditorActive}"
ItemsSource="{Binding SearchResults}"
SelectedItem="{Binding SearchCategory, Mode=OneWay}"
Visibility="{Binding IsSearchActive, Converter={StaticResource BoolToCollapsedConverter}}">
<TabControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal">
<TextBlock
Text="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ViewText[label_search_results]}"/>
<Button
Content="../Resources/close.png"
ToolTip="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.ViewText[command_close_search]}"
Command="{Binding RelativeSource={RelativeSource FindAncestor, AncestorType={x:Type Window}}, Path=DataContext.CloseSearchCommand}"
Style="{StaticResource TabButtonStyle}"/>
</StackPanel>
</DataTemplate>
</TabControl.ItemTemplate>
<TabControl.ContentTemplate>
<DataTemplate>
<controls:CategoryControl />
</DataTemplate>
</TabControl.ContentTemplate>
</TabControl>

<Grid
Grid.Row="2">
<Grid.ColumnDefinitions>
Expand Down
6 changes: 2 additions & 4 deletions tools/config/TRX_ConfigToolLib/Models/CategoryViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ public class CategoryViewModel
public CategoryViewModel(Category category)
{
_category = category;
ItemsSource = new(category.Properties);
}

public string Title
Expand All @@ -23,10 +24,7 @@ public string ImageSource
get => AssemblyUtils.GetEmbeddedResourcePath(_category.Image ?? _defaultImage);
}

public IEnumerable<BaseProperty> ItemsSource
{
get => _category.Properties;
}
public FastObservableCollection<BaseProperty> ItemsSource { get; private set; }

public double ViewPosition { get; set; }
}
88 changes: 88 additions & 0 deletions tools/config/TRX_ConfigToolLib/Models/MainWindowViewModel.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,12 @@ namespace TRX_ConfigToolLib.Models;

public class MainWindowViewModel : BaseLanguageViewModel
{
private const int _minSearchLength = 3;

private readonly Configuration _configuration;

public IEnumerable<CategoryViewModel> Categories { get; private set; }
public IEnumerable<CategoryViewModel> SearchResults { get; private set; }

public MainWindowViewModel()
{
Expand All @@ -28,6 +31,17 @@ public MainWindowViewModel()

Categories = categories;
SelectedCategory = Categories.FirstOrDefault();

// Search results are contained within a special category whose properties are built
// on-the-fly. Pick a random picture for it each time the application is used.
Random random = new();
SearchCategory = new(new()
{
Properties = new(),
Image = _configuration.Categories[random.Next(_configuration.Categories.Count)].Image,
});

SearchResults = new List<CategoryViewModel> { SearchCategory };
}

private void EditorPropertyChanged(object sender, PropertyChangedEventArgs e)
Expand All @@ -53,6 +67,8 @@ public CategoryViewModel SelectedCategory
}
}

public CategoryViewModel SearchCategory { get; private set; }

private bool _isEditorDirty;
public bool IsEditorDirty
{
Expand Down Expand Up @@ -230,6 +246,78 @@ private bool CanSaveAs()
return IsEditorActive;
}

private bool _beginSearch;
public bool BeginSearch
{
get => _beginSearch;
set
{
if (!value)
{
return;
}

_beginSearch = true;
NotifyPropertyChanged();
_beginSearch = false;
NotifyPropertyChanged();
}
}

private string _searchText = string.Empty;
public string SearchText
{
get => _searchText;
set
{
if (value == _searchText)
{
return;
}

_searchText = value;
RunPropertySearch();
NotifyPropertyChanged();
NotifyPropertyChanged(nameof(IsSearchActive));
NotifyPropertyChanged(nameof(IsSearchTextDefined));
NotifyPropertyChanged(nameof(SearchFailStatus));
}
}

public bool IsSearchActive => SearchCategory.ItemsSource.Any();
public bool IsSearchTextDefined => _searchText.Length > 0;
public bool SearchFailStatus => _searchText.Trim().Length >= _minSearchLength && !IsSearchActive;

private void RunPropertySearch()
{
string text = _searchText.Trim().ToLower();
if (text.Length < _minSearchLength)
{
SearchCategory.ItemsSource.RemoveAll();
return;
}

text = TextUtilities.Normalise(text);
List<string> keywords = new(text.Split(null));
IEnumerable<BaseProperty> matchedProperties = Categories
.SelectMany(c => c.ItemsSource)
.Where(p => keywords.Any(p.NormalisedText.Contains));

SearchCategory.ItemsSource.ReplaceCollection(matchedProperties);
}

private RelayCommand _beginSearchCommand;
public ICommand BeginSearchCommand
{
get => _beginSearchCommand ??= new RelayCommand(() => BeginSearch = true);
}

private RelayCommand _closeSearchCommand;
public ICommand CloseSearchCommand
{
get => _closeSearchCommand ??= new RelayCommand(() => SearchText = string.Empty);
}

private RelayCommand _launchGameCommand;
public ICommand LaunchGameCommand
{
Expand Down
Loading

0 comments on commit 1cbded1

Please sign in to comment.