Skip to content

Commit 06dee58

Browse files
committed
Extend ImGuiTextFilter to be customizable and usable for multiple fields. Preserved current behavior and performance.
* Fixed negative matches not working when located after a positive matches. Optimized by keeping all negative matches at the front of the filter list. * Add the following customization options: ** Custom split character, default is still ',' ** Minimum word match length can be used to keep the search more stable for the first couple characters of input ** Match mode can be switch from Or (any pass causes a success) to And (all positive filters must match). * Add a utility to allow applying a single filter to multiple fields.
1 parent aca6ee1 commit 06dee58

File tree

2 files changed

+194
-25
lines changed

2 files changed

+194
-25
lines changed

imgui.cpp

Lines changed: 148 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -2060,6 +2060,9 @@ void ImGuiStorage::SetAllInt(int v)
20602060

20612061
// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]"
20622062
ImGuiTextFilter::ImGuiTextFilter(const char* default_filter)
2063+
: WordSplitter(',')
2064+
, MatchMode(ImGuiTextFilterMode_Or)
2065+
, MinWordSize(0)
20632066
{
20642067
if (default_filter)
20652068
{
@@ -2069,7 +2072,6 @@ ImGuiTextFilter::ImGuiTextFilter(const char* default_filter)
20692072
else
20702073
{
20712074
InputBuf[0] = 0;
2072-
CountGrep = 0;
20732075
}
20742076
}
20752077

@@ -2083,7 +2085,21 @@ bool ImGuiTextFilter::Draw(const char* label, float width)
20832085
return value_changed;
20842086
}
20852087

2086-
void ImGuiTextFilter::TextRange::split(char separator, ImVector<TextRange>* out) const
2088+
static void AddWordToTextRange(const char* begin, const char* end, ImVector<ImGuiTextFilter::TextRange>* out, char minWordSize)
2089+
{
2090+
if (*begin == '-')
2091+
{
2092+
if ((end - begin) > minWordSize + 1)
2093+
out->push_front(ImGuiTextFilter::TextRange(begin, end));
2094+
}
2095+
else
2096+
{
2097+
if ((end - begin) > minWordSize)
2098+
out->push_back(ImGuiTextFilter::TextRange(begin, end));
2099+
}
2100+
}
2101+
2102+
void ImGuiTextFilter::TextRange::split(char separator, ImVector<TextRange>* out, char minWordSize) const
20872103
{
20882104
out->resize(0);
20892105
const char* wb = b;
@@ -2092,33 +2108,46 @@ void ImGuiTextFilter::TextRange::split(char separator, ImVector<TextRange>* out)
20922108
{
20932109
if (*we == separator)
20942110
{
2095-
out->push_back(TextRange(wb, we));
2111+
AddWordToTextRange(wb, we, out, minWordSize);
20962112
wb = we + 1;
20972113
}
20982114
we++;
20992115
}
2100-
if (wb != we)
2101-
out->push_back(TextRange(wb, we));
2116+
2117+
AddWordToTextRange(wb, we, out, minWordSize);
21022118
}
21032119

21042120
void ImGuiTextFilter::Build()
21052121
{
2122+
NegativeFilterCount = 0;
21062123
Filters.resize(0);
21072124
TextRange input_range(InputBuf, InputBuf+strlen(InputBuf));
2108-
input_range.split(',', &Filters);
2125+
input_range.split(WordSplitter, &Filters, MinWordSize);
21092126

2110-
CountGrep = 0;
21112127
for (int i = 0; i != Filters.Size; i++)
21122128
{
21132129
TextRange& f = Filters[i];
2130+
IM_ASSERT(!f.empty());
2131+
2132+
if (f.b[0] == '-')
2133+
{
2134+
IM_ASSERT(NegativeFilterCount == i);
2135+
NegativeFilterCount++;
2136+
f.b++;
2137+
}
2138+
21142139
while (f.b < f.e && ImCharIsBlankA(f.b[0]))
21152140
f.b++;
21162141
while (f.e > f.b && ImCharIsBlankA(f.e[-1]))
21172142
f.e--;
2143+
21182144
if (f.empty())
2119-
continue;
2120-
if (Filters[i].b[0] != '-')
2121-
CountGrep += 1;
2145+
{
2146+
Filters.erase(&f);
2147+
if (NegativeFilterCount == i)
2148+
NegativeFilterCount--;
2149+
i--;
2150+
}
21222151
}
21232152
}
21242153

@@ -2130,30 +2159,126 @@ bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const
21302159
if (text == NULL)
21312160
text = "";
21322161

2133-
for (int i = 0; i != Filters.Size; i++)
2162+
int i = 0;
2163+
for (; i != NegativeFilterCount; i++)
21342164
{
21352165
const TextRange& f = Filters[i];
2136-
if (f.empty())
2137-
continue;
2138-
if (f.b[0] == '-')
2166+
2167+
// Subtract
2168+
if (ImStristr(text, text_end, f.begin(), f.end()) != NULL)
2169+
return false;
2170+
}
2171+
2172+
// Implicit * grep
2173+
if (NegativeFilterCount == Filters.Size)
2174+
return true;
2175+
2176+
for (; i != Filters.Size; i++)
2177+
{
2178+
const TextRange& f = Filters[i];
2179+
2180+
if (ImStristr(text, text_end, f.begin(), f.end()) != NULL)
21392181
{
2140-
// Subtract
2141-
if (ImStristr(text, text_end, f.begin()+1, f.end()) != NULL)
2142-
return false;
2182+
// in Or mode we stop testing after a single hit
2183+
if (MatchMode == ImGuiTextFilterMode_Or)
2184+
return true;
21432185
}
21442186
else
21452187
{
2146-
// Grep
2147-
if (ImStristr(text, text_end, f.begin(), f.end()) != NULL)
2148-
return true;
2188+
// in And mode we stop testing after a single miss
2189+
if (MatchMode == ImGuiTextFilterMode_And)
2190+
return false;
2191+
}
2192+
}
2193+
2194+
return MatchMode == ImGuiTextFilterMode_And;
2195+
}
2196+
2197+
//-----------------------------------------------------------------------------
2198+
// [SECTION] ImGuiTextFilterMatch
2199+
//-----------------------------------------------------------------------------
2200+
ImGuiTextFilterMatch::ImGuiTextFilterMatch(const ImGuiTextFilter& filter)
2201+
: Filter(filter)
2202+
, State(ImGuiTextFilterMatchResultNone)
2203+
, MatchStates(0)
2204+
, MatchCount(filter.Filters.Size - filter.NegativeFilterCount)
2205+
, MatchMask((1 << MatchCount) - 1)
2206+
{
2207+
}
2208+
2209+
// duplicated so we don't add overhead to regular filters since it can add up quick when processing large amount of items
2210+
void ImGuiTextFilterMatch::PassFilter(const char* text, const char* text_end)
2211+
{
2212+
if (Filter.Filters.empty())
2213+
return;
2214+
2215+
// stop processing on any failure
2216+
if (State == ImGuiTextFilterMatchResultFail)
2217+
return;
2218+
2219+
// stop processing on any success with no negative filters
2220+
if (Filter.NegativeFilterCount == 0 && State == ImGuiTextFilterMatchResultPass)
2221+
return;
2222+
2223+
if (text == NULL)
2224+
text = "";
2225+
2226+
int i = 0;
2227+
for (; i != Filter.NegativeFilterCount; i++)
2228+
{
2229+
const ImGuiTextFilter::TextRange& f = Filter.Filters[i];
2230+
2231+
// Subtract
2232+
if (ImStristr(text, text_end, f.begin(), f.end()) != NULL)
2233+
{
2234+
State = ImGuiTextFilterMatchResultFail;
2235+
return;
21492236
}
21502237
}
21512238

21522239
// Implicit * grep
2153-
if (CountGrep == 0)
2154-
return true;
2240+
if (Filter.NegativeFilterCount == Filter.Filters.Size)
2241+
{
2242+
State = ImGuiTextFilterMatchResultPass;
2243+
return;
2244+
}
21552245

2156-
return false;
2246+
// already passed all statements, no sense in continuing
2247+
if (State == ImGuiTextFilterMatchResultPass)
2248+
{
2249+
return;
2250+
}
2251+
2252+
for (; i != Filter.Filters.Size; i++)
2253+
{
2254+
const ImGuiTextFilter::TextRange& f = Filter.Filters[i];
2255+
2256+
ImU64 test_bit = (1 << i) - Filter.NegativeFilterCount;
2257+
if ((MatchStates & test_bit) != 0)
2258+
{
2259+
continue;
2260+
}
2261+
2262+
if (ImStristr(text, text_end, f.begin(), f.end()) != NULL)
2263+
{
2264+
// in Or mode we stop testing after a single hit
2265+
if (Filter.MatchMode == ImGuiTextFilterMode_Or)
2266+
{
2267+
State = ImGuiTextFilterMatchResultPass;
2268+
return;
2269+
}
2270+
else
2271+
{
2272+
MatchStates |= test_bit;
2273+
}
2274+
}
2275+
}
2276+
2277+
// Check if all bits are set after each check
2278+
if (MatchStates == MatchMask)
2279+
{
2280+
State = ImGuiTextFilterMatchResultPass;
2281+
}
21572282
}
21582283

21592284
//-----------------------------------------------------------------------------

imgui.h

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -114,6 +114,7 @@ struct ImGuiStorage; // Helper for key->value storage
114114
struct ImGuiStyle; // Runtime data for styling/colors
115115
struct ImGuiTextBuffer; // Helper to hold and append into a text buffer (~string builder)
116116
struct ImGuiTextFilter; // Helper to parse and apply text filters (e.g. "aaaaa[,bbbb][,ccccc]")
117+
struct ImGuiTextFilterMatch; // Helper for ImGuiTextFilter to allow parsing of multiple string field with a single filter
117118

118119
// Typedefs and Enums/Flags (declared as int for compatibility with old C++, to allow using as flags and to not pollute the top of this file)
119120
// Use your programming IDE "Go to definition" facility on the names of the center columns to find the actual flags/enum lists.
@@ -130,6 +131,8 @@ typedef int ImGuiKey; // -> enum ImGuiKey_ // Enum: A
130131
typedef int ImGuiNavInput; // -> enum ImGuiNavInput_ // Enum: An input identifier for navigation
131132
typedef int ImGuiMouseCursor; // -> enum ImGuiMouseCursor_ // Enum: A mouse cursor identifier
132133
typedef int ImGuiStyleVar; // -> enum ImGuiStyleVar_ // Enum: A variable identifier for styling
134+
typedef int ImGuiTextFilterMode; // -> enum ImGuiTextFilterMode_ // Enum: To control how text filter handles multiple words
135+
typedef int ImGuiTextFilterMatchResult; // -> enum ImGuiTextFilterMatchResult_ // Enum: A match identifier to better control applying a text filter over multiple fields
133136
typedef int ImDrawCornerFlags; // -> enum ImDrawCornerFlags_ // Flags: for ImDrawList::AddRect*() etc.
134137
typedef int ImDrawListFlags; // -> enum ImDrawListFlags_ // Flags: for ImDrawList
135138
typedef int ImFontAtlasFlags; // -> enum ImFontAtlasFlags_ // Flags: for ImFontAtlas
@@ -1585,6 +1588,12 @@ struct ImGuiOnceUponAFrame
15851588
#define IMGUI_ONCE_UPON_A_FRAME static ImGuiOnceUponAFrame imgui_oaf; if (imgui_oaf) // OBSOLETED in 1.51, will remove!
15861589
#endif
15871590

1591+
enum ImGuiTextFilterMode_
1592+
{
1593+
ImGuiTextFilterMode_Or, // A single word match will pass the filter
1594+
ImGuiTextFilterMode_And // All words must match to pass the filter
1595+
};
1596+
15881597
// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]"
15891598
struct ImGuiTextFilter
15901599
{
@@ -1606,11 +1615,46 @@ struct ImGuiTextFilter
16061615
const char* begin() const { return b; }
16071616
const char* end () const { return e; }
16081617
bool empty() const { return b == e; }
1609-
IMGUI_API void split(char separator, ImVector<TextRange>* out) const;
1618+
IMGUI_API void split(char separator, ImVector<TextRange>* out, char minWordSize = 0) const;
16101619
};
16111620
char InputBuf[256];
16121621
ImVector<TextRange> Filters;
1613-
int CountGrep;
1622+
int NegativeFilterCount;
1623+
1624+
// [Configuration]
1625+
ImGuiTextFilterMode MatchMode; // if true then all words must match, otherwise any matching word will be a pass
1626+
char WordSplitter; // Character used to split user string, ',' by default
1627+
char MinWordSize; // Minimum number of characters before a word is used for matching, can help improve UX by avoiding mass matching against 1 or 2 characters
1628+
};
1629+
1630+
enum ImGuiTextFilterMatchResult_
1631+
{
1632+
ImGuiTextFilterMatchResultNone, // There have been no matches or failures
1633+
ImGuiTextFilterMatchResultFail, // The match explicitly failed because of a subtractive clause or because no positive matches passed
1634+
ImGuiTextFilterMatchResultPass, // The match explicitly passed because of positive match
1635+
};
1636+
1637+
// Helper: Extend a single ImGuiTextFilter to multiple fields. It tracks explicit pass/failure conditions and will
1638+
// stop processing text after an explicit failure.
1639+
// Usage:
1640+
// ImGuiTextFilterMatch match(filter);
1641+
// match.PassFilter(partial_text_to_match1);
1642+
// match.PassFilter(partial_text_to_match2);
1643+
// if (match) { ... }
1644+
struct ImGuiTextFilterMatch
1645+
{
1646+
ImGuiTextFilterMatch(const ImGuiTextFilter& filter);
1647+
1648+
IMGUI_API void PassFilter(const char* text, const char* text_end = NULL);
1649+
1650+
operator bool() const { return State == ImGuiTextFilterMatchResultPass; }
1651+
1652+
// [Internal]
1653+
const ImGuiTextFilter& Filter;
1654+
ImGuiTextFilterMatchResult State;
1655+
ImU64 MatchStates; // track which positive matches have been seen for And mode
1656+
int MatchCount;
1657+
int MatchMask;
16141658
};
16151659

16161660
// Helper: Growable text buffer for logging/accumulating text

0 commit comments

Comments
 (0)