Blender V4.5
interface_region_search.cc
Go to the documentation of this file.
1/* SPDX-FileCopyrightText: 2008 Blender Authors
2 *
3 * SPDX-License-Identifier: GPL-2.0-or-later */
4
10
11#include "MEM_guardedalloc.h"
12
13#include <cstdarg>
14#include <cstdlib>
15#include <cstring>
16
17#include "DNA_userdef_types.h"
18
19#include "BLI_listbase.h"
20#include "BLI_rect.h"
21#include "BLI_string.h"
22#include "BLI_task.hh"
23#include "BLI_utildefines.h"
24
25#include "BKE_context.hh"
26#include "BKE_screen.hh"
27
28#include "WM_api.hh"
29#include "WM_types.hh"
30
31#include "RNA_access.hh"
32
33#include "UI_interface.hh"
34#include "UI_interface_icons.hh"
35#include "UI_view2d.hh"
36
37#include "BLT_translation.hh"
38
39#include "ED_screen.hh"
40
41#include "BLF_api.hh"
42
43#include "GPU_state.hh"
44#include "interface_intern.hh"
46
48
49/* -------------------------------------------------------------------- */
52
55
56 int offset, offset_i; /* offset for inserting in array */
57 int more; /* flag indicating there are more items */
58
59 char **names;
60 void **pointers;
61 int *icons;
64
67
69 void *active;
70};
71
100
101#define SEARCH_ITEMS 10
102
104 const StringRef name,
105 void *poin,
106 int iconid,
107 const int but_flag,
108 const uint8_t name_prefix_offset)
109{
110 /* hijack for autocomplete */
111 if (items->autocpl) {
112 UI_autocomplete_update_name(items->autocpl, name.drop_prefix(name_prefix_offset));
113 return true;
114 }
115
116 if (iconid) {
117 items->has_icon = true;
118 }
119
120 /* hijack for finding active item */
121 if (items->active) {
122 if (poin == items->active) {
123 items->offset_i = items->totitem;
124 }
125 items->totitem++;
126 return true;
127 }
128
129 if (items->totitem >= items->maxitem) {
130 items->more = 1;
131 return false;
132 }
133
134 /* skip first items in list */
135 if (items->offset_i > 0) {
136 items->offset_i--;
137 return true;
138 }
139
140 if (items->names) {
141 name.copy_utf8_truncated(items->names[items->totitem], items->maxstrlen);
142 }
143 if (items->pointers) {
144 items->pointers[items->totitem] = poin;
145 }
146 if (items->icons) {
147 items->icons[items->totitem] = iconid;
148 }
149
150 if (name_prefix_offset != 0) {
151 /* Lazy initialize, as this isn't used often. */
152 if (items->name_prefix_offsets == nullptr) {
153 items->name_prefix_offsets = (uint8_t *)MEM_callocN(
154 items->maxitem * sizeof(*items->name_prefix_offsets), __func__);
155 }
156 items->name_prefix_offsets[items->totitem] = name_prefix_offset;
157 }
158
159 /* Limit flags that can be set so flags such as 'UI_SELECT' aren't accidentally set
160 * which will cause problems, add others as needed. */
161 BLI_assert((but_flag &
163 if (items->but_flags) {
164 items->but_flags[items->totitem] = but_flag;
165 }
166
167 items->totitem++;
168
169 return true;
170}
171
176
178{
179 return 12 * UI_UNIT_X;
180}
181
183{
184 using namespace blender;
185
186 /* Compute the width of each item. */
187 Array<int> item_widths(items.totitem);
188 threading::parallel_for(item_widths.index_range(), 256, [&](const IndexRange range) {
189 for (const int i : range) {
190 const blender::StringRefNull name = items.names[i];
191 const int icon = items.icons ? items.icons[i] : ICON_NONE;
192 const float text_width = BLF_width(BLF_default(), name.c_str(), name.size(), nullptr);
193 const float icon_with_padding = icon == ICON_NONE ? 0.0f : UI_ICON_SIZE + UI_UNIT_X;
194 const float padding = UI_UNIT_X;
195 item_widths[i] = int(text_width + padding + icon_with_padding);
196 }
197 });
198
199 /* Compute the final width of the search box. */
200 int box_width = UI_searchbox_size_x();
201 for (const int width : item_widths) {
202 box_width = std::max(box_width, width);
203 }
204 /* Avoid extremely wide boxes. */
205 box_width = std::min(box_width, UI_searchbox_size_x() * 5);
206 return box_width;
207}
208
209int UI_searchbox_size_x_guess(const bContext *C, const uiButSearchUpdateFn update_fn, void *arg)
210{
211 using namespace blender;
212
213 uiSearchItems items{};
214 /* Upper bound on the number of item names that are checked. */
215 items.maxitem = 1000;
216 items.maxstrlen = 256;
217
218 /* Prepare name buffers. */
219 Array<char> names_buffer(items.maxitem * items.maxstrlen);
220 Array<char *> names(items.maxitem);
221 Array<int> icons(items.maxitem);
222 items.names = names.data();
223 items.icons = icons.data();
224 for (int i : IndexRange(items.maxitem)) {
225 names[i] = names_buffer.data() + i * items.maxstrlen;
226 }
227
228 /* Gather the items shown in the search box. */
229 update_fn(C, arg, "", &items, true);
230
231 /* This is lazy-initialized in #UI_search_item_add. */
233
234 return ui_searchbox_size_x_from_items(items);
235}
236
237int UI_search_items_find_index(const uiSearchItems *items, const char *name)
238{
239 if (items->name_prefix_offsets != nullptr) {
240 for (int i = 0; i < items->totitem; i++) {
241 if (STREQ(name, items->names[i] + items->name_prefix_offsets[i])) {
242 return i;
243 }
244 }
245 }
246 else {
247 for (int i = 0; i < items->totitem; i++) {
248 if (STREQ(name, items->names[i])) {
249 return i;
250 }
251 }
252 }
253 return -1;
254}
255
256/* region is the search box itself */
257static void ui_searchbox_select(bContext *C, ARegion *region, uiBut *but, int step)
258{
259 uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
260
261 /* apply step */
262 data->active += step;
263
264 if (data->items.totitem == 0) {
265 data->active = -1;
266 }
267 else if (data->active >= data->items.totitem) {
268 if (data->items.more) {
269 data->items.offset++;
270 data->active = data->items.totitem - 1;
271 ui_searchbox_update(C, region, but, false);
272 }
273 else {
274 data->active = data->items.totitem - 1;
275 }
276 }
277 else if (data->active < 0) {
278 if (data->items.offset) {
279 data->items.offset--;
280 data->active = 0;
281 ui_searchbox_update(C, region, but, false);
282 }
283 else {
284 /* only let users step into an 'unset' state for unlink buttons */
285 data->active = (but->flag & UI_BUT_VALUE_CLEAR) ? -1 : 0;
286 }
287 }
288
289 ED_region_tag_redraw(region);
290}
291
292static void ui_searchbox_butrect(rcti *r_rect, uiSearchboxData *data, int itemnr)
293{
294 const float tria_h = data->zoom * UI_SEARCHBOX_TRIA_H;
295
296 /* thumbnail preview */
297 if (data->preview) {
298 const int butw = BLI_rcti_size_x(&data->bbox) / data->prv_cols;
299 const int buth = (BLI_rcti_size_y(&data->bbox) - 2.0f * tria_h) / data->prv_rows;
300 int row, col;
301
302 *r_rect = data->bbox;
303
304 col = itemnr % data->prv_cols;
305 row = itemnr / data->prv_cols;
306
307 r_rect->xmin += col * butw;
308 r_rect->xmax = r_rect->xmin + butw;
309
310 r_rect->ymax -= tria_h + row * buth;
311 r_rect->ymin = r_rect->ymax - buth;
312 }
313 /* list view */
314 else {
315 const float buth = (BLI_rcti_size_y(&data->bbox) - 2.0f * tria_h) / SEARCH_ITEMS;
316
317 *r_rect = data->bbox;
318
319 r_rect->xmin = data->bbox.xmin;
320 r_rect->xmax = data->bbox.xmax;
321
322 r_rect->ymax = data->bbox.ymax - tria_h - itemnr * buth;
323 r_rect->ymin = r_rect->ymax - buth;
324 }
325}
326
327int ui_searchbox_find_index(ARegion *region, const char *name)
328{
329 uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
330 return UI_search_items_find_index(&data->items, name);
331}
332
333bool ui_searchbox_inside(ARegion *region, const int xy[2])
334{
335 uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
336
337 return BLI_rcti_isect_pt(&data->bbox, xy[0] - region->winrct.xmin, xy[1] - region->winrct.ymin);
338}
339
341{
342 uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
343 uiButSearch *search_but = (uiButSearch *)but;
344
346
347 search_but->item_active = nullptr;
348
349 if (data->active != -1) {
350 const char *name = data->items.names[data->active] +
351 /* Never include the prefix in the button. */
352 (data->items.name_prefix_offsets ?
353 data->items.name_prefix_offsets[data->active] :
354 0);
355
356 const char *name_sep = data->use_shortcut_sep ? strrchr(name, UI_SEP_CHAR) : nullptr;
357
358 /* Search button with dynamic string properties may have their own method of applying
359 * the search results, so only copy the result if there is a proper space for it. */
360 if (but->hardmax != 0) {
361 BLI_strncpy(but->editstr, name, name_sep ? (name_sep - name) + 1 : data->items.maxstrlen);
362 }
363
364 search_but->item_active = data->items.pointers[data->active];
365 MEM_SAFE_FREE(search_but->item_active_str);
366 search_but->item_active_str = BLI_strdup(data->items.names[data->active]);
367
368 return true;
369 }
370 return false;
371}
372
374 bContext *C, ARegion *region, int * /*r_pass*/, double * /*pass_delay*/, bool *r_exit_on_event)
375{
376 *r_exit_on_event = true;
377
378 LISTBASE_FOREACH (uiBlock *, block, &region->runtime->uiblocks) {
379 for (const std::unique_ptr<uiBut> &but : block->buttons) {
380 if (but->type != UI_BTYPE_SEARCH_MENU) {
381 continue;
382 }
383
384 uiButSearch *search_but = (uiButSearch *)but.get();
385 if (!search_but->item_tooltip_fn) {
386 continue;
387 }
388
389 ARegion *searchbox_region = UI_region_searchbox_region_get(region);
390 uiSearchboxData *data = static_cast<uiSearchboxData *>(searchbox_region->regiondata);
391
392 BLI_assert(data->items.pointers[data->active] == search_but->item_active);
393
394 rcti rect;
395 ui_searchbox_butrect(&rect, data, data->active);
396
397 return search_but->item_tooltip_fn(
398 C, region, &rect, search_but->arg, search_but->item_active);
399 }
400 }
401 return nullptr;
402}
403
405 bContext *C, ARegion *region, uiBut *but, ARegion *butregion, const wmEvent *event)
406{
407 uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
408 uiButSearch *search_but = (uiButSearch *)but;
409 int type = event->type, val = event->val;
410 bool handled = false;
411 bool tooltip_timer_started = false;
412
414
415 if (type == MOUSEPAN) {
416 ui_pan_to_scroll(event, &type, &val);
417 }
418
419 switch (type) {
420 case WHEELUPMOUSE:
421 case EVT_UPARROWKEY:
422 ui_searchbox_select(C, region, but, -1);
423 handled = true;
424 break;
425 case WHEELDOWNMOUSE:
426 case EVT_DOWNARROWKEY:
427 ui_searchbox_select(C, region, but, 1);
428 handled = true;
429 break;
430 case RIGHTMOUSE:
431 if (val) {
432 if (search_but->item_context_menu_fn) {
433 if (data->active != -1) {
434 /* Check the cursor is over the active element
435 * (a little confusing if this isn't the case, although it does work). */
436 rcti rect;
437 ui_searchbox_butrect(&rect, data, data->active);
439 &rect, event->xy[0] - region->winrct.xmin, event->xy[1] - region->winrct.ymin))
440 {
441
442 void *active = data->items.pointers[data->active];
443 if (search_but->item_context_menu_fn(C, search_but->arg, active, event)) {
444 handled = true;
445 }
446 }
447 }
448 }
449 }
450 break;
451 case MOUSEMOVE: {
452 bool is_inside = false;
453
454 if (BLI_rcti_isect_pt(&region->winrct, event->xy[0], event->xy[1])) {
455 rcti rect;
456 int a;
457
458 for (a = 0; a < data->items.totitem; a++) {
459 ui_searchbox_butrect(&rect, data, a);
461 &rect, event->xy[0] - region->winrct.xmin, event->xy[1] - region->winrct.ymin))
462 {
463 is_inside = true;
464 if (data->active != a) {
465 data->active = a;
466 ui_searchbox_select(C, region, but, 0);
467 handled = true;
468 break;
469 }
470 }
471 }
472 }
473
474 if (U.flag & USER_TOOLTIPS) {
475 if (is_inside) {
476 if (data->active != -1) {
477 ScrArea *area = CTX_wm_area(C);
478 search_but->item_active = data->items.pointers[data->active];
480 tooltip_timer_started = true;
481 }
482 }
483 }
484
485 break;
486 }
487 }
488
489 if (handled && (tooltip_timer_started == false)) {
490 wmWindow *win = CTX_wm_window(C);
491 WM_tooltip_clear(C, win);
492 }
493
494 return handled;
495}
496
499 uiButSearch *but,
500 const char *str,
501 uiSearchItems *items)
502{
503 /* While the button is in text editing mode (searchbox open), remove tooltips on every update. */
504 if (but->editstr) {
505 wmWindow *win = CTX_wm_window(C);
506 WM_tooltip_clear(C, win);
507 }
508 const bool is_first_search = !but->changed;
509 but->items_update_fn(C, but->arg, str, items, is_first_search);
510}
511
512void ui_searchbox_update(bContext *C, ARegion *region, uiBut *but, const bool reset)
513{
514 uiButSearch *search_but = (uiButSearch *)but;
515 uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
516
518
519 /* reset vars */
520 data->items.totitem = 0;
521 data->items.more = 0;
522 if (!reset) {
523 data->items.offset_i = data->items.offset;
524 }
525 else {
526 data->items.offset_i = data->items.offset = 0;
527 data->active = -1;
528
529 /* On init, find and center active item. */
530 const bool is_first_search = !but->changed;
531 if (is_first_search && search_but->items_update_fn && search_but->item_active) {
532 data->items.active = search_but->item_active;
533 ui_searchbox_update_fn(C, search_but, but->editstr, &data->items);
534 data->items.active = nullptr;
535
536 /* found active item, calculate real offset by centering it */
537 if (data->items.totitem) {
538 /* first case, begin of list */
539 if (data->items.offset_i < data->items.maxitem) {
540 data->active = data->items.offset_i;
541 data->items.offset_i = 0;
542 }
543 else {
544 /* second case, end of list */
545 if (data->items.totitem - data->items.offset_i <= data->items.maxitem) {
546 data->active = data->items.offset_i - data->items.totitem + data->items.maxitem;
547 data->items.offset_i = data->items.totitem - data->items.maxitem;
548 }
549 else {
550 /* center active item */
551 data->items.offset_i -= data->items.maxitem / 2;
552 data->active = data->items.maxitem / 2;
553 }
554 }
555 }
556 data->items.offset = data->items.offset_i;
557 data->items.totitem = 0;
558 }
559 }
560
561 /* callback */
562 if (search_but->items_update_fn) {
563 ui_searchbox_update_fn(C, search_but, but->editstr, &data->items);
564 }
565
566 /* handle case where editstr is equal to one of items */
567 if (reset && data->active == -1) {
568 for (int a = 0; a < data->items.totitem; a++) {
569 const char *name = data->items.names[a] +
570 /* Never include the prefix in the button. */
571 (data->items.name_prefix_offsets ? data->items.name_prefix_offsets[a] :
572 0);
573 const char *name_sep = data->use_shortcut_sep ? strrchr(name, UI_SEP_CHAR) : nullptr;
574 if (STREQLEN(but->editstr, name, name_sep ? (name_sep - name) : data->items.maxstrlen)) {
575 data->active = a;
576 break;
577 }
578 }
579 if (data->items.totitem == 1 && but->editstr[0]) {
580 data->active = 0;
581 }
582 }
583
584 /* validate selected item */
585 ui_searchbox_select(C, region, but, 0);
586
587 ED_region_tag_redraw(region);
588}
589
591{
592 uiButSearch *search_but = (uiButSearch *)but;
593 uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
594 int match = AUTOCOMPLETE_NO_MATCH;
595
597
598 if (str[0]) {
599 int maxncpy = ui_but_string_get_maxncpy(but);
600 if (maxncpy == 0) {
601 /* The string length is dynamic, just assume a reasonable length. */
602 maxncpy = strlen(str) + 1024;
603 }
604 data->items.autocpl = UI_autocomplete_begin(str, maxncpy);
605
606 ui_searchbox_update_fn(C, search_but, but->editstr, &data->items);
607
608 match = UI_autocomplete_end(data->items.autocpl, str);
609 data->items.autocpl = nullptr;
610 }
611
612 return match;
613}
614
620static void ui_searchbox_draw_clip_tri_down(rcti *rect, const float zoom)
621{
622 const float x = BLI_rcti_cent_x(rect) - (0.5f * zoom * UI_ICON_SIZE);
623 const float y = rect->ymin - (0.5f * zoom * (UI_SEARCHBOX_TRIA_H - UI_ICON_SIZE) - U.pixelsize) -
624 zoom * UI_ICON_SIZE;
625 const float aspect = U.inv_scale_factor / zoom;
628 x, y, ICON_TRIA_DOWN, aspect, 1.0f, 0.0f, nullptr, false, UI_NO_ICON_OVERLAY_TEXT);
630}
631
637static void ui_searchbox_draw_clip_tri_up(rcti *rect, const float zoom)
638{
639 const float x = BLI_rcti_cent_x(rect) - (0.5f * zoom * UI_ICON_SIZE);
640 const float y = rect->ymax + (0.5f * zoom * (UI_SEARCHBOX_TRIA_H - UI_ICON_SIZE) - U.pixelsize);
641 const float aspect = U.inv_scale_factor / zoom;
643 UI_icon_draw_ex(x, y, ICON_TRIA_UP, aspect, 1.0f, 0.0f, nullptr, false, UI_NO_ICON_OVERLAY_TEXT);
645}
646
647static void ui_searchbox_region_draw_fn(const bContext *C, ARegion *region)
648{
649 uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
650
651 /* pixel space */
653
654 if (data->noback == false) {
655 ui_draw_widget_menu_back(&data->bbox, true);
656 }
657
658 /* draw text */
659 if (data->items.totitem) {
660 rcti rect;
661
662 if (data->preview) {
663 /* draw items */
664 for (int a = 0; a < data->items.totitem; a++) {
665 const int but_flag = ((a == data->active) ? UI_HOVER : 0) | data->items.but_flags[a];
666
667 /* ensure icon is up-to-date */
668 ui_icon_ensure_deferred(C, data->items.icons[a], data->preview);
669
670 ui_searchbox_butrect(&rect, data, a);
671
672 /* widget itself */
673 ui_draw_preview_item(&data->fstyle,
674 &rect,
675 data->zoom,
676 data->items.names[a],
677 data->items.icons[a],
678 but_flag,
680 }
681
682 /* indicate more */
683 if (data->items.more || data->items.offset) {
684 rcti rect_first_item;
685 ui_searchbox_butrect(&rect_first_item, data, 0);
686 rcti rect_max_item;
687 ui_searchbox_butrect(&rect_max_item, data, data->items.maxitem - 1);
688
689 if (data->items.offset) {
690 /* The first item is in the top left corner. Adjust width so the icon is centered. */
691 rect_first_item.xmax = rect_max_item.xmax;
692 ui_searchbox_draw_clip_tri_up(&rect_first_item, data->zoom);
693 }
694
695 if (data->items.more) {
696 /* The last item is in the bottom right corner. Adjust width so the icon is centered. */
697 rect_max_item.xmin = rect_first_item.xmin;
698 ui_searchbox_draw_clip_tri_down(&rect_max_item, data->zoom);
699 }
700 }
701 }
702 else {
703 const int search_sep_len = data->sep_string ? strlen(data->sep_string) : 0;
704 /* draw items */
705 for (int a = 0; a < data->items.totitem; a++) {
706 const int but_flag = ((a == data->active) ? UI_HOVER : 0) | data->items.but_flags[a];
707 const char *name = data->items.names[a];
708 int icon = data->items.icons[a];
709 char *name_sep_test = nullptr;
710
712 if (data->use_shortcut_sep) {
713 separator_type = UI_MENU_ITEM_SEPARATOR_SHORTCUT;
714 }
715 /* Only set for displaying additional hint (e.g. library name of a linked data-block). */
716 else if (but_flag & UI_BUT_HAS_SEP_CHAR) {
717 separator_type = UI_MENU_ITEM_SEPARATOR_HINT;
718 }
719
720 ui_searchbox_butrect(&rect, data, a);
721
722 /* widget itself */
723 if ((search_sep_len == 0) ||
724 !(name_sep_test = strstr(data->items.names[a], data->sep_string)))
725 {
726 if (!icon && data->items.has_icon) {
727 /* If there is any icon item, make sure all items line up. */
728 icon = ICON_BLANK1;
729 }
730
731 /* Simple menu item. */
732 ui_draw_menu_item(&data->fstyle,
733 &rect,
734 &rect,
735 data->zoom,
736 data->noback,
737 name,
738 icon,
739 but_flag,
740 separator_type,
741 nullptr);
742 }
743 else {
744 /* Split menu item, faded text before the separator. */
745 char *name_sep = nullptr;
746 do {
747 name_sep = name_sep_test;
748 name_sep_test = strstr(name_sep + search_sep_len, data->sep_string);
749 } while (name_sep_test != nullptr);
750
751 name_sep += search_sep_len;
752 const char name_sep_prev = *name_sep;
753 *name_sep = '\0';
754 int name_width = 0;
755 ui_draw_menu_item(&data->fstyle,
756 &rect,
757 &rect,
758 data->zoom,
759 data->noback,
760 name,
761 ICON_NONE,
762 but_flag | UI_BUT_INACTIVE,
764 &name_width);
765 *name_sep = name_sep_prev;
766 rect.xmin += name_width;
767 rect.xmin += UI_UNIT_X / 4;
768
769 if (icon == ICON_BLANK1) {
770 icon = ICON_NONE;
771 }
772 if (icon != ICON_NONE) {
773 rect.xmin += UI_UNIT_X / 8;
774 }
775
776 /* The previous menu item draws the active selection. */
777 ui_draw_menu_item(&data->fstyle,
778 &rect,
779 nullptr,
780 data->zoom,
781 data->noback,
782 name_sep,
783 icon,
784 but_flag,
785 separator_type,
786 nullptr);
787 }
788 }
789 /* indicate more */
790 if (data->items.more) {
791 ui_searchbox_butrect(&rect, data, data->items.maxitem - 1);
793 }
794 if (data->items.offset) {
795 ui_searchbox_butrect(&rect, data, 0);
797 }
798 }
799 }
800 else {
801 rcti rect;
802 ui_searchbox_butrect(&rect, data, 0);
803 ui_draw_menu_item(&data->fstyle,
804 &rect,
805 &rect,
806 data->zoom,
807 data->noback,
808 IFACE_("No results found"),
809 0,
810 0,
812 nullptr);
813 }
814}
815
817{
818 uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
819
820 /* free search data */
821 for (int a = 0; a < data->items.maxitem; a++) {
822 MEM_freeN(data->items.names[a]);
823 }
824 MEM_freeN(data->items.names);
825 MEM_freeN(data->items.pointers);
826 MEM_freeN(data->items.icons);
827 MEM_freeN(data->items.but_flags);
828
829 if (data->items.name_prefix_offsets != nullptr) {
830 MEM_freeN(data->items.name_prefix_offsets);
831 }
832
834 region->regiondata = nullptr;
835}
836
838{
839 uiSearchboxData *data = static_cast<uiSearchboxData *>(params->region->regiondata);
840 if (data->search_listener) {
841 data->search_listener(params, data->search_arg);
842 }
843}
844
846{
848
849 if (data->size_set) {
850 /* Already set. */
851 return;
852 }
853
854 uiButSearch *but = data->search_but;
855 ARegion *butregion = data->butregion;
856 const int margin = UI_POPUP_MARGIN;
857 wmWindow *win = CTX_wm_window(C);
858
859 /* compute position */
860 if (but->block->flag & UI_BLOCK_SEARCH_MENU) {
861 /* this case is search menu inside other menu */
862 /* we copy region size */
863
864 region->winrct = butregion->winrct;
865
866 /* Align menu items with the search button. */
867 const float zoom = data->zoom;
868 const int padding = zoom * UI_SEARCHBOX_BOUNDS - (data->preview ? 0 : U.pixelsize);
869 const int search_but_h = BLI_rctf_size_y(&but->rect) + zoom * UI_SEARCHBOX_BOUNDS;
870
871 /* widget rect, in region coords */
872 data->bbox.xmin = margin + padding;
873 data->bbox.xmax = BLI_rcti_size_x(&region->winrct) - (margin + padding);
874 data->bbox.ymin = margin;
875 data->bbox.ymax = BLI_rcti_size_y(&region->winrct) - UI_POPUP_MENU_TOP;
876
877 /* check if button is lower half */
878 if (but->rect.ymax < BLI_rctf_cent_y(&but->block->rect)) {
879 data->bbox.ymin += search_but_h;
880 }
881 else {
882 data->bbox.ymax -= search_but_h;
883 }
884 }
885 else {
886 const int searchbox_width = ui_searchbox_size_x_from_items(data->items);
887
888 rctf rect_fl;
889 rect_fl.xmin = but->rect.xmin;
890 rect_fl.xmax = but->rect.xmax;
891 rect_fl.ymax = but->rect.ymin;
892 rect_fl.ymin = rect_fl.ymax - UI_searchbox_size_y();
893
894 const int ofsx = (but->block->panel) ? but->block->panel->ofsx : 0;
895 const int ofsy = (but->block->panel) ? but->block->panel->ofsy : 0;
896
897 BLI_rctf_translate(&rect_fl, ofsx, ofsy);
898
899 /* minimal width */
900 if (BLI_rctf_size_x(&rect_fl) < searchbox_width) {
901 rect_fl.xmax = rect_fl.xmin + searchbox_width;
902 }
903
904 /* copy to int, gets projected if possible too */
905 rcti rect_i;
906 BLI_rcti_rctf_copy(&rect_i, &rect_fl);
907
908 if (butregion->v2d.cur.xmin != butregion->v2d.cur.xmax) {
909 UI_view2d_view_to_region_rcti(&butregion->v2d, &rect_fl, &rect_i);
910 }
911
912 BLI_rcti_translate(&rect_i, butregion->winrct.xmin, butregion->winrct.ymin);
913
914 int winx = WM_window_native_pixel_x(win);
915 // winy = WM_window_pixels_y(win); /* UNUSED */
916 // wm_window_get_size(win, &winx, &winy);
917
918 if (rect_i.xmax > winx) {
919 /* super size */
920 if (rect_i.xmax > winx + rect_i.xmin) {
921 rect_i.xmax = winx;
922 rect_i.xmin = 0;
923 }
924 else {
925 rect_i.xmin -= rect_i.xmax - winx;
926 rect_i.xmax = winx;
927 }
928 }
929
930 if (rect_i.ymin < 0) {
931 int newy1 = but->rect.ymax + ofsy;
932
933 if (butregion->v2d.cur.xmin != butregion->v2d.cur.xmax) {
934 newy1 = UI_view2d_view_to_region_y(&butregion->v2d, newy1);
935 }
936
937 newy1 += butregion->winrct.ymin;
938
939 rect_i.ymax = BLI_rcti_size_y(&rect_i) + newy1;
940 rect_i.ymin = newy1;
941 }
942
943 /* widget rect, in region coords */
944 data->bbox.xmin = margin;
945 data->bbox.xmax = BLI_rcti_size_x(&rect_i) + margin;
946 data->bbox.ymin = margin;
947 data->bbox.ymax = BLI_rcti_size_y(&rect_i) + margin;
948
949 /* region bigger for shadow */
950 region->winrct.xmin = rect_i.xmin - margin;
951 region->winrct.xmax = rect_i.xmax + margin;
952 region->winrct.ymin = rect_i.ymin - margin;
953 region->winrct.ymax = rect_i.ymax;
954 }
955
956 region->winx = region->winrct.xmax - region->winrct.xmin + 1;
957 region->winy = region->winrct.ymax - region->winrct.ymin + 1;
958
959 data->size_set = true;
960}
961
963 ARegion *butregion,
964 uiButSearch *but,
965 const bool use_shortcut_sep)
966{
967 const uiStyle *style = UI_style_get();
968 const float aspect = but->block->aspect;
969
970 /* create area region */
972
973 static ARegionType type;
974 memset(&type, 0, sizeof(ARegionType));
980 region->runtime->type = &type;
981
982 /* Create search-box data. */
984 data->search_arg = but->arg;
985 data->search_but = but;
986 data->butregion = butregion;
987 data->size_set = false;
988 data->search_listener = but->listen_fn;
989 data->zoom = 1.0f / aspect;
990
991 /* Set font, get the bounding-box. */
992 data->fstyle = style->widget; /* copy struct */
993 ui_fontscale(&data->fstyle.points, aspect);
994 UI_fontstyle_set(&data->fstyle);
995
996 region->regiondata = data;
997
998 /* Special case, hard-coded feature, not draw backdrop when called from menus,
999 * assume for design that popup already added it. */
1000 if (but->block->flag & UI_BLOCK_SEARCH_MENU) {
1001 data->noback = true;
1002 }
1003
1004 if (but->preview_rows > 0 && but->preview_cols > 0) {
1005 data->preview = true;
1006 data->prv_rows = but->preview_rows;
1007 data->prv_cols = but->preview_cols;
1008 }
1009
1010 if (but->optype != nullptr || use_shortcut_sep) {
1011 data->use_shortcut_sep = true;
1012 }
1013 data->sep_string = but->item_sep_string;
1014
1015 /* Adds sub-window. */
1017
1018 /* notify change and redraw */
1019 ED_region_tag_redraw(region);
1020
1021 /* prepare search data */
1022 if (data->preview) {
1023 data->items.maxitem = data->prv_rows * data->prv_cols;
1024 }
1025 else {
1026 data->items.maxitem = SEARCH_ITEMS;
1027 }
1028 /* In case the button's string is dynamic, make sure there are buffers available. */
1029 data->items.maxstrlen = but->hardmax == 0 ? UI_MAX_NAME_STR : but->hardmax;
1030 data->items.totitem = 0;
1031 data->items.names = (char **)MEM_callocN(data->items.maxitem * sizeof(void *), __func__);
1032 data->items.pointers = (void **)MEM_callocN(data->items.maxitem * sizeof(void *), __func__);
1033 data->items.icons = MEM_calloc_arrayN<int>(data->items.maxitem, __func__);
1034 data->items.but_flags = MEM_calloc_arrayN<int>(data->items.maxitem, __func__);
1035 data->items.name_prefix_offsets = nullptr; /* Lazy initialized as needed. */
1036 for (int i = 0; i < data->items.maxitem; i++) {
1037 data->items.names[i] = (char *)MEM_callocN(data->items.maxstrlen + 1, __func__);
1038 }
1039
1040 return region;
1041}
1042
1044{
1045 return ui_searchbox_create_generic_ex(C, butregion, search_but, false);
1046}
1047
1054static void str_tolower_titlecaps_ascii(char *str, const size_t len)
1055{
1056 bool prev_delim = true;
1057
1058 for (size_t i = 0; (i < len) && str[i]; i++) {
1059 if (str[i] >= 'A' && str[i] <= 'Z') {
1060 if (prev_delim == false) {
1061 str[i] += 'a' - 'A';
1062 }
1063 }
1064 else if (str[i] == '_') {
1065 str[i] = ' ';
1066 }
1067
1068 prev_delim = ELEM(str[i], ' ') || (str[i] >= '0' && str[i] <= '9');
1069 }
1070}
1071
1072static void ui_searchbox_region_draw_cb__operator(const bContext * /*C*/, ARegion *region)
1073{
1074 uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
1075
1076 /* pixel space */
1078
1079 if (data->noback == false) {
1080 ui_draw_widget_menu_back(&data->bbox, true);
1081 }
1082
1083 /* draw text */
1084 if (data->items.totitem) {
1085 rcti rect;
1086
1087 /* draw items */
1088 for (int a = 0; a < data->items.totitem; a++) {
1089 rcti rect_pre, rect_post;
1090 ui_searchbox_butrect(&rect, data, a);
1091
1092 rect_pre = rect;
1093 rect_post = rect;
1094
1095 rect_pre.xmax = rect_post.xmin = rect.xmin + ((rect.xmax - rect.xmin) / 4);
1096
1097 /* widget itself */
1098 /* NOTE: i18n messages extracting tool does the same, please keep it in sync. */
1099 {
1100 const int but_flag = ((a == data->active) ? UI_HOVER : 0) | data->items.but_flags[a];
1101
1102 wmOperatorType *ot = static_cast<wmOperatorType *>(data->items.pointers[a]);
1103 char text_pre[128];
1104 const char *text_pre_p = strstr(ot->idname, "_OT_");
1105 if (text_pre_p == nullptr) {
1106 text_pre[0] = '\0';
1107 }
1108 else {
1109 int text_pre_len;
1110 text_pre_p += 1;
1111 text_pre_len = BLI_strncpy_rlen(
1112 text_pre, ot->idname, min_ii(sizeof(text_pre), text_pre_p - ot->idname));
1113 text_pre[text_pre_len] = ':';
1114 text_pre[text_pre_len + 1] = '\0';
1115 str_tolower_titlecaps_ascii(text_pre, sizeof(text_pre));
1116 }
1117
1118 ui_draw_menu_item(&data->fstyle,
1119 &rect_pre,
1120 &rect,
1121 data->zoom,
1122 data->noback,
1124 data->items.icons[a],
1125 but_flag,
1127 nullptr);
1128 ui_draw_menu_item(&data->fstyle,
1129 &rect_post,
1130 nullptr,
1131 data->zoom,
1132 data->noback,
1133 data->items.names[a],
1134 0,
1135 but_flag,
1136 data->use_shortcut_sep ? UI_MENU_ITEM_SEPARATOR_SHORTCUT :
1138 nullptr);
1139 }
1140 }
1141 /* indicate more */
1142 if (data->items.more) {
1143 ui_searchbox_butrect(&rect, data, data->items.maxitem - 1);
1145 }
1146 if (data->items.offset) {
1147 ui_searchbox_butrect(&rect, data, 0);
1148 ui_searchbox_draw_clip_tri_up(&rect, data->zoom);
1149 }
1150 }
1151 else {
1152 rcti rect;
1153 ui_searchbox_butrect(&rect, data, 0);
1154 ui_draw_menu_item(&data->fstyle,
1155 &rect,
1156 &rect,
1157 data->zoom,
1158 data->noback,
1159 IFACE_("No results found"),
1160 0,
1161 0,
1163 nullptr);
1164 }
1165}
1166
1168{
1169 ARegion *region = ui_searchbox_create_generic_ex(C, butregion, search_but, true);
1170
1171 region->runtime->type->draw = ui_searchbox_region_draw_cb__operator;
1172
1173 return region;
1174}
1175
1177{
1179}
1180
1181static void ui_searchbox_region_draw_cb__menu(const bContext * /*C*/, ARegion * /*region*/)
1182{
1183 /* Currently unused. */
1184}
1185
1187{
1188 ARegion *region = ui_searchbox_create_generic_ex(C, butregion, search_but, true);
1189
1190 if (false) {
1191 region->runtime->type->draw = ui_searchbox_region_draw_cb__menu;
1192 }
1193
1194 return region;
1195}
1196
1198{
1199 /* possibly very large lists (such as ID datablocks) only
1200 * only validate string RNA buts (not pointers) */
1201 if (but->rnaprop && RNA_property_type(but->rnaprop) != PROP_STRING) {
1202 return;
1203 }
1204
1205 uiSearchItems *items = MEM_callocN<uiSearchItems>(__func__);
1206
1207 /* setup search struct */
1208 items->maxitem = 10;
1209 items->maxstrlen = 256;
1210 items->names = (char **)MEM_callocN(items->maxitem * sizeof(void *), __func__);
1211 for (int i = 0; i < items->maxitem; i++) {
1212 items->names[i] = (char *)MEM_callocN(but->hardmax + 1, __func__);
1213 }
1214
1215 ui_searchbox_update_fn((bContext *)but->block->evil_C, but, but->drawstr.c_str(), items);
1216
1217 if (!but->results_are_suggestions) {
1218 /* Only red-alert when we are sure of it, this can miss cases when >10 matches. */
1219 if (items->totitem == 0) {
1221 }
1222 else if (items->more == 0) {
1223 if (UI_search_items_find_index(items, but->drawstr.c_str()) == -1) {
1225 }
1226 }
1227 }
1228
1229 for (int i = 0; i < items->maxitem; i++) {
1230 MEM_freeN(items->names[i]);
1231 }
1232 MEM_freeN(items->names);
1233 MEM_freeN(items);
1234}
1235
bScreen * CTX_wm_screen(const bContext *C)
ScrArea * CTX_wm_area(const bContext *C)
wmWindow * CTX_wm_window(const bContext *C)
#define BLI_assert(a)
Definition BLI_assert.h:46
#define LISTBASE_FOREACH(type, var, list)
MINLINE int min_ii(int a, int b)
void BLI_rctf_translate(struct rctf *rect, float x, float y)
Definition rct.cc:573
BLI_INLINE int BLI_rcti_size_y(const struct rcti *rct)
Definition BLI_rect.h:198
BLI_INLINE float BLI_rctf_cent_y(const struct rctf *rct)
Definition BLI_rect.h:189
void BLI_rcti_translate(struct rcti *rect, int x, int y)
Definition rct.cc:566
bool BLI_rcti_isect_pt(const struct rcti *rect, int x, int y)
BLI_INLINE int BLI_rcti_size_x(const struct rcti *rct)
Definition BLI_rect.h:194
BLI_INLINE float BLI_rctf_size_x(const struct rctf *rct)
Definition BLI_rect.h:202
void BLI_rcti_rctf_copy(struct rcti *dst, const struct rctf *src)
BLI_INLINE float BLI_rctf_size_y(const struct rctf *rct)
Definition BLI_rect.h:206
BLI_INLINE int BLI_rcti_cent_x(const struct rcti *rct)
Definition BLI_rect.h:177
char * BLI_strdup(const char *str) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) ATTR_MALLOC
Definition string.cc:41
char char size_t BLI_strncpy_rlen(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1
char * BLI_strncpy(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
#define STREQLEN(a, b, n)
#define ELEM(...)
#define STREQ(a, b)
#define CTX_IFACE_(context, msgid)
#define BLT_I18NCONTEXT_OPERATOR_DEFAULT
#define IFACE_(msgid)
@ RGN_TYPE_TEMPORARY
#define UI_ICON_SIZE
@ USER_TOOLTIPS
void ED_region_floating_init(ARegion *region)
Definition area.cc:2305
void ED_region_tag_redraw(ARegion *region)
Definition area.cc:639
@ GPU_BLEND_NONE
Definition GPU_state.hh:85
@ GPU_BLEND_ALPHA
Definition GPU_state.hh:87
void GPU_blend(eGPUBlend blend)
Definition gpu_state.cc:42
Read Guarded memory(de)allocation.
@ PROP_STRING
Definition RNA_types.hh:153
#define C
Definition RandGen.cpp:29
#define UI_UNIT_Y
#define UI_SEP_CHAR
void(*)(const bContext *C, void *arg, const char *str, uiSearchItems *items, bool is_first) uiButSearchUpdateFn
ARegion * UI_region_searchbox_region_get(const ARegion *button_region)
#define AUTOCOMPLETE_NO_MATCH
@ UI_STYLE_TEXT_LEFT
int UI_autocomplete_end(AutoComplete *autocpl, char *autoname)
const uiStyle * UI_style_get()
void UI_fontstyle_set(const uiFontStyle *fs)
#define UI_SEARCHBOX_BOUNDS
void UI_autocomplete_update_name(AutoComplete *autocpl, blender::StringRef name)
@ UI_BLOCK_SEARCH_MENU
#define UI_UNIT_X
@ UI_BTYPE_SEARCH_MENU
@ UI_BUT_REDALERT
@ UI_BUT_DISABLED
@ UI_BUT_INACTIVE
@ UI_BUT_HAS_SEP_CHAR
@ UI_BUT_VALUE_CLEAR
AutoComplete * UI_autocomplete_begin(const char *startname, size_t maxncpy)
void UI_but_flag_enable(uiBut *but, int flag)
void(*)(const wmRegionListenerParams *params, void *arg) uiButSearchListenFn
#define UI_SEARCHBOX_TRIA_H
#define UI_NO_ICON_OVERLAY_TEXT
void UI_icon_draw_ex(float x, float y, int icon_id, float aspect, float alpha, float desaturate, const uchar mono_color[4], bool mono_border, const IconTextOverlay *text_overlay, const bool inverted=false)
#define UI_MAX_NAME_STR
float UI_view2d_view_to_region_y(const View2D *v2d, float y)
Definition view2d.cc:1695
void UI_view2d_view_to_region_rcti(const View2D *v2d, const rctf *rect_src, rcti *rect_dst) ATTR_NONNULL()
Definition view2d.cc:1785
#define U
BMesh const char void * data
void reset()
clear internal cached data and reset random seed
const T * data() const
Definition BLI_array.hh:301
IndexRange index_range() const
Definition BLI_array.hh:349
void copy_utf8_truncated(char *dst, int64_t dst_size) const
Definition string_ref.cc:28
constexpr StringRef drop_prefix(int64_t n) const
#define str(s)
static bool is_inside(int x, int y, int cols, int rows)
Definition filesel.cc:772
uint col
#define active
VecBase< float, D > step(VecOp< float, D >, VecOp< float, D >) RET
#define MEM_SAFE_FREE(v)
uint padding(uint offset, uint alignment)
int ui_but_string_get_maxncpy(uiBut *but)
void ui_fontscale(float *points, float aspect)
void ui_pan_to_scroll(const wmEvent *event, int *type, int *val)
void ui_icon_ensure_deferred(const bContext *C, const int icon_id, const bool big)
#define UI_POPUP_MENU_TOP
@ UI_HOVER
void ui_draw_preview_item(const uiFontStyle *fstyle, rcti *rect, float zoom, const char *name, int iconid, int but_flag, eFontStyle_Align text_align)
void ui_draw_widget_menu_back(const rcti *rect, bool use_shadow)
#define UI_POPUP_MARGIN
void ui_draw_menu_item(const uiFontStyle *fstyle, rcti *rect, rcti *back_rect, float zoom, bool use_unpadded, const char *name, int iconid, int but_flag, uiMenuItemSeparatorType separator_type, int *r_xmax)
uiMenuItemSeparatorType
@ UI_MENU_ITEM_SEPARATOR_NONE
@ UI_MENU_ITEM_SEPARATOR_HINT
@ UI_MENU_ITEM_SEPARATOR_SHORTCUT
static void ui_searchbox_draw_clip_tri_up(rcti *rect, const float zoom)
int ui_searchbox_find_index(ARegion *region, const char *name)
void ui_searchbox_update(bContext *C, ARegion *region, uiBut *but, const bool reset)
static void str_tolower_titlecaps_ascii(char *str, const size_t len)
static ARegion * ui_searchbox_create_generic_ex(bContext *C, ARegion *butregion, uiButSearch *but, const bool use_shortcut_sep)
void ui_but_search_refresh(uiButSearch *but)
#define SEARCH_ITEMS
static void ui_searchbox_butrect(rcti *r_rect, uiSearchboxData *data, int itemnr)
static void ui_searchbox_select(bContext *C, ARegion *region, uiBut *but, int step)
ARegion * ui_searchbox_create_operator(bContext *C, ARegion *butregion, uiButSearch *search_but)
ARegion * ui_searchbox_create_generic(bContext *C, ARegion *butregion, uiButSearch *search_but)
ARegion * ui_searchbox_create_menu(bContext *C, ARegion *butregion, uiButSearch *search_but)
int UI_searchbox_size_x_guess(const bContext *C, const uiButSearchUpdateFn update_fn, void *arg)
void ui_searchbox_free(bContext *C, ARegion *region)
static void ui_searchbox_update_fn(bContext *C, uiButSearch *but, const char *str, uiSearchItems *items)
int UI_searchbox_size_x()
bool ui_searchbox_apply(uiBut *but, ARegion *region)
static void ui_searchbox_region_draw_fn(const bContext *C, ARegion *region)
static int ui_searchbox_size_x_from_items(const uiSearchItems &items)
int UI_search_items_find_index(const uiSearchItems *items, const char *name)
int UI_searchbox_size_y()
static void ui_searchbox_region_draw_cb__menu(const bContext *, ARegion *)
static void ui_searchbox_region_layout_fn(const bContext *C, ARegion *region)
bool ui_searchbox_event(bContext *C, ARegion *region, uiBut *but, ARegion *butregion, const wmEvent *event)
static void ui_searchbox_draw_clip_tri_down(rcti *rect, const float zoom)
static ARegion * wm_searchbox_tooltip_init(bContext *C, ARegion *region, int *, double *, bool *r_exit_on_event)
static void ui_searchbox_region_draw_cb__operator(const bContext *, ARegion *region)
static void ui_searchbox_region_free_fn(ARegion *region)
bool ui_searchbox_inside(ARegion *region, const int xy[2])
bool UI_search_item_add(uiSearchItems *items, const StringRef name, void *poin, int iconid, const int but_flag, const uint8_t name_prefix_offset)
int ui_searchbox_autocomplete(bContext *C, ARegion *region, uiBut *but, char *str)
static void ui_searchbox_region_listen_fn(const wmRegionListenerParams *params)
ARegion * ui_region_temp_add(bScreen *screen)
void ui_region_temp_remove(bContext *C, bScreen *screen, ARegion *region)
uiWidgetBaseParameters params[MAX_WIDGET_BASE_BATCH]
void * MEM_calloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:123
void * MEM_callocN(size_t len, const char *str)
Definition mallocn.cc:118
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
void parallel_for(const IndexRange range, const int64_t grain_size, const Function &function, const TaskSizeHints &size_hints=detail::TaskSizeHints_Static(1))
Definition BLI_task.hh:93
PropertyType RNA_property_type(PropertyRNA *prop)
void(* free)(ARegion *)
void(* listener)(const wmRegionListenerParams *params)
void(* draw)(const bContext *C, ARegion *region)
void(* layout)(const bContext *C, ARegion *region)
void * regiondata
ARegionRuntimeHandle * runtime
float xmax
float xmin
float ymax
float ymin
int ymin
int ymax
int xmin
int xmax
uiButSearchUpdateFn items_update_fn
uiButSearchListenFn listen_fn
const char * item_sep_string
uiButSearchTooltipFn item_tooltip_fn
uiButSearchContextMenuFn item_context_menu_fn
PropertyRNA * rnaprop
wmOperatorType * optype
char * editstr
eButType type
uiBlock * block
std::string drawstr
uiButSearchListenFn search_listener
uiFontStyle widget
int xy[2]
Definition WM_types.hh:758
i
Definition text_draw.cc:230
uint len
int xy[2]
Definition wm_draw.cc:174
@ MOUSEPAN
@ RIGHTMOUSE
@ EVT_DOWNARROWKEY
@ WHEELUPMOUSE
@ WHEELDOWNMOUSE
@ MOUSEMOVE
@ EVT_UPARROWKEY
wmOperatorType * ot
Definition wm_files.cc:4225
void wmOrtho2_region_pixelspace(const ARegion *region)
void WM_tooltip_clear(bContext *C, wmWindow *win)
Definition wm_tooltip.cc:82
void WM_tooltip_timer_init(bContext *C, wmWindow *win, ScrArea *area, ARegion *region, wmTooltipInitFn init)
Definition wm_tooltip.cc:64
int WM_window_native_pixel_x(const wmWindow *win)