imguiComboSearch.h 25 KB

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