Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
116 changes: 108 additions & 8 deletions src/TSMapEditor/Helpers.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.Xna.Framework;
using FuzzySharp;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Rampastring.Tools;
using Rampastring.XNAUI;
Expand Down Expand Up @@ -739,16 +740,115 @@ public static bool IsCloningSupported(IMovable objectToClone)
return false;
}

public static IniFile ReadConfigINI(string path)
/// <summary>
/// Calculates a fuzzy search score between a search string and a target string.
/// The score is based on similarity and optionally checks individual parts of the target string.
/// Significantly improves score of exact matches and partial matches of the search string.
/// </summary>
/// <param name="searchString">The string to search for.</param>
/// <param name="targetString">The string to compare against.</param>
/// <param name="checkParts">Whether to check multiple parts separated by a space. Useful for texts that has ININame or ID, followed by the object name.</param>
/// <returns>An integer score representing the similarity between the strings.</returns>
public static int CalculateFuzzySearchScore(string searchString, string targetString, bool checkParts)
{
string customPath = Path.Combine(Environment.CurrentDirectory, "Config", path);
string defaultPath = Path.Combine(Environment.CurrentDirectory, "Config", "Default", path);
targetString = targetString.ToLowerInvariant();

if (File.Exists(customPath))
return new IniFile(customPath);
if (string.IsNullOrWhiteSpace(searchString))
return 100;

return new IniFile(defaultPath);
}
// Fuzzy Search method, used to calculate initial score
int score = Fuzz.Ratio(searchString, targetString);

List<string> nameParts = [];

if (checkParts)
{
int spaceIndex = targetString.IndexOf(" ");
if (spaceIndex >= 0)
{
string firstPart = targetString.Substring(0, spaceIndex);
string secondPart = targetString.Substring(spaceIndex + 1);
nameParts.Add(firstPart);
nameParts.Add(secondPart);
}
else
{
nameParts.Add(targetString);
}
}
else
{
nameParts.Add(targetString);
}

foreach (var namePart in nameParts)
{
if (namePart == searchString)
{
score += 100;
break;
}
else if (namePart.StartsWith(searchString))
{
score += 75;
break;
}
else if (namePart.Contains(searchString))
{
score += 35;
break;
}
}

return score;
}

/// <summary>
/// Performs a fuzzy search on a list of items and returns a list of results with their scores.
/// Items are filtered by a minimum score and optionally checked for partial matches.
/// </summary>
/// <typeparam name="T">The type of items in the list.</typeparam>
/// <param name="searchString">The string to search for.</param>
/// <param name="itemsList">The list of items to search through.</param>
/// <param name="extractTextFromItem">A function to extract the name or relevant representation of an item.</param>
/// <param name="minimumScore">The minimum score required for an item to be included in the results.</param>
/// <param name="checkParts">Whether to check multiple parts separated by a space. Useful for texts that has ININame or ID, followed by the object name.</param>
/// <returns>A list of fuzzy search results, each containing an item and a score.
public static List<FuzzySearchItem<T>> FuzzySearch<T>(string searchString, List<T> itemsList, Func<T, string> extractTextFromItem, int minimumScore, bool checkParts)
{
searchString = searchString.ToLowerInvariant();

if (string.IsNullOrWhiteSpace(searchString))
return itemsList.Select(item => new FuzzySearchItem<T>(item, 100)).ToList();

var results = itemsList
.Select(item =>
{
string itemText = extractTextFromItem(item);

if (itemText == Constants.NoneValue1 || itemText == Constants.NoneValue2)
return new FuzzySearchItem<T>(item, 0);

int score = CalculateFuzzySearchScore(searchString, itemText, checkParts);
return new FuzzySearchItem<T>(item, score);
})
.Where(item => item.Score >= minimumScore)
.OrderByDescending(item => item.Score)
.ToList();

return results;
}

public static IniFile ReadConfigINI(string path)
{
string customPath = Path.Combine(Environment.CurrentDirectory, "Config", path);
string defaultPath = Path.Combine(Environment.CurrentDirectory, "Config", "Default", path);

if (File.Exists(customPath))
return new IniFile(customPath);

return new IniFile(defaultPath);
}

public static IniFileEx ReadConfigINIEx(string path, CCFileManager fileManager)
{
Expand Down
8 changes: 8 additions & 0 deletions src/TSMapEditor/Models/FuzzySearchItem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace TSMapEditor.Models
{
public class FuzzySearchItem<T>(T item, int score)
{
public T Item { get; set; } = item;
public int Score { get; set; } = score;
};
}
1 change: 1 addition & 0 deletions src/TSMapEditor/TSMapEditor.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -477,6 +477,7 @@
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="FuzzySharp" Version="2.0.2" />
<PackageReference Include="Microsoft.CSharp" Version="4.7.0" />
<PackageReference Include="MonoGame.Framework.WindowsDX" Version="3.8.3" />
<PackageReference Include="Rampastring.Tools" Version="2.0.7" />
Expand Down
40 changes: 24 additions & 16 deletions src/TSMapEditor/UI/Windows/ScriptsWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ public ScriptsWindow(WindowManager windowManager, Map map, EditorState editorSta
private readonly EditorState editorState;
private readonly INotificationManager notificationManager;
private SelectCellCursorAction selectCellCursorAction;
private readonly int minimumFuzzySearchScore = 50;

private EditorListBox lbScriptTypes;
private EditorSuggestionTextBox tbFilter;
Expand Down Expand Up @@ -768,27 +769,34 @@ private void ListScripts()
IEnumerable<Script> sortedScripts = map.Scripts;

bool shouldViewTop = false; // when filtering the scroll bar should update so we use a flag here
bool filtering = false;
if (tbFilter.Text != string.Empty && tbFilter.Text != tbFilter.Suggestion)
{
sortedScripts = sortedScripts.Where(script => script.Name.Contains(tbFilter.Text, StringComparison.CurrentCultureIgnoreCase));
var fuzzySearchScripts = Helpers.FuzzySearch(tbFilter.Text, sortedScripts.ToList(), script => script.Name, minimumFuzzySearchScore, false);
sortedScripts = fuzzySearchScripts.Select(fuzzySearchScript => fuzzySearchScript.Item);

shouldViewTop = true;
filtering = true;
}

switch (ScriptSortMode)
{
case ScriptSortMode.Color:
sortedScripts = sortedScripts.OrderBy(script => script.EditorColor).ThenBy(script => script.ININame);
break;
case ScriptSortMode.Name:
sortedScripts = sortedScripts.OrderBy(script => script.Name).ThenBy(script => script.ININame);
break;
case ScriptSortMode.ColorThenName:
sortedScripts = sortedScripts.OrderBy(script => script.EditorColor).ThenBy(script => script.Name);
break;
case ScriptSortMode.ID:
default:
sortedScripts = sortedScripts.OrderBy(script => script.ININame);
break;
if (!filtering)
{
switch (ScriptSortMode)
{
case ScriptSortMode.Color:
sortedScripts = sortedScripts.OrderBy(script => script.EditorColor).ThenBy(script => script.ININame);
break;
case ScriptSortMode.Name:
sortedScripts = sortedScripts.OrderBy(script => script.Name).ThenBy(script => script.ININame);
break;
case ScriptSortMode.ColorThenName:
sortedScripts = sortedScripts.OrderBy(script => script.EditorColor).ThenBy(script => script.Name);
break;
case ScriptSortMode.ID:
default:
sortedScripts = sortedScripts.OrderBy(script => script.ININame);
break;
}
}

foreach (var script in sortedScripts)
Expand Down
58 changes: 44 additions & 14 deletions src/TSMapEditor/UI/Windows/SelectObjectWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
using TSMapEditor.GameMath;
using TSMapEditor.Models;
using TSMapEditor.UI.Controls;
using Rampastring.XNAUI.XNAControls;
using System.Collections.Generic;

namespace TSMapEditor.UI.Windows
{
Expand All @@ -17,6 +19,13 @@ public SelectObjectWindow(WindowManager windowManager) : base(windowManager)

protected EditorSuggestionTextBox tbSearch;
protected EditorListBox lbObjectList;
private readonly List<XNAListBoxItem> originalObjectList = [];
/// <summary>
/// The minimum score required for a Fuzzy Search item to appear.
/// Higher scores filters more heavily and increases precision,
/// while lower scores allow for more results.
/// </summary>
protected int MinimumFuzzySearchScore { get; set; } = 50;

/// <summary>
/// If the object is being selected for a trigger event or action,
Expand Down Expand Up @@ -94,28 +103,28 @@ public override void Kill()
private void TbSearch_TextChanged(object sender, EventArgs e)
{
if (string.IsNullOrWhiteSpace(tbSearch.Text) || tbSearch.Text == tbSearch.Suggestion)
{
foreach (var item in lbObjectList.Items)
{
if (!item.Visible)
lbObjectList.ViewTop = 0;
{
RestoreFromOriginalList();

item.Visible = true;
}
lbObjectList.ViewTop = 0;
}
else
{
lbObjectList.ViewTop = 0;
var fuzzySearchItems = Helpers.FuzzySearch(tbSearch.Text, originalObjectList, item => item.Text, MinimumFuzzySearchScore, true);

lbObjectList.Clear();

lbObjectList.ViewTop = 0;
lbObjectList.SelectedIndex = -1;

for (int i = 0; i < lbObjectList.Items.Count; i++)
{
var item = lbObjectList.Items[i];
item.Visible = item.Text.Contains(tbSearch.Text, StringComparison.OrdinalIgnoreCase);
foreach (var fuzzySearchItem in fuzzySearchItems)
{
var listBoxItem = fuzzySearchItem.Item;

lbObjectList.AddItem(listBoxItem);

if (item.Visible && lbObjectList.SelectedIndex == -1)
lbObjectList.SelectedIndex = i;
if (lbObjectList.SelectedIndex == -1)
lbObjectList.SelectedIndex = 0;
}
}

Expand Down Expand Up @@ -160,6 +169,7 @@ public void Open(T initialSelection)
this.initialSelection = SelectedObject;
OnOpen();
ListObjects();
SaveToOriginalList();

if (lbObjectList.SelectedItem == null)
{
Expand Down Expand Up @@ -213,6 +223,26 @@ private void LbObjectList_HoveredIndexChanged(object sender, EventArgs e)
new Point2D(Width, GetCursorPoint().Y));
}

private void SaveToOriginalList()
{
originalObjectList.Clear();

foreach (var item in lbObjectList.Items)
{
originalObjectList.Add(item);
}
}

private void RestoreFromOriginalList()
{
lbObjectList.Clear();

foreach (var item in originalObjectList)
{
lbObjectList.AddItem(item);
}
}

protected abstract void ListObjects();
}
}
1 change: 1 addition & 0 deletions src/TSMapEditor/UI/Windows/SelectScriptWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public class SelectScriptWindow : SelectObjectWindow<Script>
{
public SelectScriptWindow(WindowManager windowManager, Map map) : base(windowManager)
{
MinimumFuzzySearchScore = 25;
this.map = map;
}

Expand Down
1 change: 1 addition & 0 deletions src/TSMapEditor/UI/Windows/SelectTagWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ public class SelectTagWindow : SelectObjectWindow<Tag>
{
public SelectTagWindow(WindowManager windowManager, Map map) : base(windowManager)
{
MinimumFuzzySearchScore = 25;
this.map = map;
}

Expand Down
1 change: 1 addition & 0 deletions src/TSMapEditor/UI/Windows/SelectTaskForceWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class SelectTaskForceWindow : SelectObjectWindow<TaskForce>
{
public SelectTaskForceWindow(WindowManager windowManager, Map map) : base(windowManager)
{
MinimumFuzzySearchScore = 25;
this.map = map;
}

Expand Down
1 change: 1 addition & 0 deletions src/TSMapEditor/UI/Windows/SelectTeamTypeWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public class SelectTeamTypeWindow : SelectObjectWindow<TeamType>
{
public SelectTeamTypeWindow(WindowManager windowManager, Map map) : base(windowManager)
{
MinimumFuzzySearchScore = 25;
this.map = map;
}

Expand Down
1 change: 1 addition & 0 deletions src/TSMapEditor/UI/Windows/SelectTechnoTypeWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ public class SelectTechnoTypeWindow : SelectObjectWindow<TechnoType>
{
public SelectTechnoTypeWindow(WindowManager windowManager, Map map) : base(windowManager)
{
MinimumFuzzySearchScore = 25;
this.map = map;
}

Expand Down
4 changes: 3 additions & 1 deletion src/TSMapEditor/UI/Windows/SelectTriggerWindow.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,9 @@ public class SelectTriggerWindow : SelectObjectWindow<Trigger>
{
public SelectTriggerWindow(WindowManager windowManager, Map map) : base(windowManager)
{
this.map = map;
MinimumFuzzySearchScore = 25;

this.map = map;
}

private readonly Map map;
Expand Down
Loading
Loading