ImGuiTextFilter: Added support for space ' ' as separator. Added support for quoted "xxx" blocks. (#2435, #30)

This commit is contained in:
ocornut 2026-09-18 19:20:42 +02:00
parent 54f86be71e
commit c74e7e93a3
4 changed files with 49 additions and 37 deletions

View File

@ -84,10 +84,12 @@ Other Changes:
hasn't been owned by another items. This is more correct and also necessary hasn't been owned by another items. This is more correct and also necessary
to avoid Drags/Sliders cancelling also triggering a cancel menu. (#8564, #9534) to avoid Drags/Sliders cancelling also triggering a cancel menu. (#8564, #9534)
- ImGuiTextFilter: - ImGuiTextFilter:
- Added support for space ' ' as separator, which is more standard. (#2435, #30)
- Added support for quoted "xxx" blocks to e.g. match words sequences. (#2435)
- Fixed filtering when mixing "include" and "-exclude" keyword so that an "-exclude" - Fixed filtering when mixing "include" and "-exclude" keyword so that an "-exclude"
match always filters out. (#2435) [@kudaba, @ocornut] match always filters out. (#2435) [@kudaba, @ocornut]
- Added `DrawWithHint()` function. (#6206, #6395, #6447) - Added `DrawWithHint()` function. (#6206, #6395, #6447)
- Added a "incl,-excl" hint by default when the search field is empty. (#6206, #6395, #6447) - Added a "incl -excl" hint by default when the search field is empty. (#6206, #6395, #6447)
- Style: - Style:
- Inverted `ImGuiCol_TitleBgXXX` colors for `StyleColorsLight()` so that the selected - Inverted `ImGuiCol_TitleBgXXX` colors for `StyleColorsLight()` so that the selected
window title bar appears brighter. window title bar appears brighter.

View File

@ -3090,7 +3090,7 @@ IM_MSVC_RUNTIME_CHECKS_RESTORE
// [SECTION] ImGuiTextFilter // [SECTION] ImGuiTextFilter
//----------------------------------------------------------------------------- //-----------------------------------------------------------------------------
// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" // Helper: Parse and apply text filters e.g. 'aaa bbb -ccc'.
ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) //-V1077 ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) //-V1077
{ {
InputBuf[0] = 0; InputBuf[0] = 0;
@ -3104,7 +3104,7 @@ ImGuiTextFilter::ImGuiTextFilter(const char* default_filter) //-V1077
bool ImGuiTextFilter::Draw(const char* label) bool ImGuiTextFilter::Draw(const char* label)
{ {
return DrawWithHint(label, "incl,-excl"); return DrawWithHint(label, "incl -excl");
} }
// Use ImGui::SetNextItemWidth() manually if you want to use this. // Use ImGui::SetNextItemWidth() manually if you want to use this.
@ -3118,17 +3118,20 @@ bool ImGuiTextFilter::DrawWithHint(const char* label, const char* hint)
static void ImGuiTextFilter_BuildAddItem(ImGuiTextFilter* f, const char* word_b, const char* word_e) static void ImGuiTextFilter_BuildAddItem(ImGuiTextFilter* f, const char* word_b, const char* word_e)
{ {
// Trim (FIXME: UTF-8 support?) // Trim blanks
while (word_b < word_e && ImCharIsBlankA(word_b[0])) if (word_b < word_e && word_b[0] != '\"')
{
while (word_b < word_e && ImCharIsBlankA(word_b[0])) // FIXME: UTF-8 support
word_b++; word_b++;
while (word_e > word_b && ImCharIsBlankA(word_e[-1])) while (word_e > word_b && ImCharIsBlankA(word_e[-1]))
word_e--; word_e--;
if (word_e - word_b <= 0) }
const bool is_excl = (word_b < word_e && word_b[0] == '-');
if (word_e - word_b - (is_excl ? 1 : 0) <= 0)
return; return;
// Add to list // Add to list
// FIXME-OPT: on push_front(): as N is derived from user inputs we expect this to be fine. // FIXME-OPT: about ~push_front(): as N is derived from user inputs we expect this to be fine.
const bool is_excl = (word_b[0] == '-');
f->_Items.insert(is_excl ? f->_Items.Data : f->_Items.end(), ImGuiTextFilter::ImGuiTextFilterItem(word_b, word_e)); f->_Items.insert(is_excl ? f->_Items.Data : f->_Items.end(), ImGuiTextFilter::ImGuiTextFilterItem(word_b, word_e));
if (!is_excl) if (!is_excl)
f->_CountInclude++; f->_CountInclude++;
@ -3140,18 +3143,26 @@ void ImGuiTextFilter::Build()
_Items.resize(0); _Items.resize(0);
_CountInclude = 0; _CountInclude = 0;
const char* buf_e = InputBuf + ImStrlen(InputBuf); const char* buf_e = InputBuf + ImStrlen(InputBuf);
const char* word_b = InputBuf; const char* word_e;
const char* word_e = word_b; for (const char* word_b = InputBuf; word_b < buf_e; word_b = word_e + 1)
while (word_e < buf_e)
{ {
if (*word_e == ',') const bool is_excl = (word_b[0] == '-');
if (word_b[0] == '\"' || (is_excl && word_b + 1 < buf_e && word_b[1] == '\"'))
{ {
ImGuiTextFilter_BuildAddItem(this, word_b, word_e); // Parsing "word". Store leading " to distinguish -"word" from "-word", but omit trailing ".
word_b = word_e + 1; word_e = ImStrchrRange(word_b + (is_excl ? 2 : 1), buf_e, '\"');
if (word_e == NULL)
word_e = buf_e;
} }
word_e++; else
{
// Handle both ' ' and ',' separators.
for (word_e = word_b; word_e < buf_e; word_e++)
if (*word_e == ' ' || *word_e == ',')
break;
} }
ImGuiTextFilter_BuildAddItem(this, word_b, word_e); ImGuiTextFilter_BuildAddItem(this, word_b, word_e);
}
} }
bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const
@ -3164,16 +3175,15 @@ bool ImGuiTextFilter::PassFilter(const char* text, const char* text_end) const
// Filters are sorted so that '-' ones are always leading. // Filters are sorted so that '-' ones are always leading.
for (const ImGuiTextFilterItem& item : _Items) for (const ImGuiTextFilterItem& item : _Items)
{ {
if (item.Begin[0] == '-') const char* word_b = item.Begin;
{ const char* word_e = item.End;
if (ImStristr(text, text_end, item.Begin + 1, item.End) != NULL) // Exclude const bool is_excl = (word_b[0] == '-');
return false; if (is_excl)
} word_b++;
else if (word_b < word_e && word_b[0] == '\"')
{ word_b++;
if (ImStristr(text, text_end, item.Begin, item.End) != NULL) // Include if (ImStristr(text, text_end, word_b, word_e) != NULL)
return true; return is_excl ? false : true;
}
} }
// When no inclusion are specified (only exclusions) we implicitly pass // When no inclusion are specified (only exclusions) we implicitly pass

View File

@ -2785,7 +2785,7 @@ struct ImGuiOnceUponAFrame
operator bool() const { int current_frame = ImGui::GetFrameCount(); if (RefFrame == current_frame) return false; RefFrame = current_frame; return true; } operator bool() const { int current_frame = ImGui::GetFrameCount(); if (RefFrame == current_frame) return false; RefFrame = current_frame; return true; }
}; };
// Helper: Parse and apply text filters. In format "aaaaa[,bbbb][,ccccc]" // Helper: Parse and apply text filters e.g. 'aaa bbb -ccc'.
struct ImGuiTextFilter struct ImGuiTextFilter
{ {
IMGUI_API ImGuiTextFilter(const char* default_filter = ""); IMGUI_API ImGuiTextFilter(const char* default_filter = "");
@ -2796,7 +2796,7 @@ struct ImGuiTextFilter
// Helper to call InputText() + Build() when buffer is changed. // Helper to call InputText() + Build() when buffer is changed.
IMGUI_API bool Draw(const char* label = "Filter"); IMGUI_API bool Draw(const char* label = "Filter");
IMGUI_API bool DrawWithHint(const char* label = "Filter", const char* hint = "incl,-excl"); IMGUI_API bool DrawWithHint(const char* label = "Filter", const char* hint = "incl -excl");
#ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS #ifndef IMGUI_DISABLE_OBSOLETE_FUNCTIONS
inline bool Draw(const char* label, float width) { if (width != 0.0f) ImGui::SetNextItemWidth(width); return Draw(label); } inline bool Draw(const char* label, float width) { if (width != 0.0f) ImGui::SetNextItemWidth(width); return Draw(label); }
#endif #endif

View File

@ -1453,7 +1453,7 @@ static void DemoWindowWidgetsComboBoxes()
} }
ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F); ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F);
ImGui::SetNextItemWidth(-FLT_MIN); ImGui::SetNextItemWidth(-FLT_MIN);
filter.DrawWithHint("##Filter", "Filter (incl,-excl)"); filter.DrawWithHint("##Filter", "Filter (incl -excl)");
for (int n = 0; n < IM_COUNTOF(items); n++) for (int n = 0; n < IM_COUNTOF(items); n++)
{ {
@ -3947,7 +3947,7 @@ static void DemoWindowWidgetsTextFilter()
" \"xxx,yyy\" display lines containing \"xxx\" or \"yyy\"\n" " \"xxx,yyy\" display lines containing \"xxx\" or \"yyy\"\n"
" \"-xxx\" hide lines containing \"xxx\""); " \"-xxx\" hide lines containing \"xxx\"");
ImGui::SetNextItemWidth(-FLT_MIN); ImGui::SetNextItemWidth(-FLT_MIN);
filter.DrawWithHint("##Filter", "Filter (incl,-excl)"); filter.DrawWithHint("##Filter", "Filter (incl -excl)");
const char* lines[] = { "aaa1.c", "bbb1.c", "ccc1.c", "aaa2.cpp", "bbb2.cpp", "ccc2.cpp", "abc.h", "hello, world" }; const char* lines[] = { "aaa1.c", "bbb1.c", "ccc1.c", "aaa2.cpp", "bbb2.cpp", "ccc2.cpp", "abc.h", "hello, world" };
for (int i = 0; i < IM_COUNTOF(lines); i++) for (int i = 0; i < IM_COUNTOF(lines); i++)
if (filter.PassFilter(lines[i])) if (filter.PassFilter(lines[i]))
@ -8833,7 +8833,7 @@ void ImGui::ShowStyleEditor(ImGuiStyle* ref)
static ImGuiTextFilter filter; static ImGuiTextFilter filter;
SetNextItemWidth(-FLT_MIN); SetNextItemWidth(-FLT_MIN);
filter.DrawWithHint("##FilterColors", "Filter Colors (incl,-excl)"); filter.DrawWithHint("##FilterColors", "Filter Colors (incl -excl)");
SetNextWindowSizeConstraints(ImVec2(0.0f, GetTextLineHeightWithSpacing() * 10), ImVec2(FLT_MAX, FLT_MAX)); SetNextWindowSizeConstraints(ImVec2(0.0f, GetTextLineHeightWithSpacing() * 10), ImVec2(FLT_MAX, FLT_MAX));
BeginChild("##colors", ImVec2(0, 0), ImGuiChildFlags_Borders | ImGuiChildFlags_NavFlattened, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar); BeginChild("##colors", ImVec2(0, 0), ImGuiChildFlags_Borders | ImGuiChildFlags_NavFlattened, ImGuiWindowFlags_AlwaysVerticalScrollbar | ImGuiWindowFlags_AlwaysHorizontalScrollbar);
@ -9250,7 +9250,7 @@ struct ExampleAppConsole
ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F, ImGuiInputFlags_Tooltip); ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F, ImGuiInputFlags_Tooltip);
ImGui::SetNextItemWidth(-FLT_MIN); ImGui::SetNextItemWidth(-FLT_MIN);
Filter.DrawWithHint("##Filter", "Filter (incl,-excl)"); Filter.DrawWithHint("##Filter", "Filter (incl -excl)");
ImGui::Separator(); ImGui::Separator();
// Reserve enough left-over height for 1 separator + 1 input text // Reserve enough left-over height for 1 separator + 1 input text
@ -9586,7 +9586,7 @@ struct ExampleAppLog
bool copy = ImGui::Button("Copy"); bool copy = ImGui::Button("Copy");
ImGui::SameLine(); ImGui::SameLine();
ImGui::SetNextItemWidth(-FLT_MIN); ImGui::SetNextItemWidth(-FLT_MIN);
Filter.DrawWithHint("##Filter", "Filter (incl,-excl)"); Filter.DrawWithHint("##Filter", "Filter (incl -excl)");
ImGui::Separator(); ImGui::Separator();
@ -9781,7 +9781,7 @@ struct ExampleAppPropertyEditor
ImGui::Text("(%d root nodes)", root_node->Childs.Size); ImGui::Text("(%d root nodes)", root_node->Childs.Size);
ImGui::SetNextItemWidth(-FLT_MIN); ImGui::SetNextItemWidth(-FLT_MIN);
ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F, ImGuiInputFlags_Tooltip); ImGui::SetNextItemShortcut(ImGuiMod_Ctrl | ImGuiKey_F, ImGuiInputFlags_Tooltip);
if (ImGui::InputTextWithHint("##Filter", "incl,-excl", Filter.InputBuf, IM_COUNTOF(Filter.InputBuf), ImGuiInputTextFlags_EscapeClearsAll)) if (ImGui::InputTextWithHint("##Filter", "incl -excl", Filter.InputBuf, IM_COUNTOF(Filter.InputBuf), ImGuiInputTextFlags_EscapeClearsAll))
Filter.Build(); Filter.Build();
ImGui::PopItemFlag(); ImGui::PopItemFlag();