Blender V4.3
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
11#include <cstdarg>
12#include <cstdlib>
13#include <cstring>
14#include <iostream>
15
16#include "DNA_ID.h"
17#include "MEM_guardedalloc.h"
18
19#include "DNA_userdef_types.h"
20
21#include "BLI_listbase.h"
22#include "BLI_rect.h"
23#include "BLI_string.h"
24#include "BLI_utildefines.h"
25
26#include "BKE_context.hh"
27#include "BKE_screen.hh"
28
29#include "WM_api.hh"
30#include "WM_types.hh"
31
32#include "RNA_access.hh"
33
34#include "UI_interface.hh"
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 "GPU_state.hh"
43#include "interface_intern.hh"
45
46#define MENU_BORDER int(0.3f * U.widget_unit)
47
48/* -------------------------------------------------------------------- */
54
55 int offset, offset_i; /* offset for inserting in array */
56 int more; /* flag indicating there are more items */
57
58 char **names;
59 void **pointers;
60 int *icons;
63
66
68 void *active;
69};
70
97
98#define SEARCH_ITEMS 10
99
101 const char *name,
102 void *poin,
103 int iconid,
104 const int but_flag,
105 const uint8_t name_prefix_offset)
106{
107 /* hijack for autocomplete */
108 if (items->autocpl) {
109 UI_autocomplete_update_name(items->autocpl, name + name_prefix_offset);
110 return true;
111 }
112
113 if (iconid) {
114 items->has_icon = true;
115 }
116
117 /* hijack for finding active item */
118 if (items->active) {
119 if (poin == items->active) {
120 items->offset_i = items->totitem;
121 }
122 items->totitem++;
123 return true;
124 }
125
126 if (items->totitem >= items->maxitem) {
127 items->more = 1;
128 return false;
129 }
130
131 /* skip first items in list */
132 if (items->offset_i > 0) {
133 items->offset_i--;
134 return true;
135 }
136
137 if (items->names) {
138 BLI_strncpy(items->names[items->totitem], name, items->maxstrlen);
139 }
140 if (items->pointers) {
141 items->pointers[items->totitem] = poin;
142 }
143 if (items->icons) {
144 items->icons[items->totitem] = iconid;
145 }
146
147 if (name_prefix_offset != 0) {
148 /* Lazy initialize, as this isn't used often. */
149 if (items->name_prefix_offsets == nullptr) {
151 items->maxitem * sizeof(*items->name_prefix_offsets), __func__);
152 }
153 items->name_prefix_offsets[items->totitem] = name_prefix_offset;
154 }
155
156 /* Limit flags that can be set so flags such as 'UI_SELECT' aren't accidentally set
157 * which will cause problems, add others as needed. */
158 BLI_assert((but_flag &
160 if (items->but_flags) {
161 items->but_flags[items->totitem] = but_flag;
162 }
163
164 items->totitem++;
165
166 return true;
167}
168
173
175{
176 return 12 * UI_UNIT_X;
177}
178
179int UI_search_items_find_index(const uiSearchItems *items, const char *name)
180{
181 if (items->name_prefix_offsets != nullptr) {
182 for (int i = 0; i < items->totitem; i++) {
183 if (STREQ(name, items->names[i] + items->name_prefix_offsets[i])) {
184 return i;
185 }
186 }
187 }
188 else {
189 for (int i = 0; i < items->totitem; i++) {
190 if (STREQ(name, items->names[i])) {
191 return i;
192 }
193 }
194 }
195 return -1;
196}
197
198/* region is the search box itself */
199static void ui_searchbox_select(bContext *C, ARegion *region, uiBut *but, int step)
200{
201 uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
202
203 /* apply step */
204 data->active += step;
205
206 if (data->items.totitem == 0) {
207 data->active = -1;
208 }
209 else if (data->active >= data->items.totitem) {
210 if (data->items.more) {
211 data->items.offset++;
212 data->active = data->items.totitem - 1;
213 ui_searchbox_update(C, region, but, false);
214 }
215 else {
216 data->active = data->items.totitem - 1;
217 }
218 }
219 else if (data->active < 0) {
220 if (data->items.offset) {
221 data->items.offset--;
222 data->active = 0;
223 ui_searchbox_update(C, region, but, false);
224 }
225 else {
226 /* only let users step into an 'unset' state for unlink buttons */
227 data->active = (but->flag & UI_BUT_VALUE_CLEAR) ? -1 : 0;
228 }
229 }
230
231 ED_region_tag_redraw(region);
232}
233
234static void ui_searchbox_butrect(rcti *r_rect, uiSearchboxData *data, int itemnr)
235{
236 /* thumbnail preview */
237 if (data->preview) {
238 const int butw = (BLI_rcti_size_x(&data->bbox) - 2 * MENU_BORDER) / data->prv_cols;
239 const int buth = (BLI_rcti_size_y(&data->bbox) - 2 * MENU_BORDER) / data->prv_rows;
240 int row, col;
241
242 *r_rect = data->bbox;
243
244 col = itemnr % data->prv_cols;
245 row = itemnr / data->prv_cols;
246
247 r_rect->xmin += MENU_BORDER + (col * butw);
248 r_rect->xmax = r_rect->xmin + butw;
249
250 r_rect->ymax -= MENU_BORDER + (row * buth);
251 r_rect->ymin = r_rect->ymax - buth;
252 }
253 /* list view */
254 else {
255 const int buth = (BLI_rcti_size_y(&data->bbox) - 2 * UI_POPUP_MENU_TOP) / SEARCH_ITEMS;
256
257 *r_rect = data->bbox;
258 r_rect->xmin = data->bbox.xmin + 3.0f;
259 r_rect->xmax = data->bbox.xmax - 3.0f;
260
261 r_rect->ymax = data->bbox.ymax - UI_POPUP_MENU_TOP - itemnr * buth;
262 r_rect->ymin = r_rect->ymax - buth;
263 }
264}
265
266int ui_searchbox_find_index(ARegion *region, const char *name)
267{
268 uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
269 return UI_search_items_find_index(&data->items, name);
270}
271
272bool ui_searchbox_inside(ARegion *region, const int xy[2])
273{
274 uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
275
276 return BLI_rcti_isect_pt(&data->bbox, xy[0] - region->winrct.xmin, xy[1] - region->winrct.ymin);
277}
278
280{
281 uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
282 uiButSearch *search_but = (uiButSearch *)but;
283
285
286 search_but->item_active = nullptr;
287
288 if (data->active != -1) {
289 const char *name = data->items.names[data->active] +
290 /* Never include the prefix in the button. */
291 (data->items.name_prefix_offsets ?
292 data->items.name_prefix_offsets[data->active] :
293 0);
294
295 const char *name_sep = data->use_shortcut_sep ? strrchr(name, UI_SEP_CHAR) : nullptr;
296
297 /* Search button with dynamic string properties may have their own method of applying
298 * the search results, so only copy the result if there is a proper space for it. */
299 if (but->hardmax != 0) {
300 BLI_strncpy(but->editstr, name, name_sep ? (name_sep - name) + 1 : data->items.maxstrlen);
301 }
302
303 search_but->item_active = data->items.pointers[data->active];
304 MEM_SAFE_FREE(search_but->item_active_str);
305 search_but->item_active_str = BLI_strdup(data->items.names[data->active]);
306
307 return true;
308 }
309 return false;
310}
311
313 bContext *C, ARegion *region, int * /*r_pass*/, double * /*pass_delay*/, bool *r_exit_on_event)
314{
315 *r_exit_on_event = true;
316
317 LISTBASE_FOREACH (uiBlock *, block, &region->uiblocks) {
318 LISTBASE_FOREACH (uiBut *, but, &block->buttons) {
319 if (but->type != UI_BTYPE_SEARCH_MENU) {
320 continue;
321 }
322
323 uiButSearch *search_but = (uiButSearch *)but;
324 if (!search_but->item_tooltip_fn) {
325 continue;
326 }
327
328 ARegion *searchbox_region = UI_region_searchbox_region_get(region);
329 uiSearchboxData *data = static_cast<uiSearchboxData *>(searchbox_region->regiondata);
330
331 BLI_assert(data->items.pointers[data->active] == search_but->item_active);
332
333 rcti rect;
334 ui_searchbox_butrect(&rect, data, data->active);
335
336 return search_but->item_tooltip_fn(
337 C, region, &rect, search_but->arg, search_but->item_active);
338 }
339 }
340 return nullptr;
341}
342
344 bContext *C, ARegion *region, uiBut *but, ARegion *butregion, const wmEvent *event)
345{
346 uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
347 uiButSearch *search_but = (uiButSearch *)but;
348 int type = event->type, val = event->val;
349 bool handled = false;
350 bool tooltip_timer_started = false;
351
353
354 if (type == MOUSEPAN) {
355 ui_pan_to_scroll(event, &type, &val);
356 }
357
358 switch (type) {
359 case WHEELUPMOUSE:
360 case EVT_UPARROWKEY:
361 ui_searchbox_select(C, region, but, -1);
362 handled = true;
363 break;
364 case WHEELDOWNMOUSE:
365 case EVT_DOWNARROWKEY:
366 ui_searchbox_select(C, region, but, 1);
367 handled = true;
368 break;
369 case RIGHTMOUSE:
370 if (val) {
371 if (search_but->item_context_menu_fn) {
372 if (data->active != -1) {
373 /* Check the cursor is over the active element
374 * (a little confusing if this isn't the case, although it does work). */
375 rcti rect;
376 ui_searchbox_butrect(&rect, data, data->active);
378 &rect, event->xy[0] - region->winrct.xmin, event->xy[1] - region->winrct.ymin))
379 {
380
381 void *active = data->items.pointers[data->active];
382 if (search_but->item_context_menu_fn(C, search_but->arg, active, event)) {
383 handled = true;
384 }
385 }
386 }
387 }
388 }
389 break;
390 case MOUSEMOVE: {
391 bool is_inside = false;
392
393 if (BLI_rcti_isect_pt(&region->winrct, event->xy[0], event->xy[1])) {
394 rcti rect;
395 int a;
396
397 for (a = 0; a < data->items.totitem; a++) {
398 ui_searchbox_butrect(&rect, data, a);
400 &rect, event->xy[0] - region->winrct.xmin, event->xy[1] - region->winrct.ymin))
401 {
402 is_inside = true;
403 if (data->active != a) {
404 data->active = a;
405 ui_searchbox_select(C, region, but, 0);
406 handled = true;
407 break;
408 }
409 }
410 }
411 }
412
413 if (U.flag & USER_TOOLTIPS) {
414 if (is_inside) {
415 if (data->active != -1) {
416 ScrArea *area = CTX_wm_area(C);
417 search_but->item_active = data->items.pointers[data->active];
419 tooltip_timer_started = true;
420 }
421 }
422 }
423
424 break;
425 }
426 }
427
428 if (handled && (tooltip_timer_started == false)) {
429 wmWindow *win = CTX_wm_window(C);
430 WM_tooltip_clear(C, win);
431 }
432
433 return handled;
434}
435
438 uiButSearch *but,
439 const char *str,
440 uiSearchItems *items)
441{
442 /* While the button is in text editing mode (searchbox open), remove tooltips on every update. */
443 if (but->editstr) {
444 wmWindow *win = CTX_wm_window(C);
445 WM_tooltip_clear(C, win);
446 }
447 const bool is_first_search = !but->changed;
448 but->items_update_fn(C, but->arg, str, items, is_first_search);
449}
450
451void ui_searchbox_update(bContext *C, ARegion *region, uiBut *but, const bool reset)
452{
453 uiButSearch *search_but = (uiButSearch *)but;
454 uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
455
457
458 /* reset vars */
459 data->items.totitem = 0;
460 data->items.more = 0;
461 if (!reset) {
462 data->items.offset_i = data->items.offset;
463 }
464 else {
465 data->items.offset_i = data->items.offset = 0;
466 data->active = -1;
467
468 /* On init, find and center active item. */
469 const bool is_first_search = !but->changed;
470 if (is_first_search && search_but->items_update_fn && search_but->item_active) {
471 data->items.active = search_but->item_active;
472 ui_searchbox_update_fn(C, search_but, but->editstr, &data->items);
473 data->items.active = nullptr;
474
475 /* found active item, calculate real offset by centering it */
476 if (data->items.totitem) {
477 /* first case, begin of list */
478 if (data->items.offset_i < data->items.maxitem) {
479 data->active = data->items.offset_i;
480 data->items.offset_i = 0;
481 }
482 else {
483 /* second case, end of list */
484 if (data->items.totitem - data->items.offset_i <= data->items.maxitem) {
485 data->active = data->items.offset_i - data->items.totitem + data->items.maxitem;
486 data->items.offset_i = data->items.totitem - data->items.maxitem;
487 }
488 else {
489 /* center active item */
490 data->items.offset_i -= data->items.maxitem / 2;
491 data->active = data->items.maxitem / 2;
492 }
493 }
494 }
495 data->items.offset = data->items.offset_i;
496 data->items.totitem = 0;
497 }
498 }
499
500 /* callback */
501 if (search_but->items_update_fn) {
502 ui_searchbox_update_fn(C, search_but, but->editstr, &data->items);
503 }
504
505 /* handle case where editstr is equal to one of items */
506 if (reset && data->active == -1) {
507 for (int a = 0; a < data->items.totitem; a++) {
508 const char *name = data->items.names[a] +
509 /* Never include the prefix in the button. */
510 (data->items.name_prefix_offsets ? data->items.name_prefix_offsets[a] :
511 0);
512 const char *name_sep = data->use_shortcut_sep ? strrchr(name, UI_SEP_CHAR) : nullptr;
513 if (STREQLEN(but->editstr, name, name_sep ? (name_sep - name) : data->items.maxstrlen)) {
514 data->active = a;
515 break;
516 }
517 }
518 if (data->items.totitem == 1 && but->editstr[0]) {
519 data->active = 0;
520 }
521 }
522
523 /* validate selected item */
524 ui_searchbox_select(C, region, but, 0);
525
526 ED_region_tag_redraw(region);
527}
528
530{
531 uiButSearch *search_but = (uiButSearch *)but;
532 uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
533 int match = AUTOCOMPLETE_NO_MATCH;
534
536
537 if (str[0]) {
538 data->items.autocpl = UI_autocomplete_begin(str, ui_but_string_get_maxncpy(but));
539
540 ui_searchbox_update_fn(C, search_but, but->editstr, &data->items);
541
542 match = UI_autocomplete_end(data->items.autocpl, str);
543 data->items.autocpl = nullptr;
544 }
545
546 return match;
547}
548
549static void ui_searchbox_region_draw_fn(const bContext *C, ARegion *region)
550{
551 uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
552
553 /* pixel space */
555
556 if (data->noback == false) {
557 ui_draw_widget_menu_back(&data->bbox, true);
558 }
559
560 /* draw text */
561 if (data->items.totitem) {
562 rcti rect;
563
564 if (data->preview) {
565 /* draw items */
566 for (int a = 0; a < data->items.totitem; a++) {
567 const int but_flag = ((a == data->active) ? UI_HOVER : 0) | data->items.but_flags[a];
568
569 /* ensure icon is up-to-date */
570 ui_icon_ensure_deferred(C, data->items.icons[a], data->preview);
571
572 ui_searchbox_butrect(&rect, data, a);
573
574 /* widget itself */
575 ui_draw_preview_item(&data->fstyle,
576 &rect,
577 data->items.names[a],
578 data->items.icons[a],
579 but_flag,
581 }
582
583 /* indicate more */
584 if (data->items.more) {
585 ui_searchbox_butrect(&rect, data, data->items.maxitem - 1);
587 UI_icon_draw(rect.xmax - 18, rect.ymin - 7, ICON_TRIA_DOWN);
589 }
590 if (data->items.offset) {
591 ui_searchbox_butrect(&rect, data, 0);
593 UI_icon_draw(rect.xmin, rect.ymax - 9, ICON_TRIA_UP);
595 }
596 }
597 else {
598 const int search_sep_len = data->sep_string ? strlen(data->sep_string) : 0;
599 /* draw items */
600 for (int a = 0; a < data->items.totitem; a++) {
601 const int but_flag = ((a == data->active) ? UI_HOVER : 0) | data->items.but_flags[a];
602 const char *name = data->items.names[a];
603 int icon = data->items.icons[a];
604 char *name_sep_test = nullptr;
605
607 if (data->use_shortcut_sep) {
608 separator_type = UI_MENU_ITEM_SEPARATOR_SHORTCUT;
609 }
610 /* Only set for displaying additional hint (e.g. library name of a linked data-block). */
611 else if (but_flag & UI_BUT_HAS_SEP_CHAR) {
612 separator_type = UI_MENU_ITEM_SEPARATOR_HINT;
613 }
614
615 ui_searchbox_butrect(&rect, data, a);
616
617 /* widget itself */
618 if ((search_sep_len == 0) ||
619 !(name_sep_test = strstr(data->items.names[a], data->sep_string)))
620 {
621 if (!icon && data->items.has_icon) {
622 /* If there is any icon item, make sure all items line up. */
623 icon = ICON_BLANK1;
624 }
625
626 /* Simple menu item. */
627 ui_draw_menu_item(&data->fstyle, &rect, name, icon, but_flag, separator_type, nullptr);
628 }
629 else {
630 /* Split menu item, faded text before the separator. */
631 char *name_sep = nullptr;
632 do {
633 name_sep = name_sep_test;
634 name_sep_test = strstr(name_sep + search_sep_len, data->sep_string);
635 } while (name_sep_test != nullptr);
636
637 name_sep += search_sep_len;
638 const char name_sep_prev = *name_sep;
639 *name_sep = '\0';
640 int name_width = 0;
641 ui_draw_menu_item(&data->fstyle,
642 &rect,
643 name,
644 0,
645 but_flag | UI_BUT_INACTIVE,
647 &name_width);
648 *name_sep = name_sep_prev;
649 rect.xmin += name_width;
650 rect.xmin += UI_UNIT_X / 4;
651
652 if (icon == ICON_BLANK1) {
653 icon = ICON_NONE;
654 rect.xmin -= UI_ICON_SIZE / 4;
655 }
656
657 /* The previous menu item draws the active selection. */
659 &data->fstyle, &rect, name_sep, icon, but_flag, separator_type, nullptr);
660 }
661 }
662 /* indicate more */
663 if (data->items.more) {
664 ui_searchbox_butrect(&rect, data, data->items.maxitem - 1);
666 UI_icon_draw(BLI_rcti_size_x(&rect) / 2, rect.ymin - 9, ICON_TRIA_DOWN);
668 }
669 if (data->items.offset) {
670 ui_searchbox_butrect(&rect, data, 0);
672 UI_icon_draw(BLI_rcti_size_x(&rect) / 2, rect.ymax - 7, ICON_TRIA_UP);
674 }
675 }
676 }
677 else {
678 rcti rect;
679 ui_searchbox_butrect(&rect, data, 0);
680 ui_draw_menu_item(&data->fstyle,
681 &rect,
682 IFACE_("No results found"),
683 0,
684 0,
686 nullptr);
687 }
688}
689
691{
692 uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
693
694 /* free search data */
695 for (int a = 0; a < data->items.maxitem; a++) {
696 MEM_freeN(data->items.names[a]);
697 }
698 MEM_freeN(data->items.names);
699 MEM_freeN(data->items.pointers);
700 MEM_freeN(data->items.icons);
701 MEM_freeN(data->items.but_flags);
702
703 if (data->items.name_prefix_offsets != nullptr) {
704 MEM_freeN(data->items.name_prefix_offsets);
705 }
706
707 MEM_freeN(data);
708 region->regiondata = nullptr;
709}
710
712{
713 uiSearchboxData *data = static_cast<uiSearchboxData *>(params->region->regiondata);
714 if (data->search_listener) {
715 data->search_listener(params, data->search_arg);
716 }
717}
718
720{
721 uiMenuItemSeparatorType separator_type = data->use_shortcut_sep ?
724 if (separator_type == UI_MENU_ITEM_SEPARATOR_NONE && !data->preview) {
725 for (int a = 0; a < data->items.totitem; a++) {
726 if (data->items.but_flags[a] & UI_BUT_HAS_SEP_CHAR) {
727 separator_type = UI_MENU_ITEM_SEPARATOR_HINT;
728 break;
729 }
730 }
731 }
732 return separator_type;
733}
734
735static void ui_searchbox_region_layout_fn(const bContext *C, ARegion *region)
736{
737 uiSearchboxData *data = (uiSearchboxData *)region->regiondata;
738
739 if (data->size_set) {
740 /* Already set. */
741 return;
742 }
743
744 uiButSearch *but = data->search_but;
745 ARegion *butregion = data->butregion;
746 const int margin = UI_POPUP_MARGIN;
747 wmWindow *win = CTX_wm_window(C);
748
749 /* compute position */
750 if (but->block->flag & UI_BLOCK_SEARCH_MENU) {
751 const int search_but_h = BLI_rctf_size_y(&but->rect) + 10;
752 /* this case is search menu inside other menu */
753 /* we copy region size */
754
755 region->winrct = butregion->winrct;
756
757 /* widget rect, in region coords */
758 data->bbox.xmin = margin;
759 data->bbox.xmax = BLI_rcti_size_x(&region->winrct) - margin;
760 data->bbox.ymin = margin;
761 data->bbox.ymax = BLI_rcti_size_y(&region->winrct) - margin;
762
763 /* check if button is lower half */
764 if (but->rect.ymax < BLI_rctf_cent_y(&but->block->rect)) {
765 data->bbox.ymin += search_but_h;
766 }
767 else {
768 data->bbox.ymax -= search_but_h;
769 }
770 }
771 else {
772 int searchbox_width = int(float(UI_searchbox_size_x()) * 1.4f);
773
774 /* We should make this wider if there is a path or hint on the right. */
776 searchbox_width += 12 * data->fstyle.points * UI_SCALE_FAC;
777 }
778
779 rctf rect_fl;
780 rect_fl.xmin = but->rect.xmin - 5; /* align text with button */
781 rect_fl.xmax = but->rect.xmax + 5; /* symmetrical */
782 rect_fl.ymax = but->rect.ymin;
783 rect_fl.ymin = rect_fl.ymax - UI_searchbox_size_y();
784
785 const int ofsx = (but->block->panel) ? but->block->panel->ofsx : 0;
786 const int ofsy = (but->block->panel) ? but->block->panel->ofsy : 0;
787
788 BLI_rctf_translate(&rect_fl, ofsx, ofsy);
789
790 /* minimal width */
791 if (BLI_rctf_size_x(&rect_fl) < searchbox_width) {
792 rect_fl.xmax = rect_fl.xmin + searchbox_width;
793 }
794
795 /* copy to int, gets projected if possible too */
796 rcti rect_i;
797 BLI_rcti_rctf_copy(&rect_i, &rect_fl);
798
799 if (butregion->v2d.cur.xmin != butregion->v2d.cur.xmax) {
800 UI_view2d_view_to_region_rcti(&butregion->v2d, &rect_fl, &rect_i);
801 }
802
803 BLI_rcti_translate(&rect_i, butregion->winrct.xmin, butregion->winrct.ymin);
804
805 int winx = WM_window_native_pixel_x(win);
806 // winy = WM_window_pixels_y(win); /* UNUSED */
807 // wm_window_get_size(win, &winx, &winy);
808
809 if (rect_i.xmax > winx) {
810 /* super size */
811 if (rect_i.xmax > winx + rect_i.xmin) {
812 rect_i.xmax = winx;
813 rect_i.xmin = 0;
814 }
815 else {
816 rect_i.xmin -= rect_i.xmax - winx;
817 rect_i.xmax = winx;
818 }
819 }
820
821 if (rect_i.ymin < 0) {
822 int newy1 = but->rect.ymax + ofsy;
823
824 if (butregion->v2d.cur.xmin != butregion->v2d.cur.xmax) {
825 newy1 = UI_view2d_view_to_region_y(&butregion->v2d, newy1);
826 }
827
828 newy1 += butregion->winrct.ymin;
829
830 rect_i.ymax = BLI_rcti_size_y(&rect_i) + newy1;
831 rect_i.ymin = newy1;
832 }
833
834 /* widget rect, in region coords */
835 data->bbox.xmin = margin;
836 data->bbox.xmax = BLI_rcti_size_x(&rect_i) + margin;
837 data->bbox.ymin = margin;
838 data->bbox.ymax = BLI_rcti_size_y(&rect_i) + margin;
839
840 /* region bigger for shadow */
841 region->winrct.xmin = rect_i.xmin - margin;
842 region->winrct.xmax = rect_i.xmax + margin;
843 region->winrct.ymin = rect_i.ymin - margin;
844 region->winrct.ymax = rect_i.ymax;
845 }
846
847 region->winx = region->winrct.xmax - region->winrct.xmin + 1;
848 region->winy = region->winrct.ymax - region->winrct.ymin + 1;
849
850 data->size_set = true;
851}
852
854 ARegion *butregion,
855 uiButSearch *but,
856 const bool use_shortcut_sep)
857{
858 const uiStyle *style = UI_style_get();
859 const float aspect = but->block->aspect;
860
861 /* create area region */
863
864 static ARegionType type;
865 memset(&type, 0, sizeof(ARegionType));
866 type.layout = ui_searchbox_region_layout_fn;
867 type.draw = ui_searchbox_region_draw_fn;
868 type.free = ui_searchbox_region_free_fn;
869 type.listener = ui_searchbox_region_listen_fn;
870 type.regionid = RGN_TYPE_TEMPORARY;
871 region->type = &type;
872
873 /* Create search-box data. */
874 uiSearchboxData *data = MEM_cnew<uiSearchboxData>(__func__);
875 data->search_arg = but->arg;
876 data->search_but = but;
877 data->butregion = butregion;
878 data->size_set = false;
879 data->search_listener = but->listen_fn;
880
881 /* Set font, get the bounding-box. */
882 data->fstyle = style->widget; /* copy struct */
883 ui_fontscale(&data->fstyle.points, aspect);
884 UI_fontstyle_set(&data->fstyle);
885
886 region->regiondata = data;
887
888 /* Special case, hard-coded feature, not draw backdrop when called from menus,
889 * assume for design that popup already added it. */
890 if (but->block->flag & UI_BLOCK_SEARCH_MENU) {
891 data->noback = true;
892 }
893
894 if (but->preview_rows > 0 && but->preview_cols > 0) {
895 data->preview = true;
896 data->prv_rows = but->preview_rows;
897 data->prv_cols = but->preview_cols;
898 }
899
900 if (but->optype != nullptr || use_shortcut_sep) {
901 data->use_shortcut_sep = true;
902 }
903 data->sep_string = but->item_sep_string;
904
905 /* Adds sub-window. */
907
908 /* notify change and redraw */
909 ED_region_tag_redraw(region);
910
911 /* prepare search data */
912 if (data->preview) {
913 data->items.maxitem = data->prv_rows * data->prv_cols;
914 }
915 else {
916 data->items.maxitem = SEARCH_ITEMS;
917 }
918 /* In case the button's string is dynamic, make sure there are buffers available. */
919 data->items.maxstrlen = but->hardmax == 0 ? UI_MAX_NAME_STR : but->hardmax;
920 data->items.totitem = 0;
921 data->items.names = (char **)MEM_callocN(data->items.maxitem * sizeof(void *), __func__);
922 data->items.pointers = (void **)MEM_callocN(data->items.maxitem * sizeof(void *), __func__);
923 data->items.icons = (int *)MEM_callocN(data->items.maxitem * sizeof(int), __func__);
924 data->items.but_flags = (int *)MEM_callocN(data->items.maxitem * sizeof(int), __func__);
925 data->items.name_prefix_offsets = nullptr; /* Lazy initialized as needed. */
926 for (int i = 0; i < data->items.maxitem; i++) {
927 data->items.names[i] = (char *)MEM_callocN(data->items.maxstrlen + 1, __func__);
928 }
929
930 return region;
931}
932
934{
935 return ui_searchbox_create_generic_ex(C, butregion, search_but, false);
936}
937
944static void str_tolower_titlecaps_ascii(char *str, const size_t len)
945{
946 bool prev_delim = true;
947
948 for (size_t i = 0; (i < len) && str[i]; i++) {
949 if (str[i] >= 'A' && str[i] <= 'Z') {
950 if (prev_delim == false) {
951 str[i] += 'a' - 'A';
952 }
953 }
954 else if (str[i] == '_') {
955 str[i] = ' ';
956 }
957
958 prev_delim = ELEM(str[i], ' ') || (str[i] >= '0' && str[i] <= '9');
959 }
960}
961
962static void ui_searchbox_region_draw_cb__operator(const bContext * /*C*/, ARegion *region)
963{
964 uiSearchboxData *data = static_cast<uiSearchboxData *>(region->regiondata);
965
966 /* pixel space */
968
969 if (data->noback == false) {
970 ui_draw_widget_menu_back(&data->bbox, true);
971 }
972
973 /* draw text */
974 if (data->items.totitem) {
975 rcti rect;
976
977 /* draw items */
978 for (int a = 0; a < data->items.totitem; a++) {
979 rcti rect_pre, rect_post;
980 ui_searchbox_butrect(&rect, data, a);
981
982 rect_pre = rect;
983 rect_post = rect;
984
985 rect_pre.xmax = rect_post.xmin = rect.xmin + ((rect.xmax - rect.xmin) / 4);
986
987 /* widget itself */
988 /* NOTE: i18n messages extracting tool does the same, please keep it in sync. */
989 {
990 const int but_flag = ((a == data->active) ? UI_HOVER : 0) | data->items.but_flags[a];
991
992 wmOperatorType *ot = static_cast<wmOperatorType *>(data->items.pointers[a]);
993 char text_pre[128];
994 const char *text_pre_p = strstr(ot->idname, "_OT_");
995 if (text_pre_p == nullptr) {
996 text_pre[0] = '\0';
997 }
998 else {
999 int text_pre_len;
1000 text_pre_p += 1;
1001 text_pre_len = BLI_strncpy_rlen(
1002 text_pre, ot->idname, min_ii(sizeof(text_pre), text_pre_p - ot->idname));
1003 text_pre[text_pre_len] = ':';
1004 text_pre[text_pre_len + 1] = '\0';
1005 str_tolower_titlecaps_ascii(text_pre, sizeof(text_pre));
1006 }
1007
1008 rect_pre.xmax += 4; /* sneaky, avoid showing ugly margin */
1009 ui_draw_menu_item(&data->fstyle,
1010 &rect_pre,
1012 data->items.icons[a],
1013 but_flag,
1015 nullptr);
1016 ui_draw_menu_item(&data->fstyle,
1017 &rect_post,
1018 data->items.names[a],
1019 0,
1020 but_flag,
1021 data->use_shortcut_sep ? UI_MENU_ITEM_SEPARATOR_SHORTCUT :
1023 nullptr);
1024 }
1025 }
1026 /* indicate more */
1027 if (data->items.more) {
1028 ui_searchbox_butrect(&rect, data, data->items.maxitem - 1);
1030 UI_icon_draw(BLI_rcti_size_x(&rect) / 2, rect.ymin - 9, ICON_TRIA_DOWN);
1032 }
1033 if (data->items.offset) {
1034 ui_searchbox_butrect(&rect, data, 0);
1036 UI_icon_draw(BLI_rcti_size_x(&rect) / 2, rect.ymax - 7, ICON_TRIA_UP);
1038 }
1039 }
1040 else {
1041 rcti rect;
1042 ui_searchbox_butrect(&rect, data, 0);
1043 ui_draw_menu_item(&data->fstyle,
1044 &rect,
1045 IFACE_("No results found"),
1046 0,
1047 0,
1049 nullptr);
1050 }
1051}
1052
1054{
1055 ARegion *region = ui_searchbox_create_generic_ex(C, butregion, search_but, true);
1056
1057 region->type->draw = ui_searchbox_region_draw_cb__operator;
1058
1059 return region;
1060}
1061
1063{
1064 ui_region_temp_remove(C, CTX_wm_screen(C), region);
1065}
1066
1067static void ui_searchbox_region_draw_cb__menu(const bContext * /*C*/, ARegion * /*region*/)
1068{
1069 /* Currently unused. */
1070}
1071
1073{
1074 ARegion *region = ui_searchbox_create_generic_ex(C, butregion, search_but, true);
1075
1076 if (false) {
1077 region->type->draw = ui_searchbox_region_draw_cb__menu;
1078 }
1079
1080 return region;
1081}
1082
1084{
1085 /* possibly very large lists (such as ID datablocks) only
1086 * only validate string RNA buts (not pointers) */
1087 if (but->rnaprop && RNA_property_type(but->rnaprop) != PROP_STRING) {
1088 return;
1089 }
1090
1091 uiSearchItems *items = MEM_cnew<uiSearchItems>(__func__);
1092
1093 /* setup search struct */
1094 items->maxitem = 10;
1095 items->maxstrlen = 256;
1096 items->names = (char **)MEM_callocN(items->maxitem * sizeof(void *), __func__);
1097 for (int i = 0; i < items->maxitem; i++) {
1098 items->names[i] = (char *)MEM_callocN(but->hardmax + 1, __func__);
1099 }
1100
1101 ui_searchbox_update_fn((bContext *)but->block->evil_C, but, but->drawstr.c_str(), items);
1102
1103 if (!but->results_are_suggestions) {
1104 /* Only red-alert when we are sure of it, this can miss cases when >10 matches. */
1105 if (items->totitem == 0) {
1107 }
1108 else if (items->more == 0) {
1109 if (UI_search_items_find_index(items, but->drawstr.c_str()) == -1) {
1111 }
1112 }
1113 }
1114
1115 for (int i = 0; i < items->maxitem; i++) {
1116 MEM_freeN(items->names[i]);
1117 }
1118 MEM_freeN(items->names);
1119 MEM_freeN(items);
1120}
1121
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:50
#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.c:567
BLI_INLINE int BLI_rcti_size_y(const struct rcti *rct)
Definition BLI_rect.h:193
BLI_INLINE float BLI_rctf_cent_y(const struct rctf *rct)
Definition BLI_rect.h:184
void BLI_rcti_translate(struct rcti *rect, int x, int y)
Definition rct.c:560
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:189
BLI_INLINE float BLI_rctf_size_x(const struct rctf *rct)
Definition BLI_rect.h:197
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:201
char * BLI_strdup(const char *str) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) ATTR_MALLOC
Definition string.c:40
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)
ID and Library types, which are fundamental for SDNA.
@ RGN_TYPE_TEMPORARY
@ USER_TOOLTIPS
#define UI_SCALE_FAC
#define UI_ICON_SIZE
void ED_region_floating_init(ARegion *region)
Definition area.cc:2206
void ED_region_tag_redraw(ARegion *region)
Definition area.cc:634
@ 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.
#define MEM_SAFE_FREE(v)
@ PROP_STRING
Definition RNA_types.hh:68
#define UI_UNIT_Y
@ UI_BLOCK_SEARCH_MENU
#define UI_SEP_CHAR
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_UNIT_X
@ UI_BTYPE_SEARCH_MENU
#define UI_MAX_NAME_STR
AutoComplete * UI_autocomplete_begin(const char *startname, size_t maxncpy)
void UI_autocomplete_update_name(AutoComplete *autocpl, const char *name)
void UI_but_flag_enable(uiBut *but, int flag)
void(*)(const wmRegionListenerParams *params, void *arg) uiButSearchListenFn
@ UI_BUT_REDALERT
@ UI_BUT_DISABLED
@ UI_BUT_INACTIVE
@ UI_BUT_HAS_SEP_CHAR
@ UI_BUT_VALUE_CLEAR
void UI_icon_draw(float x, float y, int icon_id)
float UI_view2d_view_to_region_y(const View2D *v2d, float y)
Definition view2d.cc:1691
void UI_view2d_view_to_region_rcti(const View2D *v2d, const rctf *rect_src, rcti *rect_dst) ATTR_NONNULL()
Definition view2d.cc:1781
unsigned int U
Definition btGjkEpa3.h:78
void reset()
clear internal cached data and reset random seed
int len
draw_view push_constant(Type::INT, "radiance_src") .push_constant(Type capture_info_buf storage_buf(1, Qualifier::READ, "ObjectBounds", "bounds_buf[]") .push_constant(Type draw_view int
#define str(s)
static bool is_inside(int x, int y, int cols, int rows)
Definition filesel.cc:768
uint col
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_widget_menu_back(const rcti *rect, bool use_shadow)
void ui_draw_preview_item(const uiFontStyle *fstyle, rcti *rect, const char *name, int iconid, int but_flag, eFontStyle_Align text_align)
#define UI_POPUP_MARGIN
void ui_draw_menu_item(const uiFontStyle *fstyle, rcti *rect, const char *name, int iconid, int but_flag, uiMenuItemSeparatorType separator_type, int *r_xmax)
@ UI_HOVER
uiMenuItemSeparatorType
@ UI_MENU_ITEM_SEPARATOR_NONE
@ UI_MENU_ITEM_SEPARATOR_HINT
@ UI_MENU_ITEM_SEPARATOR_SHORTCUT
bool UI_search_item_add(uiSearchItems *items, const char *name, void *poin, int iconid, const int but_flag, const uint8_t name_prefix_offset)
int ui_searchbox_find_index(ARegion *region, const char *name)
static uiMenuItemSeparatorType ui_searchbox_item_separator(uiSearchboxData *data)
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)
#define MENU_BORDER
ARegion * ui_searchbox_create_menu(bContext *C, ARegion *butregion, uiButSearch *search_but)
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)
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 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])
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_freeN(void *vmemh)
Definition mallocn.cc:105
void *(* MEM_callocN)(size_t len, const char *str)
Definition mallocn.cc:42
PropertyType RNA_property_type(PropertyRNA *prop)
unsigned char uint8_t
Definition stdint.h:78
void * regiondata
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:726
const char * idname
Definition WM_types.hh:992
int xy[2]
Definition wm_draw.cc:170
@ MOUSEPAN
@ RIGHTMOUSE
@ EVT_DOWNARROWKEY
@ WHEELUPMOUSE
@ WHEELDOWNMOUSE
@ MOUSEMOVE
@ EVT_UPARROWKEY
wmOperatorType * ot
Definition wm_files.cc:4125
void wmOrtho2_region_pixelspace(const ARegion *region)
void WM_tooltip_clear(bContext *C, wmWindow *win)
Definition wm_tooltip.cc:81
void WM_tooltip_timer_init(bContext *C, wmWindow *win, ScrArea *area, ARegion *region, wmTooltipInitFn init)
Definition wm_tooltip.cc:63
int WM_window_native_pixel_x(const wmWindow *win)