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