imguiComboSearch.cpp 25 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668
  1. #include "imgui_internal.h"
  2. #include "imguiComboSearch.h"
  3. namespace ImGui
  4. {
  5. // https://github.com/forrestthewoods/lib_fts
  6. // Forward declarations for "private" implementation
  7. namespace fuzzy_internal
  8. {
  9. static bool fuzzy_match_recursive(const char *pattern, const char *str, int &outScore, const char *strBegin,
  10. uint8_t const *srcMatches, uint8_t *newMatches, int maxMatches, int nextMatch,
  11. int &recursionCount, int recursionLimit);
  12. }
  13. // Private implementation
  14. static bool fuzzy_internal::fuzzy_match_recursive(const char *pattern, const char *str, int &outScore,
  15. const char *strBegin, uint8_t const *srcMatches, uint8_t *matches, int maxMatches,
  16. int nextMatch, int &recursionCount, int recursionLimit)
  17. {
  18. // Count recursions
  19. ++recursionCount;
  20. if (recursionCount >= recursionLimit)
  21. return false;
  22. // Detect end of strings
  23. if (*pattern == '\0' || *str == '\0')
  24. return false;
  25. // Recursion params
  26. bool recursiveMatch = false;
  27. uint8_t bestRecursiveMatches[256];
  28. int bestRecursiveScore = 0;
  29. // Loop through pattern and str looking for a match
  30. bool first_match = true;
  31. while (*pattern != '\0' && *str != '\0')
  32. {
  33. // Found match
  34. if (tolower(*pattern) == tolower(*str))
  35. {
  36. // Supplied matches buffer was too short
  37. if (nextMatch >= maxMatches)
  38. return false;
  39. // "Copy-on-Write" srcMatches into matches
  40. if (first_match && srcMatches)
  41. {
  42. memcpy(matches, srcMatches, nextMatch);
  43. first_match = false;
  44. }
  45. // Recursive call that "skips" this match
  46. uint8_t recursiveMatches[256];
  47. int recursiveScore;
  48. if (fuzzy_match_recursive(pattern, str + 1, recursiveScore, strBegin, matches, recursiveMatches, sizeof(recursiveMatches), nextMatch, recursionCount, recursionLimit))
  49. {
  50. // Pick best recursive score
  51. if (!recursiveMatch || recursiveScore > bestRecursiveScore)
  52. {
  53. memcpy(bestRecursiveMatches, recursiveMatches, 256);
  54. bestRecursiveScore = recursiveScore;
  55. }
  56. recursiveMatch = true;
  57. }
  58. // Advance
  59. matches[nextMatch++] = (uint8_t)(str - strBegin);
  60. ++pattern;
  61. }
  62. ++str;
  63. }
  64. // Determine if full pattern was matched
  65. bool matched = *pattern == '\0' ? true : false;
  66. // Calculate score
  67. if (matched)
  68. {
  69. const int sequential_bonus = 15; // bonus for adjacent matches
  70. const int separator_bonus = 30; // bonus if match occurs after a separator
  71. const int camel_bonus = 30; // bonus if match is uppercase and prev is lower
  72. const int first_letter_bonus = 15; // bonus if the first letter is matched
  73. const int leading_letter_penalty = -5; // penalty applied for every letter in str before the first match
  74. const int max_leading_letter_penalty = -15; // maximum penalty for leading letters
  75. const int unmatched_letter_penalty = -1; // penalty for every letter that doesn't matter
  76. // Iterate str to end
  77. while (*str != '\0')
  78. ++str;
  79. // Initialize score
  80. outScore = 100;
  81. // Apply leading letter penalty
  82. int penalty = leading_letter_penalty * matches[0];
  83. if (penalty < max_leading_letter_penalty)
  84. penalty = max_leading_letter_penalty;
  85. outScore += penalty;
  86. // Apply unmatched penalty
  87. int unmatched = (int)(str - strBegin) - nextMatch;
  88. outScore += unmatched_letter_penalty * unmatched;
  89. // Apply ordering bonuses
  90. for (int i = 0; i < nextMatch; ++i)
  91. {
  92. uint8_t currIdx = matches[i];
  93. if (i > 0)
  94. {
  95. uint8_t prevIdx = matches[i - 1];
  96. // Sequential
  97. if (currIdx == (prevIdx + 1))
  98. outScore += sequential_bonus;
  99. }
  100. // Check for bonuses based on neighbor character value
  101. if (currIdx > 0)
  102. {
  103. // Camel case
  104. char neighbor = strBegin[currIdx - 1];
  105. char curr = strBegin[currIdx];
  106. if (::islower(neighbor) && ::isupper(curr))
  107. outScore += camel_bonus;
  108. // Separator
  109. bool neighborSeparator = neighbor == '_' || neighbor == ' ';
  110. if (neighborSeparator)
  111. outScore += separator_bonus;
  112. }
  113. else
  114. {
  115. // First letter
  116. outScore += first_letter_bonus;
  117. }
  118. }
  119. }
  120. // Return best result
  121. if (recursiveMatch && (!matched || bestRecursiveScore > outScore))
  122. {
  123. // Recursive score is better than "this"
  124. memcpy(matches, bestRecursiveMatches, maxMatches);
  125. outScore = bestRecursiveScore;
  126. return true;
  127. }
  128. else if (matched)
  129. {
  130. // "this" score is better than recursive
  131. return true;
  132. }
  133. else
  134. {
  135. // no match
  136. return false;
  137. }
  138. }
  139. static bool fuzzy_match(char const *pattern, char const *str, int &outScore, uint8_t *matches, int maxMatches)
  140. {
  141. int recursionCount = 0;
  142. int recursionLimit = 10;
  143. return fuzzy_internal::fuzzy_match_recursive(pattern, str, outScore, str, nullptr, matches, maxMatches, 0, recursionCount, recursionLimit);
  144. }
  145. // Public interface
  146. bool fuzzy_match_simple(char const *pattern, char const *str)
  147. {
  148. while (*pattern != '\0' && *str != '\0')
  149. {
  150. if (tolower(*pattern) == tolower(*str))
  151. ++pattern;
  152. ++str;
  153. }
  154. return *pattern == '\0' ? true : false;
  155. }
  156. bool fuzzy_match(char const *pattern, char const *str, int &outScore)
  157. {
  158. uint8_t matches[256];
  159. return fuzzy_match(pattern, str, outScore, matches, sizeof(matches));
  160. }
  161. static bool sortbysec_desc(const std::pair<int, int> &a, const std::pair<int, int> &b)
  162. {
  163. return (b.second < a.second);
  164. }
  165. bool ComboWithFilter(const char *label, int *current_item, std::vector<const char *> &items)
  166. {
  167. ImGuiContext &g = *GImGui;
  168. ImGuiWindow *window = GetCurrentWindow();
  169. if (window->SkipItems)
  170. return false;
  171. const ImGuiStyle &style = g.Style;
  172. int items_count = items.size();
  173. // Call the getter to obtain the preview string which is a parameter to BeginCombo()
  174. const char *preview_value = NULL;
  175. if (*current_item >= 0 && *current_item < items_count)
  176. preview_value = items[*current_item];
  177. static char pattern_buffer[256] = {0};
  178. bool isNeedFilter = false;
  179. char comboButtonName[512] = {0};
  180. ImFormatString(comboButtonName, IM_ARRAYSIZE(comboButtonName), "%s##name_ComboWithFilter_button_%s", preview_value ? preview_value : "", label);
  181. char name_popup[256 + 10];
  182. ImFormatString(name_popup, IM_ARRAYSIZE(name_popup), "##name_popup_%s", label);
  183. // Display items
  184. // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed)
  185. bool value_changed = false;
  186. const float expected_w = CalcItemWidth();
  187. ImVec2 item_min = GetItemRectMin();
  188. bool isNewOpen = false;
  189. float sz = GetFrameHeight();
  190. ImVec2 size(sz, sz);
  191. ImVec2 CursorPos = window->DC.CursorPos;
  192. ImVec2 pos = ImVec2{CursorPos.x + expected_w - sz, CursorPos.y};
  193. const ImRect bb(pos, ImVec2{pos.x + size.x , pos.y + size.y});
  194. float ButtonTextAlignX = g.Style.ButtonTextAlign.x;
  195. g.Style.ButtonTextAlign.x = 0;
  196. if (ImGui::Button(comboButtonName, ImVec2(expected_w, 0)))
  197. {
  198. ImGui::OpenPopup(name_popup);
  199. isNewOpen = true;
  200. }
  201. g.Style.ButtonTextAlign.x = ButtonTextAlignX;
  202. bool hovered = IsItemHovered();
  203. bool active = IsItemActivated();
  204. bool pressed = IsItemClicked();
  205. // Render
  206. //const ImU32 bg_col = GetColorU32((active && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
  207. //RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding);
  208. const ImU32 text_col = GetColorU32(ImGuiCol_Text);
  209. RenderArrow(window->DrawList, ImVec2(ImMax(0.0f, (size.x - g.FontSize) * 0.5f) + bb.Min.x,
  210. ImMax(0.0f, (size.y - g.FontSize) * 0.5f) + bb.Min.y), text_col, ImGuiDir_Down);
  211. if (isNewOpen)
  212. {
  213. memset(pattern_buffer, 0, IM_ARRAYSIZE(pattern_buffer));
  214. }
  215. ImVec2 item_max = GetItemRectMax();
  216. SetNextWindowPos({CursorPos.x, item_max.y});
  217. ImGui::SetNextWindowSize({ImGui::GetItemRectSize().x, 0});
  218. if (ImGui::BeginPopup(name_popup))
  219. {
  220. ImGui::PushStyleColor(ImGuiCol_FrameBg, (ImVec4)ImColor(240, 240, 240, 255));
  221. ImGui::PushStyleColor(ImGuiCol_Text, (ImVec4)ImColor(0, 0, 0, 255));
  222. ImGui::PushItemWidth(-FLT_MIN);
  223. // Filter input
  224. if (isNewOpen)
  225. ImGui::SetKeyboardFocusHere();
  226. InputText("##ComboWithFilter_inputText", pattern_buffer, 256);
  227. // Search Icon, you can use it if you load IconsFontAwesome5 https://github.com/juliettef/IconFontCppHeaders
  228. //const ImVec2 label_size = CalcTextSize(ICON_FA_SEARCH, NULL, true);
  229. //const ImVec2 search_icon_pos(ImGui::GetItemRectMax().x - label_size.x - style.ItemInnerSpacing.x * 2, window->DC.CursorPos.y + style.FramePadding.y + g.FontSize * 0.1f);
  230. //RenderText(search_icon_pos, ICON_FA_SEARCH);
  231. ImGui::PopStyleColor(2);
  232. if (pattern_buffer[0] != '\0')
  233. {
  234. isNeedFilter = true;
  235. }
  236. std::vector<std::pair<int, int> > itemScoreVector;
  237. if (isNeedFilter)
  238. {
  239. for (int i = 0; i < items_count; i++)
  240. {
  241. int score = 0;
  242. bool matched = fuzzy_match(pattern_buffer, items[i], score);
  243. if (matched)
  244. itemScoreVector.push_back(std::make_pair(i, score));
  245. }
  246. std::sort(itemScoreVector.begin(), itemScoreVector.end(), sortbysec_desc);
  247. }
  248. int show_count = isNeedFilter ? itemScoreVector.size() : items_count;
  249. if (ImGui::ListBoxHeader("##ComboWithFilter_itemList", show_count))
  250. {
  251. for (int i = 0; i < show_count; i++)
  252. {
  253. int idx = isNeedFilter ? itemScoreVector[i].first : i;
  254. PushID((void *)(intptr_t)idx);
  255. const bool item_selected = (idx == *current_item);
  256. const char *item_text = items[idx];
  257. if (Selectable(item_text, item_selected))
  258. {
  259. value_changed = true;
  260. *current_item = idx;
  261. CloseCurrentPopup();
  262. }
  263. if (item_selected)
  264. SetItemDefaultFocus();
  265. PopID();
  266. }
  267. ImGui::ListBoxFooter();
  268. }
  269. ImGui::PopItemWidth();
  270. ImGui::EndPopup();
  271. }
  272. if (value_changed)
  273. {
  274. MarkItemEdited(g.LastItemData.ID);
  275. }
  276. return value_changed;
  277. }
  278. bool ComboWithFilter(const char *label, int *current_item, const std::vector<char *> &items)
  279. {
  280. ImGuiContext &g = *GImGui;
  281. ImGuiWindow *window = GetCurrentWindow();
  282. if (window->SkipItems)
  283. return false;
  284. const ImGuiStyle &style = g.Style;
  285. int items_count = items.size();
  286. // Call the getter to obtain the preview string which is a parameter to BeginCombo()
  287. const char *preview_value = NULL;
  288. if (*current_item >= 0 && *current_item < items_count)
  289. preview_value = items[*current_item];
  290. static char pattern_buffer[256] = {0};
  291. bool isNeedFilter = false;
  292. char comboButtonName[512] = {0};
  293. ImFormatString(comboButtonName, IM_ARRAYSIZE(comboButtonName), "%s##name_ComboWithFilter_button_%s", preview_value ? preview_value : "", label);
  294. char name_popup[256 + 10];
  295. ImFormatString(name_popup, IM_ARRAYSIZE(name_popup), "##name_popup_%s", label);
  296. // Display items
  297. // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed)
  298. bool value_changed = false;
  299. const float expected_w = CalcItemWidth();
  300. ImVec2 item_min = GetItemRectMin();
  301. bool isNewOpen = false;
  302. float sz = GetFrameHeight();
  303. ImVec2 size(sz, sz);
  304. ImVec2 CursorPos = window->DC.CursorPos;
  305. ImVec2 pos = ImVec2{CursorPos.x + expected_w - sz, CursorPos.y};
  306. const ImRect bb(pos, ImVec2{pos.x + size.x , pos.y + size.y});
  307. float ButtonTextAlignX = g.Style.ButtonTextAlign.x;
  308. g.Style.ButtonTextAlign.x = 0;
  309. if (ImGui::Button(comboButtonName, ImVec2(expected_w, 0)))
  310. {
  311. ImGui::OpenPopup(name_popup);
  312. isNewOpen = true;
  313. }
  314. g.Style.ButtonTextAlign.x = ButtonTextAlignX;
  315. bool hovered = IsItemHovered();
  316. bool active = IsItemActivated();
  317. bool pressed = IsItemClicked();
  318. // Render
  319. //const ImU32 bg_col = GetColorU32((active && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
  320. //RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding);
  321. const ImU32 text_col = GetColorU32(ImGuiCol_Text);
  322. RenderArrow(window->DrawList, ImVec2(ImMax(0.0f, (size.x - g.FontSize) * 0.5f) + bb.Min.x,
  323. ImMax(0.0f, (size.y - g.FontSize) * 0.5f) + bb.Min.y), text_col, ImGuiDir_Down);
  324. if (isNewOpen)
  325. {
  326. memset(pattern_buffer, 0, IM_ARRAYSIZE(pattern_buffer));
  327. }
  328. ImVec2 item_max = GetItemRectMax();
  329. SetNextWindowPos({CursorPos.x, item_max.y});
  330. ImGui::SetNextWindowSize({ImGui::GetItemRectSize().x, 0});
  331. if (ImGui::BeginPopup(name_popup))
  332. {
  333. ImGui::PushStyleColor(ImGuiCol_FrameBg, (ImVec4)ImColor(240, 240, 240, 255));
  334. ImGui::PushStyleColor(ImGuiCol_Text, (ImVec4)ImColor(0, 0, 0, 255));
  335. ImGui::PushItemWidth(-FLT_MIN);
  336. // Filter input
  337. if (isNewOpen)
  338. ImGui::SetKeyboardFocusHere();
  339. InputText("##ComboWithFilter_inputText", pattern_buffer, 256);
  340. // Search Icon, you can use it if you load IconsFontAwesome5 https://github.com/juliettef/IconFontCppHeaders
  341. //const ImVec2 label_size = CalcTextSize(ICON_FA_SEARCH, NULL, true);
  342. //const ImVec2 search_icon_pos(ImGui::GetItemRectMax().x - label_size.x - style.ItemInnerSpacing.x * 2, window->DC.CursorPos.y + style.FramePadding.y + g.FontSize * 0.1f);
  343. //RenderText(search_icon_pos, ICON_FA_SEARCH);
  344. ImGui::PopStyleColor(2);
  345. if (pattern_buffer[0] != '\0')
  346. {
  347. isNeedFilter = true;
  348. }
  349. std::vector<std::pair<int, int> > itemScoreVector;
  350. if (isNeedFilter)
  351. {
  352. for (int i = 0; i < items_count; i++)
  353. {
  354. int score = 0;
  355. bool matched = fuzzy_match(pattern_buffer, items[i], score);
  356. if (matched)
  357. itemScoreVector.push_back(std::make_pair(i, score));
  358. }
  359. std::sort(itemScoreVector.begin(), itemScoreVector.end(), sortbysec_desc);
  360. }
  361. int show_count = isNeedFilter ? itemScoreVector.size() : items_count;
  362. if (ImGui::ListBoxHeader("##ComboWithFilter_itemList", show_count))
  363. {
  364. for (int i = 0; i < show_count; i++)
  365. {
  366. int idx = isNeedFilter ? itemScoreVector[i].first : i;
  367. PushID((void *)(intptr_t)idx);
  368. const bool item_selected = (idx == *current_item);
  369. const char *item_text = items[idx];
  370. if (Selectable(item_text, item_selected))
  371. {
  372. value_changed = true;
  373. *current_item = idx;
  374. CloseCurrentPopup();
  375. }
  376. if (item_selected)
  377. SetItemDefaultFocus();
  378. PopID();
  379. }
  380. ImGui::ListBoxFooter();
  381. }
  382. ImGui::PopItemWidth();
  383. ImGui::EndPopup();
  384. }
  385. if (value_changed)
  386. {
  387. MarkItemEdited(g.LastItemData.ID);
  388. }
  389. return value_changed;
  390. }
  391. bool ComboWithFilter(const char *label, int *current_item, const std::vector<std::string> &items)
  392. {
  393. ImGuiContext &g = *GImGui;
  394. ImGuiWindow *window = GetCurrentWindow();
  395. if (window->SkipItems)
  396. return false;
  397. const ImGuiStyle &style = g.Style;
  398. int items_count = items.size();
  399. // Call the getter to obtain the preview string which is a parameter to BeginCombo()
  400. const char *preview_value = NULL;
  401. if (*current_item >= 0 && *current_item < items_count)
  402. preview_value = items[*current_item].c_str();
  403. static char pattern_buffer[256] = {0};
  404. bool isNeedFilter = false;
  405. char comboButtonName[512] = {0};
  406. ImFormatString(comboButtonName, IM_ARRAYSIZE(comboButtonName), "%s##name_ComboWithFilter_button_%s", preview_value ? preview_value : "", label);
  407. char name_popup[256 + 10];
  408. ImFormatString(name_popup, IM_ARRAYSIZE(name_popup), "##name_popup_%s", label);
  409. // Display items
  410. // FIXME-OPT: Use clipper (but we need to disable it on the appearing frame to make sure our call to SetItemDefaultFocus() is processed)
  411. bool value_changed = false;
  412. const float expected_w = CalcItemWidth();
  413. ImVec2 item_min = GetItemRectMin();
  414. bool isNewOpen = false;
  415. float sz = GetFrameHeight();
  416. ImVec2 size(sz, sz);
  417. ImVec2 CursorPos = window->DC.CursorPos;
  418. ImVec2 pos = ImVec2{CursorPos.x + expected_w - sz, CursorPos.y};
  419. const ImRect bb(pos, ImVec2{pos.x + size.x , pos.y + size.y});
  420. float ButtonTextAlignX = g.Style.ButtonTextAlign.x;
  421. g.Style.ButtonTextAlign.x = 0;
  422. if (ImGui::Button(comboButtonName, ImVec2(expected_w, 0)))
  423. {
  424. ImGui::OpenPopup(name_popup);
  425. isNewOpen = true;
  426. }
  427. g.Style.ButtonTextAlign.x = ButtonTextAlignX;
  428. bool hovered = IsItemHovered();
  429. bool active = IsItemActivated();
  430. bool pressed = IsItemClicked();
  431. // Render
  432. //const ImU32 bg_col = GetColorU32((active && hovered) ? ImGuiCol_ButtonActive : hovered ? ImGuiCol_ButtonHovered : ImGuiCol_Button);
  433. //RenderFrame(bb.Min, bb.Max, bg_col, true, g.Style.FrameRounding);
  434. const ImU32 text_col = GetColorU32(ImGuiCol_Text);
  435. RenderArrow(window->DrawList, ImVec2(ImMax(0.0f, (size.x - g.FontSize) * 0.5f) + bb.Min.x,
  436. ImMax(0.0f, (size.y - g.FontSize) * 0.5f) + bb.Min.y), text_col, ImGuiDir_Down);
  437. if (isNewOpen)
  438. {
  439. memset(pattern_buffer, 0, IM_ARRAYSIZE(pattern_buffer));
  440. }
  441. ImVec2 item_max = GetItemRectMax();
  442. SetNextWindowPos({CursorPos.x, item_max.y});
  443. ImGui::SetNextWindowSize({ImGui::GetItemRectSize().x, 0});
  444. if (ImGui::BeginPopup(name_popup))
  445. {
  446. ImGui::PushStyleColor(ImGuiCol_FrameBg, (ImVec4)ImColor(240, 240, 240, 255));
  447. ImGui::PushStyleColor(ImGuiCol_Text, (ImVec4)ImColor(0, 0, 0, 255));
  448. ImGui::PushItemWidth(-FLT_MIN);
  449. // Filter input
  450. if (isNewOpen)
  451. ImGui::SetKeyboardFocusHere();
  452. InputText("##ComboWithFilter_inputText", pattern_buffer, 256);
  453. // Search Icon, you can use it if you load IconsFontAwesome5 https://github.com/juliettef/IconFontCppHeaders
  454. //const ImVec2 label_size = CalcTextSize(ICON_FA_SEARCH, NULL, true);
  455. //const ImVec2 search_icon_pos(ImGui::GetItemRectMax().x - label_size.x - style.ItemInnerSpacing.x * 2, window->DC.CursorPos.y + style.FramePadding.y + g.FontSize * 0.1f);
  456. //RenderText(search_icon_pos, ICON_FA_SEARCH);
  457. ImGui::PopStyleColor(2);
  458. if (pattern_buffer[0] != '\0')
  459. {
  460. isNeedFilter = true;
  461. }
  462. std::vector<std::pair<int, int> > itemScoreVector;
  463. if (isNeedFilter)
  464. {
  465. for (int i = 0; i < items_count; i++)
  466. {
  467. int score = 0;
  468. bool matched = fuzzy_match(pattern_buffer, items[i].c_str(), score);
  469. if (matched)
  470. itemScoreVector.push_back(std::make_pair(i, score));
  471. }
  472. std::sort(itemScoreVector.begin(), itemScoreVector.end(), sortbysec_desc);
  473. }
  474. int show_count = isNeedFilter ? itemScoreVector.size() : items_count;
  475. if (ImGui::ListBoxHeader("##ComboWithFilter_itemList", show_count))
  476. {
  477. for (int i = 0; i < show_count; i++)
  478. {
  479. int idx = isNeedFilter ? itemScoreVector[i].first : i;
  480. PushID((void *)(intptr_t)idx);
  481. const bool item_selected = (idx == *current_item);
  482. const char *item_text = items[idx].c_str();
  483. if (Selectable(item_text, item_selected))
  484. {
  485. value_changed = true;
  486. *current_item = idx;
  487. CloseCurrentPopup();
  488. }
  489. if (item_selected)
  490. SetItemDefaultFocus();
  491. PopID();
  492. }
  493. ImGui::ListBoxFooter();
  494. }
  495. ImGui::PopItemWidth();
  496. ImGui::EndPopup();
  497. }
  498. if (value_changed)
  499. {
  500. MarkItemEdited(g.LastItemData.ID);
  501. }
  502. return value_changed;
  503. }
  504. void ListWithFilter(const char *label, int *current_item,
  505. char *filter, size_t filterSize,
  506. std::vector<std::string> &items, ImVec2 size)
  507. {
  508. if (size.x > 0)
  509. {
  510. ImGui::SetNextItemWidth(size.x);
  511. }
  512. std::string textLabel = std::string(label) + "##Filter1";
  513. ImGui::InputText(textLabel.c_str(), filter, filterSize);
  514. #pragma region filter
  515. bool isNeedFilter = false;
  516. if (filter[0] != '\0')
  517. {
  518. isNeedFilter = true;
  519. }
  520. std::vector<std::pair<int, int> > itemScoreVector;
  521. if (isNeedFilter)
  522. {
  523. for (int i = 0; i < items.size(); i++)
  524. {
  525. int score = 0;
  526. bool matched = fuzzy_match(filter, items[i].c_str(), score);
  527. if (matched)
  528. itemScoreVector.push_back(std::make_pair(i, score));
  529. }
  530. std::sort(itemScoreVector.begin(), itemScoreVector.end(), sortbysec_desc);
  531. }
  532. #pragma endregion
  533. textLabel = "##list box" + textLabel;
  534. if (ImGui::BeginListBox(textLabel.c_str(), size))
  535. {
  536. if (isNeedFilter)
  537. {
  538. for (int n = 0; n < itemScoreVector.size(); n++)
  539. {
  540. bool isSelected = (*current_item == itemScoreVector[n].first);
  541. if (ImGui::Selectable(items[itemScoreVector[n].first].c_str(), isSelected))
  542. *current_item = itemScoreVector[n].first;
  543. if (isSelected)
  544. ImGui::SetItemDefaultFocus();
  545. }
  546. }
  547. else
  548. {
  549. for (int n = 0; n < items.size(); n++)
  550. {
  551. bool isSelected = (*current_item == n);
  552. if (ImGui::Selectable(items[n].c_str(), isSelected))
  553. *current_item = n;
  554. if (isSelected)
  555. ImGui::SetItemDefaultFocus();
  556. }
  557. }
  558. ImGui::EndListBox();
  559. }
  560. }
  561. }