Blender V5.0
interface_handlers.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
8
9#include <algorithm>
10#include <cctype>
11#include <cfloat>
12#include <cmath>
13#include <cstdlib>
14#include <cstring>
15#include <variant>
16
17#include "MEM_guardedalloc.h"
18
19#include "DNA_brush_types.h"
21#include "DNA_scene_types.h"
22#include "DNA_screen_types.h"
23
24#include "BLI_array.hh"
25#include "BLI_array_utils.h"
26#include "BLI_linklist.h"
27#include "BLI_listbase.h"
28#include "BLI_math_geom.h"
29#include "BLI_rect.h"
30#include "BLI_sort_utils.h"
31#include "BLI_string.h"
33#include "BLI_string_utf8.h"
34#include "BLI_time.h"
35#include "BLI_utildefines.h"
36
37#include "BKE_animsys.h"
38#include "BKE_blender_undo.hh"
39#include "BKE_brush.hh"
40#include "BKE_colorband.hh"
41#include "BKE_colortools.hh"
42#include "BKE_context.hh"
43#include "BKE_curveprofile.h"
44#include "BKE_movieclip.h"
45#include "BKE_paint.hh"
46#include "BKE_paint_types.hh"
47#include "BKE_report.hh"
48#include "BKE_scene.hh"
49#include "BKE_screen.hh"
50#include "BKE_tracking.h"
51#include "BKE_unit.hh"
52
53#include "BLT_translation.hh"
54
55#include "GHOST_C-api.h"
56
58
59#include "ED_screen.hh"
60#include "ED_undo.hh"
61
62#include "UI_abstract_view.hh"
63#include "UI_interface.hh"
64#include "UI_interface_c.hh"
65#include "UI_string_search.hh"
66
67#include "BLF_api.hh"
68
69#include "interface_intern.hh"
70
71#include "RNA_access.hh"
72#include "RNA_prototypes.hh"
73
74#include "CLG_log.h"
75
76#include "WM_api.hh"
77#include "WM_types.hh"
78#include "wm_event_system.hh"
79
80#ifdef WITH_INPUT_IME
81# include "wm_window.hh"
82#endif
83
85
86static CLG_LogRef LOG = {"ui.handler"};
87
88/* -------------------------------------------------------------------- */
96
98#define USE_CONT_MOUSE_CORRECT
100#define USE_DRAG_TOGGLE
101
103#define USE_DRAG_MULTINUM
104
106#define USE_ALLSELECT
107
112#define USE_KEYNAV_LIMIT
113
115#define USE_DRAG_POPUP
116
118
119/* -------------------------------------------------------------------- */
122
127#define UI_MAX_PASSWORD_STR 128
128
139#define UI_PROP_SCALE_LOG_MIN 0.5e-8f
145#define UI_PROP_SCALE_LOG_SNAP_OFFSET 0.03f
146
158#define UI_DRAG_MAP_SOFT_RANGE_PIXEL_MAX 1000
159
161
162/* -------------------------------------------------------------------- */
165
167struct uiTextEdit;
168
169static int ui_do_but_EXIT(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event);
170static bool ui_but_find_select_in_enum__cmp(const uiBut *but_a, const uiBut *but_b);
171static void ui_textedit_string_set(uiBut *but, uiTextEdit &text_edit, const char *str);
172static void button_tooltip_timer_reset(bContext *C, uiBut *but);
173
175 uiBlock *block,
177 const bool is_click);
179 uiBlock *block,
180 const bool is_click);
183 uiBlockInteraction_Handle *interaction);
186 uiBlockInteraction_Handle *interaction);
187
188#ifdef USE_KEYNAV_LIMIT
189static void ui_mouse_motion_keynav_init(uiKeyNavLock *keynav, const wmEvent *event);
190static bool ui_mouse_motion_keynav_test(uiKeyNavLock *keynav, const wmEvent *event);
191#endif
192
194 ARegion *region,
195 uiBut *but,
196 blender::FunctionRef<void()> fn);
197static int ui_handle_region_semi_modal_buttons(bContext *C, const wmEvent *event, ARegion *region);
198
200
201/* -------------------------------------------------------------------- */
204
205#define BUTTON_FLASH_DELAY 0.020
206#define MENU_SCROLL_INTERVAL 0.1
207#define PIE_MENU_INTERVAL 0.01
208#define BUTTON_AUTO_OPEN_THRESH 0.2
209#define BUTTON_MOUSE_TOWARDS_THRESH 1.0
211#define BUTTON_KEYNAV_PX_LIMIT 8
212
214#define MENU_TOWARDS_MARGIN 20
216#define MENU_TOWARDS_WIGGLE_ROOM 64
217
225
239
246
259
260#ifdef USE_ALLSELECT
261
262/* Unfortunately there's no good way handle more generally:
263 * (propagate single clicks on layer buttons to other objects) */
264# define USE_ALLSELECT_LAYER_HACK
265
268 union {
269 bool val_b;
270 int val_i;
271 float val_f;
272 };
273};
274
277 bool do_free = false;
278 bool is_enabled = false;
279 /* When set, simply copy values (don't apply difference).
280 * Rules are:
281 * - dragging numbers uses delta.
282 * - typing in values will assign to all. */
283 bool is_copy = false;
284};
285
286static bool ui_selectcontext_begin(bContext *C, uiBut *but, uiSelectContextStore *selctx_data);
287static void ui_selectcontext_end(uiBut *but, uiSelectContextStore *selctx_data);
289 uiBut *but,
290 uiSelectContextStore *selctx_data,
291 const double value,
292 const double value_orig);
293
300# define IS_ALLSELECT_EVENT(event) (((event)->modifier & KM_ALT) != 0)
301
303# define UI_BUT_IS_SELECT_CONTEXT UI_BUT_NODE_ACTIVE
304
305#endif /* USE_ALLSELECT */
306
307#ifdef USE_DRAG_MULTINUM
308
312# define DRAG_MULTINUM_THRESHOLD_DRAG_X (UI_UNIT_Y / 4)
313
319# define DRAG_MULTINUM_THRESHOLD_DRAG_Y (UI_UNIT_Y / 4)
320
329# define DRAG_MULTINUM_THRESHOLD_VERTICAL (0.75f)
330
331/* a simple version of uiHandleButtonData when accessing multiple buttons */
333 double origvalue = 0.0f;
334 uiBut *but = nullptr;
335
336# ifdef USE_ALLSELECT
338# endif
339};
340
342 enum {
352
353 bool has_mbuts = false; /* any buttons flagged UI_BUT_DRAG_MULTI */
354 LinkNode *mbuts = nullptr;
356
357 bool is_proportional = false;
358
359 /* In some cases we directly apply the changes to multiple buttons,
360 * so we don't want to do it twice. */
361 bool skip = false;
362
363 /* before activating, we need to check gesture direction accumulate signed cursor movement
364 * here so we can tell if this is a vertical motion or not. */
365 float drag_dir[2] = {0.0f, 0.0f};
366
367 /* values copied direct from event->xy
368 * used to detect buttons between the current and initial mouse position */
369 int drag_start[2] = {0, 0};
370
371 /* store x location once INIT_SETUP is set,
372 * moving outside this sets INIT_ENABLE */
373 int drag_lock_x = 0;
374};
375
376#endif /* USE_DRAG_MULTINUM */
377
383 char *edit_string = nullptr;
384 /* Maximum string size the button accepts, and as such the maximum size for #edit_string
385 * (including terminator). */
387 /* Allow reallocating #edit_string and using #max_string_size to track alloc size (maxlen + 1) */
388 bool is_str_dynamic = false;
389 char *original_string = nullptr;
390
391 /* Button text selection:
392 * extension direction, selextend, inside ui_do_but_TEX */
394
395 /* Text field undo. */
397};
398
400 wmWindowManager *wm = nullptr;
401 wmWindow *window = nullptr;
402 ScrArea *area = nullptr;
403 ARegion *region = nullptr;
404
405 bool interactive = false;
406
407 /* overall state */
409 int retval = 0;
410 /* booleans (could be made into flags) */
411 bool cancel = false;
412 bool escapecancel = false;
413 bool applied = false;
415 /* Button is being applied through an extra icon. */
417 bool changed_cursor = false;
418 wmTimer *flashtimer = nullptr;
419
421
422 double value = 0.0f;
423 double origvalue = 0.0f;
424 double startvalue = 0.0f;
425 float vec[3], origvec[3];
426 ColorBand *coba = nullptr;
427
428 /* True when alt is held and the preference for displaying tooltips should be ignored. */
429 bool tooltip_force = false;
435 bool disable_force = false;
436
442 bool is_semi_modal = false;
443
444 /* auto open */
445 bool used_mouse = false;
447
448 /* auto open (hold) */
450
451 /* number editing / dragging */
452 /* coords are Window/uiBlock relative (depends on the button) */
453 int draglastx = 0;
454 int draglasty = 0;
455 int dragstartx = 0;
456 int dragstarty = 0;
457 bool dragchange = false;
458 bool draglock = false;
459 int dragsel = 0;
460 float dragf = 0.0f;
461 float dragfstart = 0.0f;
462 CBData *dragcbd = nullptr;
463
465 float drag_map_soft_min = 0.0f;
466 float drag_map_soft_max = 0.0f;
467
468#ifdef USE_CONT_MOUSE_CORRECT
469 /* when ungrabbing buttons which are #ui_but_is_cursor_warp(),
470 * we may want to position them.
471 * FLT_MAX signifies do-nothing, use #ui_block_to_window_fl()
472 * to get this into a usable space. */
473 float ungrab_mval[2];
474#endif
475
476 /* Menu open, see: #UI_screen_free_active_but_highlight. */
478
479 /* Search box see: #UI_screen_free_active_but_highlight. */
480 ARegion *searchbox = nullptr;
481#ifdef USE_KEYNAV_LIMIT
483#endif
484
485#ifdef USE_DRAG_MULTINUM
486 /* Multi-buttons will be updated in unison with the active button. */
488#endif
489
490#ifdef USE_ALLSELECT
492#endif
493
495
496 /* post activate */
498 uiBut *postbut = nullptr;
499};
500
545
546static void button_activate_init(bContext *C,
547 ARegion *region,
548 uiBut *but,
551static void button_activate_exit(
552 bContext *C, uiBut *but, uiHandleButtonData *data, const bool mousemove, const bool onfree);
553static int ui_handler_region_menu(bContext *C, const wmEvent *event, void *userdata);
555 ARegion *region,
556 uiBut *but,
559 uiBut *but,
561 const wmEvent *event);
564 const wmEvent *event);
566
567#ifdef USE_DRAG_MULTINUM
570#endif
571
572/* buttons clipboard */
575static bool but_copypaste_curve_alive = false;
577static bool but_copypaste_profile_alive = false;
578
580
581/* -------------------------------------------------------------------- */
584
586{
587 MEM_delete(*data);
588 *data = nullptr;
589}
590
592
593/* -------------------------------------------------------------------- */
596
597bool ui_but_is_editing(const uiBut *but)
598{
599 const uiHandleButtonData *data = but->active;
601}
602
603void ui_pan_to_scroll(const wmEvent *event, int *type, int *val)
604{
605 static int lastdy = 0;
606 const int dy = WM_event_absolute_delta_y(event);
607
608 /* This event should be originally from event->type,
609 * converting wrong event into wheel is bad, see #33803. */
610 BLI_assert(*type == MOUSEPAN);
611
612 /* sign differs, reset */
613 if ((dy > 0 && lastdy < 0) || (dy < 0 && lastdy > 0)) {
614 lastdy = dy;
615 }
616 else {
617 lastdy += dy;
618
619 if (abs(lastdy) > int(UI_UNIT_Y)) {
620 *val = KM_PRESS;
621
622 if (dy > 0) {
623 *type = WHEELUPMOUSE;
624 }
625 else {
626 *type = WHEELDOWNMOUSE;
627 }
628
629 lastdy = 0;
630 }
631 }
632}
633
634static bool ui_but_find_select_in_enum__cmp(const uiBut *but_a, const uiBut *but_b)
635{
636 return ((but_a->type == but_b->type) && (but_a->alignnr == but_b->alignnr) &&
637 (but_a->poin == but_b->poin) && (but_a->rnapoin.type == but_b->rnapoin.type) &&
638 (but_a->rnaprop == but_b->rnaprop));
639}
640
642{
643 uiBlock *block = but->block;
644 int i = block->but_index(but);
645 uiBut *but_found = nullptr;
646 BLI_assert(ELEM(direction, -1, 1));
647
648 while (i > 0 && ui_but_find_select_in_enum__cmp(block->buttons[i - 1].get(), but)) {
649 i--;
650 }
651
652 while (i < block->buttons.size() &&
653 ui_but_find_select_in_enum__cmp(block->buttons[i].get(), but))
654 {
655 if (block->buttons[i]->flag & UI_SELECT) {
656 but_found = block->buttons[i].get();
657 if (direction == 1) {
658 break;
659 }
660 }
661 i++;
662 }
663
664 return but_found;
665}
666
667static float ui_mouse_scale_warp_factor(const bool shift)
668{
669 return shift ? 0.05f : 1.0f;
670}
671
673 const float mx,
674 const float my,
675 float *r_mx,
676 float *r_my,
677 const bool shift)
678{
679 const float fac = ui_mouse_scale_warp_factor(shift);
680
681 /* slow down the mouse, this is fairly picky */
682 *r_mx = (data->dragstartx * (1.0f - fac) + mx * fac);
683 *r_my = (data->dragstarty * (1.0f - fac) + my * fac);
684}
685
687
688/* -------------------------------------------------------------------- */
691
692#ifdef USE_DRAG_MULTINUM
693static bool ui_multibut_drag_wait(const uiHandleButtonMulti &multi_data)
694{
695 const bool initializing = ELEM(
697 const bool vertical_dragging = abs(multi_data.drag_dir[1]) > abs(multi_data.drag_dir[0]);
698
699 /* Continue waiting if we are dragging vertically but have not yet detected gesture. */
700 return (initializing && vertical_dragging);
701}
702#endif /* USE_DRAG_MULTINUM */
703
708 int mx,
709 blender::FunctionRef<int()> drag_threshold_fn)
710{
711 if (data->draglock) {
712 const int threshold = drag_threshold_fn();
713 if (abs(mx - data->dragstartx) < threshold) {
714 return false;
715 }
716#ifdef USE_DRAG_MULTINUM
717 /* Continue to wait for multibut drag initialization if dragging vertically. */
718 if (ui_multibut_drag_wait(data->multi_data)) {
719 return false;
720 }
721#endif
722 data->draglock = false;
723 data->dragstartx = mx; /* ignore mouse movement within drag-lock */
724 }
725
726 return true;
727}
728
730{
731 /* Not very elegant, but ensures preference changes force re-save. */
732
733 if (!prop) {
734 return false;
735 }
737 return false;
738 }
739
740 StructRNA *base = RNA_struct_base(ptr->type);
741 if (base == nullptr) {
742 base = ptr->type;
743 }
744
745 bool is_userdef = false;
746 if (ELEM(base,
747 &RNA_AddonPreferences,
748 &RNA_KeyConfigPreferences,
749 &RNA_KeyMapItem,
750 &RNA_UserAssetLibrary))
751 {
752 is_userdef = true;
753 }
754 else if (ptr->owner_id) {
755 switch (GS(ptr->owner_id->name)) {
756 case ID_WM: {
757 for (const AncestorPointerRNA &ancestor : ptr->ancestors) {
758 if (RNA_struct_is_a(ancestor.type, &RNA_KeyConfigPreferences)) {
759 is_userdef = true;
760 break;
761 }
762 }
763 break;
764 }
765 default: {
766 break;
767 }
768 }
769 }
770 else if (ptr->owner_id == nullptr) {
771 for (const AncestorPointerRNA &ancestor : ptr->ancestors) {
772 if (RNA_struct_is_a(ancestor.type, &RNA_AddonPreferences)) {
773 is_userdef = true;
774 break;
775 }
776 }
777 }
778
779 return is_userdef;
780}
781
782bool UI_but_is_userdef(const uiBut *but)
783{
784 /* This is read-only, RNA API isn't using const when it could. */
785 return ui_rna_is_userdef((PointerRNA *)&but->rnapoin, but->rnaprop);
786}
787
789{
790 if (ui_rna_is_userdef(ptr, prop)) {
791 U.runtime.is_dirty = true;
793 }
794}
795
800
805
807
808/* -------------------------------------------------------------------- */
811
817
818static enum eSnapType ui_event_to_snap(const wmEvent *event)
819{
820 return (event->modifier & KM_CTRL) ? (event->modifier & KM_SHIFT) ? SNAP_ON_SMALL : SNAP_ON :
821 SNAP_OFF;
822}
823
824static bool ui_event_is_snap(const wmEvent *event)
825{
826 return (ELEM(event->type, EVT_LEFTCTRLKEY, EVT_RIGHTCTRLKEY) ||
828}
829
830static void ui_color_snap_hue(const enum eSnapType snap, float *r_hue)
831{
832 const float snap_increment = (snap == SNAP_ON_SMALL) ? 24 : 12;
834 *r_hue = roundf((*r_hue) * snap_increment) / snap_increment;
835}
836
838
839/* -------------------------------------------------------------------- */
842
843static ListBase UIAfterFuncs = {nullptr, nullptr};
844
846{
847 uiAfterFunc *after = MEM_new<uiAfterFunc>(__func__);
848 /* Safety asserts to check if members were 0 initialized properly. */
849 BLI_assert(after->next == nullptr && after->prev == nullptr);
850 BLI_assert(after->undostr[0] == '\0');
851
852 BLI_addtail(&UIAfterFuncs, after);
853
854 return after;
855}
856
868 PointerRNA **properties,
870 const uiBut *context_but)
871{
872 uiAfterFunc *after = ui_afterfunc_new();
873
874 after->optype = ot;
875 after->opcontext = opcontext;
876 if (properties) {
877 after->opptr = *properties;
878 *properties = nullptr;
879 }
880
881 if (context_but && context_but->context) {
882 after->context = *context_but->context;
883 }
884
885 if (context_but) {
886 after->drawstr = ui_but_drawstr_without_sep_char(context_but);
887 }
888}
889
894
896{
897 if (op && op->type->check) {
898 op->type->check(C, op);
899 }
900}
901
905static bool ui_afterfunc_check(const uiBlock *block, const uiBut *but)
906{
907 return (but->func || but->apply_func || but->funcN || but->rename_func ||
908 but->rename_full_func || but->optype || but->rnaprop || block->handle_func ||
909 (block->handle && block->handle->popup_op));
910}
911
918{
919 uiBlock *block = but->block;
920 if (!ui_afterfunc_check(block, but)) {
921 return;
922 }
923
924 uiAfterFunc *after = ui_afterfunc_new();
925
926 if (but->func && ELEM(but, but->func_arg1, but->func_arg2)) {
927 /* exception, this will crash due to removed button otherwise */
928 but->func(C, but->func_arg1, but->func_arg2);
929 }
930 else {
931 after->func = but->func;
932 }
933
934 after->func_arg1 = but->func_arg1;
935 after->func_arg2 = but->func_arg2;
936
937 after->apply_func = but->apply_func;
938
939 after->funcN = but->funcN;
940 after->func_argN = (but->func_argN) ? but->func_argN_copy_fn(but->func_argN) : nullptr;
942 /* but->func_argN_copy_fn is not needed for #uiAfterFunc. */
943
944 after->rename_func = but->rename_func;
945 after->rename_arg1 = but->rename_arg1;
946 after->rename_orig = but->rename_orig; /* needs free! */
947
949 after->rename_full_new = std::move(but->rename_full_new);
950 but->rename_full_new = "";
951
952 after->handle_func = block->handle_func;
953 after->handle_func_arg = block->handle_func_arg;
954 after->retval = but->retval;
955
956 if (block->handle) {
957 after->popup_op = block->handle->popup_op;
958 }
959
960 if (!but->operator_never_call) {
961 after->optype = but->optype;
962 after->opcontext = but->opcontext;
963 after->opptr = but->opptr;
964
965 but->optype = nullptr;
967 but->opptr = nullptr;
968 }
969
970 after->rnapoin = but->rnapoin;
971 after->rnaprop = but->rnaprop;
972
973 if (but->type == ButType::SearchMenu) {
974 uiButSearch *search_but = (uiButSearch *)but;
975 after->search_arg_free_fn = search_but->arg_free_fn;
976 after->search_arg = search_but->arg;
977 search_but->arg_free_fn = nullptr;
978 search_but->arg = nullptr;
979 }
980
981 if (but->active != nullptr) {
983 if (data->custom_interaction_handle != nullptr) {
985 after->custom_interaction_handle = data->custom_interaction_handle;
986
987 /* Ensure this callback runs once and last. */
988 uiAfterFunc *after_prev = after->prev;
989 if (after_prev && (after_prev->custom_interaction_handle == data->custom_interaction_handle))
990 {
991 after_prev->custom_interaction_handle = nullptr;
992 memset(&after_prev->custom_interaction_callbacks,
993 0x0,
994 sizeof(after_prev->custom_interaction_callbacks));
995 }
996 else {
998 }
999 }
1000 }
1001
1002 if (but->context) {
1003 after->context = *but->context;
1004 }
1005
1007}
1008
1009/* typically call ui_apply_but_undo(), ui_apply_but_autokey() */
1010static void ui_apply_but_undo(uiBut *but)
1011{
1012 if (!(but->flag & UI_BUT_UNDO)) {
1013 return;
1014 }
1015
1016 /* Skip undo push for buttons in redo panel, see: #134505. */
1017 const ARegion *region = CTX_wm_region(static_cast<bContext *>(but->block->evil_C));
1018 if (region->regiontype == RGN_TYPE_HUD) {
1019 return;
1020 }
1021
1022 std::optional<StringRef> str;
1023 size_t str_len_clip = SIZE_MAX - 1;
1024 bool skip_undo = false;
1025
1026 /* define which string to use for undo */
1027 if (but->type == ButType::Menu) {
1028 if (!but->drawstr.empty()) {
1029 str = but->drawstr;
1030 }
1031 str_len_clip = ui_but_drawstr_len_without_sep_char(but);
1032 }
1033 else if (!but->drawstr.empty()) {
1034 str = but->drawstr;
1035 str_len_clip = ui_but_drawstr_len_without_sep_char(but);
1036 }
1037 else {
1038 str = but->tip;
1039 str_len_clip = ui_but_tip_len_only_first_line(but);
1040 }
1041
1042 /* fallback, else we don't get an undo! */
1043 if (!str || str->is_empty() || str_len_clip == 0) {
1044 str = "Unknown Action";
1045 str_len_clip = str->size();
1046 }
1047
1048 /* Optionally override undo when undo system doesn't support storing properties. */
1049 if (but->rnapoin.owner_id) {
1050 /* Exception for renaming ID data, we always need undo pushes in this case,
1051 * because undo systems track data by their ID, see: #67002. */
1052 /* Exception for active shape-key, since changing this in edit-mode updates
1053 * the shape key from object mode data. */
1054 if (ELEM(but->rnaprop, &rna_ID_name, &rna_Object_active_shape_key_index)) {
1055 /* pass */
1056 }
1057 else {
1058 ID *id = but->rnapoin.owner_id;
1060 static_cast<bContext *>(but->block->evil_C), id, but->rnapoin))
1061 {
1062 skip_undo = true;
1063 }
1064 }
1065 }
1066
1067 if (skip_undo == false) {
1068 /* XXX: disable all undo pushes from UI changes from sculpt mode as they cause memfile undo
1069 * steps to be written which cause lag: #71434. */
1070 if (BKE_paintmode_get_active_from_context(static_cast<bContext *>(but->block->evil_C)) ==
1072 {
1073 skip_undo = true;
1074 }
1075 }
1076
1077 if (skip_undo) {
1078 str = "";
1079 }
1080
1081 /* Delayed, after all other functions run, popups are closed, etc. */
1082 uiAfterFunc *after = ui_afterfunc_new();
1083 str->copy_utf8_truncated(after->undostr, min_zz(str_len_clip + 1, sizeof(after->undostr)));
1084}
1085
1087{
1088 Scene *scene = CTX_data_scene(C);
1089
1090 /* try autokey */
1091 ui_but_anim_autokey(C, but, scene, BKE_scene_frame_get(scene));
1092
1093 if (!but->rnaprop) {
1094 return;
1095 }
1096
1098 return;
1099 }
1100
1101 /* make a little report about what we've done! */
1102 std::optional<const std::string> str = WM_prop_pystring_assign(
1103 C, &but->rnapoin, but->rnaprop, but->rnaindex);
1104 if (!str.has_value()) {
1105 return;
1106 }
1107 BKE_report(CTX_wm_reports(C), RPT_PROPERTY, str.value().c_str());
1109}
1110
1112{
1113 /* Copy to avoid recursive calls. */
1114 ListBase funcs = UIAfterFuncs;
1116
1117 LISTBASE_FOREACH_MUTABLE (uiAfterFunc *, afterf, &funcs) {
1118 uiAfterFunc after = *afterf; /* Copy to avoid memory leak on exit(). */
1119 BLI_remlink(&funcs, afterf);
1120 MEM_delete(afterf);
1121
1122 if (after.context) {
1123 CTX_store_set(C, &after.context.value());
1124 }
1125
1126 if (after.popup_op) {
1127 popup_check(C, after.popup_op);
1128 }
1129
1130 PointerRNA opptr;
1131 if (after.opptr) {
1132 /* free in advance to avoid leak on exit */
1133 opptr = *after.opptr;
1134 MEM_delete(after.opptr);
1135 }
1136
1137 if (after.optype) {
1139 after.optype,
1140 after.opcontext,
1141 (after.opptr) ? &opptr : nullptr,
1142 nullptr,
1143 after.drawstr);
1144 }
1145
1146 if (after.opptr) {
1148 }
1149
1150 if (after.rnapoin.data) {
1151 RNA_property_update(C, &after.rnapoin, after.rnaprop);
1152 }
1153
1154 if (after.context) {
1155 CTX_store_set(C, nullptr);
1156 }
1157
1158 if (after.rename_full_func) {
1159 BLI_assert(!after.rename_func);
1160 after.rename_full_func(after.rename_full_new);
1161 }
1162
1163 if (after.func) {
1164 after.func(C, after.func_arg1, after.func_arg2);
1165 }
1166 if (after.apply_func) {
1167 after.apply_func(*C);
1168 }
1169 if (after.funcN) {
1170 after.funcN(C, after.func_argN, after.func_arg2);
1171 }
1172 if (after.func_argN) {
1173 after.func_argN_free_fn(after.func_argN);
1174 }
1175
1176 if (after.handle_func) {
1177 after.handle_func(C, after.handle_func_arg, after.retval);
1178 }
1179
1180 if (after.rename_func) {
1181 after.rename_func(C, after.rename_arg1, static_cast<char *>(after.rename_orig));
1182 }
1183 if (after.rename_orig) {
1184 MEM_freeN(after.rename_orig);
1185 }
1186
1187 if (after.search_arg_free_fn) {
1188 after.search_arg_free_fn(after.search_arg);
1189 }
1190
1191 if (after.custom_interaction_handle != nullptr) {
1194 if (after.custom_interaction_handle->user_count == 0) {
1199 }
1200 after.custom_interaction_handle = nullptr;
1201 }
1202
1204
1205 if (after.undostr[0]) {
1206 /* Remove "Adjust Last Operation" HUD. Using it would revert this undo push which isn't
1207 * obvious, see #78171. */
1209 ED_undo_push(C, after.undostr);
1210 }
1211 }
1212}
1213
1215{
1216 ui_apply_but_func(C, but);
1217
1218 data->retval = but->retval;
1219 data->applied = true;
1220}
1221
1223{
1224 ui_but_value_set(but, but->hardmin);
1225 ui_apply_but_func(C, but);
1226
1227 data->retval = but->retval;
1228 data->applied = true;
1229}
1230
1232{
1233 if (but->type == ButType::Menu) {
1234 ui_but_value_set(but, data->value);
1235 }
1236
1238 ui_apply_but_func(C, but);
1239 data->retval = but->retval;
1240 data->applied = true;
1241}
1242
1244{
1245 const double value = ui_but_value_get(but);
1246 int value_toggle;
1247 if (but->bit) {
1248 value_toggle = UI_BITBUT_VALUE_TOGGLED(int(value), but->bitnr);
1249 }
1250 else {
1251 value_toggle = (value == 0.0);
1252 }
1253
1254 ui_but_value_set(but, double(value_toggle));
1257 }
1258
1259 ui_apply_but_func(C, but);
1260
1261 data->retval = but->retval;
1262 data->applied = true;
1263}
1264
1266{
1267 ui_but_value_set(but, but->hardmax);
1268
1269 ui_apply_but_func(C, but);
1270
1271 /* states of other row buttons */
1272 for (const std::unique_ptr<uiBut> &bt : block->buttons) {
1273 if (bt.get() != but && bt->poin == but->poin && ELEM(bt->type, ButType::Row, ButType::ListRow))
1274 {
1275 ui_but_update_edited(bt.get());
1276 }
1277 }
1278
1279 data->retval = but->retval;
1280 data->applied = true;
1281}
1282
1284 uiBlock *block,
1285 uiBut *but,
1287{
1288 if (data->apply_through_extra_icon) {
1289 /* Don't apply this, it would cause unintended tree-row toggling when clicking on extra icons.
1290 */
1291 return;
1292 }
1293 ui_apply_but_ROW(C, block, but, data);
1294}
1295
1297{
1298 if (!data->text_edit.edit_string) {
1299 return;
1300 }
1301
1302 ui_but_string_set(C, but, data->text_edit.edit_string);
1304
1305 /* only if there are afterfuncs, otherwise 'renam_orig' isn't freed */
1306 if (ui_afterfunc_check(but->block, but)) {
1307 /* give butfunc a copy of the original text too.
1308 * feature used for bone renaming, channels, etc.
1309 * afterfunc frees rename_orig */
1310 if (data->text_edit.original_string && (but->flag & UI_BUT_TEXTEDIT_UPDATE)) {
1311 /* In this case, we need to keep `original_string` available,
1312 * to restore real org string in case we cancel after having typed something already. */
1313 but->rename_orig = BLI_strdup(data->text_edit.original_string);
1314 }
1315 else {
1316 but->rename_orig = data->text_edit.original_string;
1317 data->text_edit.original_string = nullptr;
1318 }
1319 }
1320
1321 void *orig_arg2 = but->func_arg2;
1322
1323 /* If arg2 isn't in use already, pass the active search item through it. */
1324 if ((but->func_arg2 == nullptr) && (but->type == ButType::SearchMenu)) {
1325 uiButSearch *search_but = (uiButSearch *)but;
1326 but->func_arg2 = search_but->item_active;
1327 if ((U.flag & USER_FLAG_RECENT_SEARCHES_DISABLE) == 0) {
1329 }
1330 }
1331
1332 ui_apply_but_func(C, but);
1333
1334 but->func_arg2 = orig_arg2;
1335
1336 data->retval = but->retval;
1337 data->applied = true;
1338}
1339
1341{
1342 if (data->text_edit.edit_string) {
1343 ui_but_string_set(C, but, data->text_edit.edit_string);
1345 }
1346 else {
1347 ui_but_value_set(but, but->hardmax);
1348 ui_apply_but_func(C, but);
1349 }
1350
1351 data->retval = but->retval;
1352 data->applied = true;
1353}
1354
1356{
1357 if (data->text_edit.edit_string) {
1358 /* This is intended to avoid unnecessary updates when the value stays the same, however there
1359 * are issues with the current implementation. It does not work with multi-button editing
1360 * (#89996) or operator popups where a number button requires an update even if the value is
1361 * unchanged (#89996).
1362 *
1363 * Trying to detect changes at this level is not reliable. Instead it could be done at the
1364 * level of RNA update/set, skipping RNA update if RNA set did not change anything, instead
1365 * of skipping all button updates. */
1366#if 0
1367 double value;
1368 /* Check if the string value is a number and cancel if it's equal to the startvalue. */
1369 if (ui_but_string_eval_number(C, but, data->str, &value) && (value == data->startvalue)) {
1370 data->cancel = true;
1371 return;
1372 }
1373#endif
1374
1375 if (ui_but_string_set(C, but, data->text_edit.edit_string)) {
1376 data->value = ui_but_value_get(but);
1377 }
1378 else {
1379 data->cancel = true;
1380 return;
1381 }
1382 }
1383 else {
1384 ui_but_value_set(but, data->value);
1385 }
1386
1388 ui_apply_but_func(C, but);
1389
1390 data->retval = but->retval;
1391 data->applied = true;
1392}
1393
1395{
1396 ui_but_v3_set(but, data->vec);
1398 ui_apply_but_func(C, but);
1399
1400 data->retval = but->retval;
1401 data->applied = true;
1402}
1403
1405{
1406 ui_apply_but_func(C, but);
1407 data->retval = but->retval;
1408 data->applied = true;
1409}
1410
1412{
1413 ui_apply_but_func(C, but);
1414 data->retval = but->retval;
1415 data->applied = true;
1416}
1417
1419{
1420 ui_apply_but_func(C, but);
1421 data->retval = but->retval;
1422 data->applied = true;
1423}
1424
1426
1427/* -------------------------------------------------------------------- */
1430
1431#ifdef USE_DRAG_MULTINUM
1432
1433/* Small multi-but API. */
1435{
1437 BLI_assert(data->multi_data.has_mbuts);
1438
1439 uiButMultiState *mbut_state = MEM_new<uiButMultiState>(__func__);
1440 mbut_state->but = but;
1441 mbut_state->origvalue = ui_but_value_get(but);
1442# ifdef USE_ALLSELECT
1443 mbut_state->select_others.is_copy = data->select_others.is_copy;
1444# endif
1445
1446 BLI_linklist_prepend(&data->multi_data.mbuts, mbut_state);
1447
1448 UI_butstore_register(data->multi_data.bs_mbuts, &mbut_state->but);
1449}
1450
1452{
1453 for (LinkNode *l = data->multi_data.mbuts; l; l = l->next) {
1454 uiButMultiState *mbut_state = static_cast<uiButMultiState *>(l->link);
1455
1456 if (mbut_state->but == but) {
1457 return mbut_state;
1458 }
1459 }
1460
1461 return nullptr;
1462}
1463
1465{
1466 for (const std::unique_ptr<uiBut> &but : block->buttons) {
1467 if (but->flag & UI_BUT_DRAG_MULTI) {
1468 uiButMultiState *mbut_state = ui_multibut_lookup(data, but.get());
1469 if (mbut_state) {
1470 ui_but_value_set(but.get(), mbut_state->origvalue);
1471
1472# ifdef USE_ALLSELECT
1473 if (!mbut_state->select_others.elems.is_empty()) {
1475 but.get(),
1476 &mbut_state->select_others,
1477 mbut_state->origvalue,
1478 mbut_state->origvalue);
1479 }
1480# else
1481 UNUSED_VARS(C);
1482# endif
1483 }
1484 }
1485 }
1486}
1487
1489{
1490# ifdef USE_ALLSELECT
1491 if (data->multi_data.mbuts) {
1492 LinkNode *list = data->multi_data.mbuts;
1493 while (list) {
1494 LinkNode *next = list->next;
1495 uiButMultiState *mbut_state = static_cast<uiButMultiState *>(list->link);
1496 MEM_delete(mbut_state);
1497 MEM_freeN(list);
1498 list = next;
1499 }
1500 }
1501# else
1502 BLI_linklist_freeN(data->multi_data.mbuts);
1503# endif
1504
1505 data->multi_data.mbuts = nullptr;
1506
1507 if (data->multi_data.bs_mbuts) {
1508 UI_butstore_free(block, data->multi_data.bs_mbuts);
1509 data->multi_data.bs_mbuts = nullptr;
1510 }
1511}
1512
1513static bool ui_multibut_states_tag(uiBut *but_active,
1515 const wmEvent *event)
1516{
1517 float seg[2][2];
1518 bool changed = false;
1519
1520 seg[0][0] = data->multi_data.drag_start[0];
1521 seg[0][1] = data->multi_data.drag_start[1];
1522
1523 seg[1][0] = event->xy[0];
1524 seg[1][1] = event->xy[1];
1525
1526 BLI_assert(data->multi_data.init == uiHandleButtonMulti::INIT_SETUP);
1527
1528 ui_window_to_block_fl(data->region, but_active->block, &seg[0][0], &seg[0][1]);
1529 ui_window_to_block_fl(data->region, but_active->block, &seg[1][0], &seg[1][1]);
1530
1531 data->multi_data.has_mbuts = false;
1532
1533 /* follow ui_but_find_mouse_over_ex logic */
1534 for (const std::unique_ptr<uiBut> &but : but_active->block->buttons) {
1535 bool drag_prev = false;
1536 bool drag_curr = false;
1537
1538 /* re-set each time */
1539 if (but->flag & UI_BUT_DRAG_MULTI) {
1540 but->flag &= ~UI_BUT_DRAG_MULTI;
1541 drag_prev = true;
1542 }
1543
1544 if (ui_but_is_interactive(but.get(), false)) {
1545
1546 /* drag checks */
1547 if (but_active != but.get()) {
1548 if (ui_but_is_compatible(but_active, but.get())) {
1549
1550 BLI_assert(but->active == nullptr);
1551
1552 /* finally check for overlap */
1553 if (BLI_rctf_isect_segment(&but->rect, seg[0], seg[1])) {
1554
1555 but->flag |= UI_BUT_DRAG_MULTI;
1556 data->multi_data.has_mbuts = true;
1557 drag_curr = true;
1558 }
1559 }
1560 }
1561 }
1562
1563 changed |= (drag_prev != drag_curr);
1564 }
1565
1566 return changed;
1567}
1568
1570{
1571 BLI_assert(data->multi_data.init == uiHandleButtonMulti::INIT_SETUP);
1572 BLI_assert(data->multi_data.has_mbuts);
1573
1574 data->multi_data.bs_mbuts = UI_butstore_create(but_active->block);
1575
1576 for (const std::unique_ptr<uiBut> &but : but_active->block->buttons) {
1577 if (but->flag & UI_BUT_DRAG_MULTI) {
1578 ui_multibut_add(data, but.get());
1579 }
1580 }
1581
1582 /* Edit buttons proportionally to each other.
1583 * NOTE: if we mix buttons which are proportional and others which are not,
1584 * this may work a bit strangely. */
1585 if ((but_active->rnaprop && (RNA_property_flag(but_active->rnaprop) & PROP_PROPORTIONAL)) ||
1587 {
1588 if (data->origvalue != 0.0) {
1589 data->multi_data.is_proportional = true;
1590 }
1591 }
1592}
1593
1595{
1596 ARegion *region = data->region;
1597 const double value_delta = data->value - data->origvalue;
1598 const double value_scale = data->multi_data.is_proportional ? (data->value / data->origvalue) :
1599 0.0;
1600
1602 BLI_assert(data->multi_data.skip == false);
1603
1604 for (const std::unique_ptr<uiBut> &but : block->buttons) {
1605 if (!(but->flag & UI_BUT_DRAG_MULTI)) {
1606 continue;
1607 }
1608
1609 uiButMultiState *mbut_state = ui_multibut_lookup(data, but.get());
1610
1611 if (mbut_state == nullptr) {
1612 /* Highly unlikely. */
1613 CLOG_WARN(&LOG, "%s: Can't find button", __func__);
1614 /* While this avoids crashing, multi-button dragging will fail,
1615 * which is still a bug from the user perspective. See #83651. */
1616 continue;
1617 }
1618
1619 void *active_back;
1620 ui_but_execute_begin(C, region, but.get(), &active_back);
1621
1622# ifdef USE_ALLSELECT
1623 if (data->select_others.is_enabled) {
1624 /* init once! */
1625 if (mbut_state->select_others.elems.is_empty()) {
1626 ui_selectcontext_begin(C, but.get(), &mbut_state->select_others);
1627 }
1628 if (mbut_state->select_others.elems.is_empty()) {
1629 mbut_state->select_others.elems.clear();
1630 }
1631 }
1632
1633 /* Needed so we apply the right deltas. */
1634 but->active->origvalue = mbut_state->origvalue;
1635 but->active->select_others = mbut_state->select_others;
1636 but->active->select_others.do_free = false;
1637# endif
1638
1639 BLI_assert(active_back == nullptr);
1640 /* No need to check 'data->state' here. */
1641 if (data->text_edit.edit_string) {
1642 /* Entering text (set all). */
1643 but->active->value = data->value;
1644 ui_but_string_set(C, but.get(), data->text_edit.edit_string);
1645 }
1646 else {
1647 /* Dragging (use delta). */
1648 if (data->multi_data.is_proportional) {
1649 but->active->value = mbut_state->origvalue * value_scale;
1650 }
1651 else {
1652 but->active->value = mbut_state->origvalue + value_delta;
1653 }
1654
1655 /* Clamp based on soft limits, see #40154. */
1656 CLAMP(but->active->value, double(but->softmin), double(but->softmax));
1657 }
1658
1659 ui_but_execute_end(C, region, but.get(), active_back);
1660 }
1661}
1662
1663#endif /* USE_DRAG_MULTINUM */
1664
1666
1667/* -------------------------------------------------------------------- */
1670
1671#ifdef USE_DRAG_TOGGLE
1672
1673/* Helpers that wrap boolean functions, to support different kinds of buttons. */
1674
1676{
1677 if (but->flag & UI_BUT_DISABLED) {
1678 return false;
1679 }
1680 if (ui_but_is_bool(but)) {
1681 return true;
1682 }
1683 if (UI_but_is_decorator(but)) {
1684 const uiButDecorator *but_decorate = static_cast<const uiButDecorator *>(but);
1685 return but_decorate->toggle_keyframe_on_click;
1686 }
1687 return false;
1688}
1689
1690/* Button pushed state to compare if other buttons match. Can be more
1691 * then just true or false for toggle buttons with more than 2 states. */
1693{
1694 if (but->rnapoin.data == nullptr && but->poin == nullptr && but->icon) {
1695 /* Assume icon identifies a unique state, for buttons that
1696 * work through functions callbacks and don't have an boolean
1697 * value that indicates the state. */
1698 return but->icon + but->iconadd;
1699 }
1700 if (ui_but_is_bool(but)) {
1701 return ui_but_is_pushed(but);
1702 }
1703 return 0;
1704}
1705
1707 /* init */
1710
1712 bool xy_lock[2];
1713
1714 int xy_init[2];
1715 int xy_last[2];
1716};
1717
1719 bContext *C, ARegion *region, const int pushed_state, const int xy_src[2], const int xy_dst[2])
1720{
1721 /* popups such as layers won't re-evaluate on redraw */
1722 const bool do_check = (region->regiontype == RGN_TYPE_TEMPORARY);
1723 bool changed = false;
1724
1725 LISTBASE_FOREACH (uiBlock *, block, &region->runtime->uiblocks) {
1726 float xy_a_block[2] = {float(xy_src[0]), float(xy_src[1])};
1727 float xy_b_block[2] = {float(xy_dst[0]), float(xy_dst[1])};
1728
1729 ui_window_to_block_fl(region, block, &xy_a_block[0], &xy_a_block[1]);
1730 ui_window_to_block_fl(region, block, &xy_b_block[0], &xy_b_block[1]);
1731
1732 for (const std::unique_ptr<uiBut> &but : block->buttons) {
1733 /* NOTE: ctrl is always true here because (at least for now)
1734 * we always want to consider text control in this case, even when not embossed. */
1735
1736 if (!ui_but_is_interactive(but.get(), true)) {
1737 continue;
1738 }
1739 if (!BLI_rctf_isect_segment(&but->rect, xy_a_block, xy_b_block)) {
1740 continue;
1741 }
1742 if (!ui_drag_toggle_but_is_supported(but.get())) {
1743 continue;
1744 }
1745 /* is it pressed? */
1746 const int pushed_state_but = ui_drag_toggle_but_pushed_state(but.get());
1747 if (pushed_state_but == pushed_state) {
1748 continue;
1749 }
1750
1751 /* execute the button */
1752 UI_but_execute(C, region, but.get());
1753 if (do_check) {
1754 ui_but_update_edited(but.get());
1755 }
1756 if (U.runtime.is_dirty == false) {
1758 }
1759 changed = true;
1760 }
1761 }
1762
1763 if (changed) {
1764 /* apply now, not on release (or if handlers are canceled for whatever reason) */
1766 }
1767
1768 return changed;
1769}
1770
1771static void ui_drag_toggle_set(bContext *C, uiDragToggleHandle *drag_info, const int xy_input[2])
1772{
1773 ARegion *region = CTX_wm_region(C);
1774 bool do_draw = false;
1775
1782 if (drag_info->is_xy_lock_init == false) {
1783 /* first store the buttons original coords */
1784 uiBut *but = ui_but_find_mouse_over_ex(region, xy_input, true, false, nullptr, nullptr);
1785
1786 if (but) {
1787 if (but->flag & UI_BUT_DRAG_LOCK) {
1788 const float but_cent_new[2] = {
1789 BLI_rctf_cent_x(&but->rect),
1790 BLI_rctf_cent_y(&but->rect),
1791 };
1792
1793 /* check if this is a different button,
1794 * chances are high the button won't move about :) */
1795 if (len_manhattan_v2v2(drag_info->but_cent_start, but_cent_new) > 1.0f) {
1796 if (fabsf(drag_info->but_cent_start[0] - but_cent_new[0]) <
1797 fabsf(drag_info->but_cent_start[1] - but_cent_new[1]))
1798 {
1799 drag_info->xy_lock[0] = true;
1800 }
1801 else {
1802 drag_info->xy_lock[1] = true;
1803 }
1804 drag_info->is_xy_lock_init = true;
1805 }
1806 }
1807 else {
1808 drag_info->is_xy_lock_init = true;
1809 }
1810 }
1811 }
1812 /* done with axis locking */
1813
1814 int xy[2];
1815 xy[0] = (drag_info->xy_lock[0] == false) ? xy_input[0] : drag_info->xy_last[0];
1816 xy[1] = (drag_info->xy_lock[1] == false) ? xy_input[1] : drag_info->xy_last[1];
1817
1818 /* touch all buttons between last mouse coord and this one */
1819 do_draw = ui_drag_toggle_set_xy_xy(C, region, drag_info->pushed_state, drag_info->xy_last, xy);
1820
1821 if (do_draw) {
1822 ED_region_tag_redraw(region);
1823 }
1824
1825 copy_v2_v2_int(drag_info->xy_last, xy);
1826}
1827
1828static void ui_handler_region_drag_toggle_remove(bContext * /*C*/, void *userdata)
1829{
1830 uiDragToggleHandle *drag_info = static_cast<uiDragToggleHandle *>(userdata);
1831 MEM_freeN(drag_info);
1832}
1833
1834static int ui_handler_region_drag_toggle(bContext *C, const wmEvent *event, void *userdata)
1835{
1836 uiDragToggleHandle *drag_info = static_cast<uiDragToggleHandle *>(userdata);
1837 bool done = false;
1838
1839 switch (event->type) {
1840 case LEFTMOUSE: {
1841 if (event->val == KM_RELEASE) {
1842 done = true;
1843 }
1844 break;
1845 }
1846 case MOUSEMOVE: {
1847 ui_drag_toggle_set(C, drag_info, event->xy);
1848 break;
1849 }
1850 default: {
1851 break;
1852 }
1853 }
1854
1855 if (done) {
1856 wmWindow *win = CTX_wm_window(C);
1857 const ARegion *region = CTX_wm_region(C);
1859 region, drag_info->xy_init, true, false, nullptr, nullptr);
1860
1861 if (but) {
1862 ui_apply_but_undo(but);
1863 }
1864
1868 drag_info,
1869 false);
1871
1873 return WM_UI_HANDLER_BREAK;
1874 }
1876}
1877
1878static bool ui_but_is_drag_toggle(const uiBut *but)
1879{
1880 return ((ui_drag_toggle_but_is_supported(but) == true) &&
1881 /* Menu check is important so the button dragged over isn't removed instantly. */
1882 (ui_block_is_menu(but->block) == false));
1883}
1884
1885#endif /* USE_DRAG_TOGGLE */
1886
1887#ifdef USE_ALLSELECT
1888
1890{
1891 PointerRNA lptr;
1892 PropertyRNA *lprop;
1893 bool success = false;
1894
1896
1897 PointerRNA ptr = but->rnapoin;
1898 PropertyRNA *prop = but->rnaprop;
1899 const int index = but->rnaindex;
1900
1901 /* for now don't support whole colors */
1902 if (index == -1) {
1903 return false;
1904 }
1905
1906 /* if there is a valid property that is editable... */
1907 if (ptr.data && prop) {
1908 bool use_path_from_id;
1909
1910 /* some facts we want to know */
1911 const bool is_array = RNA_property_array_check(prop);
1912 const int rna_type = RNA_property_type(prop);
1913
1914 std::optional<std::string> path;
1915 if (UI_context_copy_to_selected_list(C, &ptr, prop, &lb, &use_path_from_id, &path) &&
1916 !lb.is_empty())
1917 {
1918 selctx_data->elems.resize(lb.size());
1919
1920 int i;
1921 PointerRNA *link;
1922 for (i = 0, link = lb.data(); i < selctx_data->elems.size(); i++, link++) {
1924 link,
1925 prop,
1926 path.has_value() ? path->c_str() : nullptr,
1927 use_path_from_id,
1928 &lptr,
1929 &lprop))
1930 {
1931 selctx_data->elems.pop_last();
1932 i -= 1;
1933 continue;
1934 }
1935
1936 uiSelectContextElem *other = &selctx_data->elems[i];
1937 other->ptr = lptr;
1938 if (is_array) {
1939 if (rna_type == PROP_FLOAT) {
1940 other->val_f = RNA_property_float_get_index(&lptr, lprop, index);
1941 }
1942 else if (rna_type == PROP_INT) {
1943 other->val_i = RNA_property_int_get_index(&lptr, lprop, index);
1944 }
1945 /* ignored for now */
1946# if 0
1947 else if (rna_type == PROP_BOOLEAN) {
1948 other->val_b = RNA_property_boolean_get_index(&lptr, lprop, index);
1949 }
1950# endif
1951 }
1952 else {
1953 if (rna_type == PROP_FLOAT) {
1954 other->val_f = RNA_property_float_get(&lptr, lprop);
1955 }
1956 else if (rna_type == PROP_INT) {
1957 other->val_i = RNA_property_int_get(&lptr, lprop);
1958 }
1959 /* ignored for now */
1960# if 0
1961 else if (rna_type == PROP_BOOLEAN) {
1962 other->val_b = RNA_property_boolean_get(&lptr, lprop);
1963 }
1964 else if (rna_type == PROP_ENUM) {
1965 other->val_i = RNA_property_enum_get(&lptr, lprop);
1966 }
1967# endif
1968 }
1969 }
1970 success = !selctx_data->elems.is_empty();
1971 }
1972 }
1973
1974 /* caller can clear */
1975 selctx_data->do_free = true;
1976
1977 if (success) {
1979 }
1980
1981 return success;
1982}
1983
1984static void ui_selectcontext_end(uiBut *but, uiSelectContextStore *selctx_data)
1985{
1986 if (selctx_data->do_free) {
1987 selctx_data->elems.clear();
1988 }
1989
1991}
1992
1994 uiBut *but,
1995 uiSelectContextStore *selctx_data,
1996 const double value,
1997 const double value_orig)
1998{
1999 if (!selctx_data->elems.is_empty()) {
2000 PropertyRNA *prop = but->rnaprop;
2001 PropertyRNA *lprop = but->rnaprop;
2002 const int index = but->rnaindex;
2003 const bool use_delta = (selctx_data->is_copy == false);
2004
2005 std::variant<bool, int, float, std::string, PointerRNA> delta, min, max;
2006
2007 const bool is_array = RNA_property_array_check(prop);
2008 const int rna_type = RNA_property_type(prop);
2009
2010 if (rna_type == PROP_FLOAT) {
2011 delta.emplace<float>(use_delta ? (value - value_orig) : value);
2012 float min_v, max_v;
2013 RNA_property_float_range(&but->rnapoin, prop, &min_v, &max_v);
2014 min.emplace<float>(min_v);
2015 max.emplace<float>(max_v);
2016 }
2017 else if (rna_type == PROP_INT) {
2018 delta.emplace<int>(int(use_delta ? (value - value_orig) : value));
2019 int min_v, max_v;
2020 RNA_property_int_range(&but->rnapoin, prop, &min_v, &max_v);
2021 min.emplace<int>(min_v);
2022 max.emplace<int>(max_v);
2023 }
2024 else if (rna_type == PROP_ENUM) {
2025 /* Not a delta in fact. */
2026 delta.emplace<int>(RNA_property_enum_get(&but->rnapoin, prop));
2027 }
2028 else if (rna_type == PROP_BOOLEAN) {
2029 if (is_array) {
2030 /* Not a delta in fact. */
2031 delta.emplace<bool>(RNA_property_boolean_get_index(&but->rnapoin, prop, index));
2032 }
2033 else {
2034 /* Not a delta in fact. */
2035 delta.emplace<bool>(RNA_property_boolean_get(&but->rnapoin, prop));
2036 }
2037 }
2038 else if (rna_type == PROP_POINTER) {
2039 /* Not a delta in fact. */
2040 delta.emplace<PointerRNA>(RNA_property_pointer_get(&but->rnapoin, prop));
2041 }
2042 else if (rna_type == PROP_STRING) {
2043 /* Not a delta in fact. */
2044 delta.emplace<std::string>(RNA_property_string_get(&but->rnapoin, prop));
2045 }
2046
2047# ifdef USE_ALLSELECT_LAYER_HACK
2048 /* make up for not having 'handle_layer_buttons' */
2049 {
2050 const PropertySubType subtype = RNA_property_subtype(prop);
2051
2052 if ((rna_type == PROP_BOOLEAN) && ELEM(subtype, PROP_LAYER, PROP_LAYER_MEMBER) && is_array &&
2053 /* could check for 'handle_layer_buttons' */
2054 but->func)
2055 {
2056 wmWindow *win = CTX_wm_window(C);
2057 if ((win->eventstate->modifier & KM_SHIFT) == 0) {
2058 const int len = RNA_property_array_length(&but->rnapoin, prop);
2059 bool *tmparray = MEM_calloc_arrayN<bool>(len, __func__);
2060
2061 tmparray[index] = true;
2062
2063 for (uiSelectContextElem &other : selctx_data->elems) {
2064 PointerRNA lptr = other.ptr;
2065 RNA_property_boolean_set_array(&lptr, lprop, tmparray);
2066 RNA_property_update(C, &lptr, lprop);
2067 }
2068
2069 MEM_freeN(tmparray);
2070
2071 return;
2072 }
2073 }
2074 }
2075# endif
2076
2077 for (uiSelectContextElem &other : selctx_data->elems) {
2078 PointerRNA lptr = other.ptr;
2079
2080 if (rna_type == PROP_FLOAT) {
2081 float other_value = std::get<float>(delta) + (use_delta ? other.val_f : 0.0f);
2082 CLAMP(other_value, std::get<float>(min), std::get<float>(max));
2083 if (is_array) {
2084 RNA_property_float_set_index(&lptr, lprop, index, other_value);
2085 }
2086 else {
2087 RNA_property_float_set(&lptr, lprop, other_value);
2088 }
2089 }
2090 else if (rna_type == PROP_INT) {
2091 int other_value = std::get<int>(delta) + (use_delta ? other.val_i : 0);
2092 CLAMP(other_value, std::get<int>(min), std::get<int>(max));
2093 if (is_array) {
2094 RNA_property_int_set_index(&lptr, lprop, index, other_value);
2095 }
2096 else {
2097 RNA_property_int_set(&lptr, lprop, other_value);
2098 }
2099 }
2100 else if (rna_type == PROP_BOOLEAN) {
2101 const bool other_value = std::get<bool>(delta);
2102 if (is_array) {
2103 RNA_property_boolean_set_index(&lptr, lprop, index, other_value);
2104 }
2105 else {
2106 RNA_property_boolean_set(&lptr, lprop, other_value);
2107 }
2108 }
2109 else if (rna_type == PROP_ENUM) {
2110 const int other_value = std::get<int>(delta);
2111 BLI_assert(!is_array);
2112 RNA_property_enum_set(&lptr, lprop, other_value);
2113 }
2114 else if (rna_type == PROP_POINTER) {
2115 const PointerRNA &other_value = std::get<PointerRNA>(delta);
2116 RNA_property_pointer_set(&lptr, lprop, other_value, nullptr);
2117 }
2118 else if (rna_type == PROP_STRING) {
2119 const std::string &other_value = std::get<std::string>(delta);
2120 RNA_property_string_set(&lptr, lprop, other_value.c_str());
2121 }
2122
2123 RNA_property_update(C, &lptr, prop);
2124 }
2125 }
2126}
2127
2128#endif /* USE_ALLSELECT */
2129
2131
2132/* -------------------------------------------------------------------- */
2135
2137 uiBut *but,
2139 const wmEvent *event)
2140{
2141 /* prevent other WM gestures to start while we try to drag */
2143
2144 /* Clamp the maximum to half the UI unit size so a high user preference
2145 * doesn't require the user to drag more than half the default button height. */
2146 const int drag_threshold = min_ii(
2148 int((UI_UNIT_Y / 2) * ui_block_to_window_scale(data->region, but->block)));
2149
2150 if (abs(data->dragstartx - event->xy[0]) + abs(data->dragstarty - event->xy[1]) > drag_threshold)
2151 {
2153 data->cancel = true;
2154#ifdef USE_DRAG_TOGGLE
2157 ARegion *region_prev;
2158
2159 /* call here because regular mouse-up event won't run,
2160 * typically 'button_activate_exit()' handles this */
2161 ui_apply_but_autokey(C, but);
2162
2164 drag_info->but_cent_start[0] = BLI_rctf_cent_x(&but->rect);
2165 drag_info->but_cent_start[1] = BLI_rctf_cent_y(&but->rect);
2166 copy_v2_v2_int(drag_info->xy_init, event->xy);
2167 copy_v2_v2_int(drag_info->xy_last, event->xy);
2168
2169 /* needed for toggle drag on popups */
2170 region_prev = CTX_wm_region(C);
2171 CTX_wm_region_set(C, data->region);
2172
2174 &data->window->modalhandlers,
2177 drag_info,
2179
2180 CTX_wm_region_set(C, region_prev);
2181
2182 /* Initialize alignment for single row/column regions,
2183 * otherwise we use the relative position of the first other button dragged over. */
2184 if (ELEM(data->region->regiontype,
2190 {
2191 const int region_alignment = RGN_ALIGN_ENUM_FROM_MASK(data->region->alignment);
2192 int lock_axis = -1;
2193
2194 if (ELEM(region_alignment, RGN_ALIGN_LEFT, RGN_ALIGN_RIGHT)) {
2195 lock_axis = 0;
2196 }
2197 else if (ELEM(region_alignment, RGN_ALIGN_TOP, RGN_ALIGN_BOTTOM)) {
2198 lock_axis = 1;
2199 }
2200 if (lock_axis != -1) {
2201 drag_info->xy_lock[lock_axis] = true;
2202 drag_info->is_xy_lock_init = true;
2203 }
2204 }
2205 }
2206 else
2207#endif
2208 if (but->type == ButType::Color)
2209 {
2210 bool valid = false;
2212
2213 drag_info->has_alpha = ui_but_color_has_alpha(but);
2214
2215 /* TODO: support more button pointer types. */
2217 ui_but_v4_get(but, drag_info->color);
2218 drag_info->gamma_corrected = true;
2219 valid = true;
2220 }
2221 else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) {
2222 ui_but_v4_get(but, drag_info->color);
2223 drag_info->gamma_corrected = false;
2224 valid = true;
2225 }
2227 ui_but_v4_get(but, drag_info->color);
2228 copy_v4_v4(drag_info->color, (float *)but->poin);
2229 drag_info->gamma_corrected = false;
2230 valid = true;
2231 }
2232
2233 if (valid) {
2234 WM_event_start_drag(C, ICON_COLOR, WM_DRAG_COLOR, drag_info, WM_DRAG_FREE_DATA);
2235 }
2236 else {
2237 MEM_freeN(drag_info);
2238 return false;
2239 }
2240 }
2241 else if (but->type == ButType::ViewItem) {
2242 const uiButViewItem *view_item_but = (uiButViewItem *)but;
2243 if (view_item_but->view_item) {
2244 return UI_view_item_drag_start(*C, *view_item_but->view_item);
2245 }
2246 }
2247 else {
2248 ui_but_drag_start(C, but);
2249 }
2250 return true;
2251 }
2252
2253 return false;
2254}
2255
2257
2258/* -------------------------------------------------------------------- */
2261
2263{
2264 ui_apply_but_func(C, but);
2265 data->retval = but->retval;
2266 data->applied = true;
2267}
2268
2270{
2271 ui_apply_but_func(C, but);
2272 data->retval = but->retval;
2273 data->applied = true;
2274}
2275
2277{
2278 ui_apply_but_func(C, but);
2279 data->retval = but->retval;
2280 data->applied = true;
2281}
2282
2284{
2285 ui_apply_but_func(C, but);
2286 data->retval = but->retval;
2287 data->applied = true;
2288}
2289
2290static void ui_apply_but(
2291 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const bool interactive)
2292{
2293 const ButType but_type = but->type; /* Store as const to quiet maybe uninitialized warning. */
2294
2295 data->retval = 0;
2296
2297 /* if we cancel and have not applied yet, there is nothing to do,
2298 * otherwise we have to restore the original value again */
2299 if (data->cancel) {
2300 if (!data->applied) {
2301 return;
2302 }
2303
2304 if (data->text_edit.edit_string) {
2305 MEM_freeN(data->text_edit.edit_string);
2306 }
2307 data->text_edit.edit_string = data->text_edit.original_string;
2308 data->text_edit.original_string = nullptr;
2309 data->value = data->origvalue;
2310 copy_v3_v3(data->vec, data->origvec);
2311 /* postpone clearing origdata */
2312 }
2313 else {
2314 /* We avoid applying interactive edits a second time
2315 * at the end with the #uiHandleButtonData.applied_interactive flag. */
2316 if (interactive) {
2317 data->applied_interactive = true;
2318 }
2319 else if (data->applied_interactive) {
2320 return;
2321 }
2322
2323#ifdef USE_ALLSELECT
2324# ifdef USE_DRAG_MULTINUM
2325 if (but->flag & UI_BUT_DRAG_MULTI) {
2326 /* pass */
2327 }
2328 else
2329# endif
2330 if (data->select_others.elems.is_empty())
2331 {
2332 wmWindow *win = CTX_wm_window(C);
2333 const wmEvent *event = win->eventstate;
2334 /* May have been enabled before activating, don't do for array pasting. */
2335 if (data->select_others.is_enabled || IS_ALLSELECT_EVENT(event)) {
2336 /* See comment for #IS_ALLSELECT_EVENT why this needs to be filtered here. */
2337 const bool is_array_paste = (event->val == KM_PRESS) &&
2338 (event->modifier & (KM_CTRL | KM_OSKEY)) &&
2339 (event->modifier & KM_SHIFT) == 0 && (event->type == EVT_VKEY);
2340 if (!is_array_paste) {
2341 ui_selectcontext_begin(C, but, &data->select_others);
2342 data->select_others.is_enabled = true;
2343 }
2344 }
2345 }
2346 if (data->select_others.elems.is_empty()) {
2347 /* Don't check again. */
2348 data->select_others.elems.clear();
2349 }
2350#endif
2351 }
2352
2353 /* ensures we are writing actual values */
2354 char *editstr = but->editstr;
2355 double *editval = but->editval;
2356 float *editvec = but->editvec;
2357 ColorBand *editcoba;
2358 CurveMapping *editcumap;
2359 CurveProfile *editprofile;
2360 if (but_type == ButType::ColorBand) {
2361 uiButColorBand *but_coba = (uiButColorBand *)but;
2362 editcoba = but_coba->edit_coba;
2363 }
2364 else if (but_type == ButType::Curve) {
2365 uiButCurveMapping *but_cumap = (uiButCurveMapping *)but;
2366 editcumap = but_cumap->edit_cumap;
2367 }
2368 else if (but_type == ButType::CurveProfile) {
2369 uiButCurveProfile *but_profile = (uiButCurveProfile *)but;
2370 editprofile = but_profile->edit_profile;
2371 }
2372 but->editstr = nullptr;
2373 but->editval = nullptr;
2374 but->editvec = nullptr;
2375 if (but_type == ButType::ColorBand) {
2376 uiButColorBand *but_coba = (uiButColorBand *)but;
2377 but_coba->edit_coba = nullptr;
2378 }
2379 else if (but_type == ButType::Curve) {
2380 uiButCurveMapping *but_cumap = (uiButCurveMapping *)but;
2381 but_cumap->edit_cumap = nullptr;
2382 }
2383 else if (but_type == ButType::CurveProfile) {
2384 uiButCurveProfile *but_profile = (uiButCurveProfile *)but;
2385 but_profile->edit_profile = nullptr;
2386 }
2387
2388 /* handle different types */
2389 switch (but_type) {
2390 case ButType::But:
2391 case ButType::Decorator:
2393 ui_apply_but_BUT(C, but, data);
2394 break;
2395 case ButType::Text:
2397 ui_apply_but_TEX(C, but, data);
2398 break;
2399 case ButType::ButToggle:
2400 case ButType::Toggle:
2401 case ButType::ToggleN:
2404 case ButType::Checkbox:
2405 case ButType::CheckboxN:
2406 ui_apply_but_TOG(C, but, data);
2407 break;
2408 case ButType::ListRow:
2409 case ButType::Row:
2410 ui_apply_but_ROW(C, block, but, data);
2411 break;
2412 case ButType::ViewItem:
2413 ui_apply_but_VIEW_ITEM(C, block, but, data);
2414 break;
2415 case ButType::Tab:
2416 ui_apply_but_TAB(C, but, data);
2417 break;
2418 case ButType::Scroll:
2419 case ButType::Grip:
2420 case ButType::Num:
2421 case ButType::NumSlider:
2422 ui_apply_but_NUM(C, but, data);
2423 break;
2424 case ButType::Menu:
2425 case ButType::Block:
2426 case ButType::Pulldown:
2427 ui_apply_but_BLOCK(C, but, data);
2428 break;
2429 case ButType::Color:
2430 if (data->cancel) {
2431 ui_apply_but_VEC(C, but, data);
2432 }
2433 else {
2434 ui_apply_but_BLOCK(C, but, data);
2435 }
2436 break;
2437 case ButType::ButMenu:
2438 ui_apply_but_BUTM(C, but, data);
2439 break;
2440 case ButType::Unitvec:
2441 case ButType::HsvCube:
2442 case ButType::HsvCircle:
2443 ui_apply_but_VEC(C, but, data);
2444 break;
2445 case ButType::ColorBand:
2447 break;
2448 case ButType::Curve:
2449 ui_apply_but_CURVE(C, but, data);
2450 break;
2453 break;
2454 case ButType::KeyEvent:
2456 ui_apply_but_BUT(C, but, data);
2457 break;
2458 case ButType::Image:
2459 ui_apply_but_IMAGE(C, but, data);
2460 break;
2461 case ButType::Histogram:
2463 break;
2464 case ButType::Waveform:
2466 break;
2469 break;
2470 default:
2471 break;
2472 }
2473
2474#ifdef USE_DRAG_MULTINUM
2475 if (data->multi_data.has_mbuts) {
2476 if ((data->multi_data.init == uiHandleButtonMulti::INIT_ENABLE) &&
2477 (data->multi_data.skip == false))
2478 {
2479 if (data->cancel) {
2480 ui_multibut_restore(C, data, block);
2481 }
2482 else {
2484 }
2485 }
2486 }
2487#endif
2488
2489#ifdef USE_ALLSELECT
2490 ui_selectcontext_apply(C, but, &data->select_others, data->value, data->origvalue);
2491#endif
2492
2493 if (data->cancel) {
2494 data->origvalue = 0.0;
2495 zero_v3(data->origvec);
2496 }
2497
2498 but->editstr = editstr;
2499 but->editval = editval;
2500 but->editvec = editvec;
2501 if (but_type == ButType::ColorBand) {
2502 uiButColorBand *but_coba = (uiButColorBand *)but;
2503 but_coba->edit_coba = editcoba;
2504 }
2505 else if (but_type == ButType::Curve) {
2506 uiButCurveMapping *but_cumap = (uiButCurveMapping *)but;
2507 but_cumap->edit_cumap = editcumap;
2508 }
2509 else if (but_type == ButType::CurveProfile) {
2510 uiButCurveProfile *but_profile = (uiButCurveProfile *)but;
2511 but_profile->edit_profile = editprofile;
2512 }
2513
2514 if (data->custom_interaction_handle != nullptr) {
2516 C, &block->custom_interaction_callbacks, data->custom_interaction_handle);
2517 }
2518}
2519
2521
2522/* -------------------------------------------------------------------- */
2525
2526static void ui_but_get_pasted_text_from_clipboard(const bool ensure_utf8,
2527 char **r_buf_paste,
2528 int *r_buf_len)
2529{
2530 /* get only first line even if the clipboard contains multiple lines */
2531 int length;
2532 char *text = WM_clipboard_text_get_firstline(false, ensure_utf8, &length);
2533
2534 if (text) {
2535 *r_buf_paste = text;
2536 *r_buf_len = length;
2537 }
2538 else {
2539 *r_buf_paste = MEM_callocN<char>(__func__);
2540 *r_buf_len = 0;
2541 }
2542}
2543
2545{
2546 return RNA_property_array_length(&but->rnapoin, but->rnaprop);
2547}
2548
2550 bContext *C, uiBut *but, uiHandleButtonData *data, const float *values, const int values_len)
2551{
2553
2554 RNA_property_float_set_array_at_most(&but->rnapoin, but->rnaprop, values, values_len);
2555 if (data) {
2556 if (but->type == ButType::Unitvec) {
2557 BLI_assert(values_len == 3);
2558 copy_v3_v3(data->vec, values);
2559 }
2560 else {
2561 data->value = values[but->rnaindex];
2562 }
2563 }
2564
2566}
2567
2568static void float_array_to_string(const float *values,
2569 const int values_len,
2570 char *output,
2571 int output_maxncpy)
2572{
2573 const int values_end = values_len - 1;
2574 int ofs = 0;
2575 output[ofs++] = '[';
2576 for (int i = 0; i < values_len; i++) {
2578 output + ofs, output_maxncpy - ofs, (i != values_end) ? "%f, " : "%f]", values[i]);
2579 }
2580}
2581
2582static void ui_but_copy_numeric_array(uiBut *but, char *output, int output_maxncpy)
2583{
2584 const int values_len = get_but_property_array_length(but);
2585 blender::Array<float, 16> values(values_len);
2586 RNA_property_float_get_array(&but->rnapoin, but->rnaprop, values.data());
2587 float_array_to_string(values.data(), values_len, output, output_maxncpy);
2588}
2589
2590static bool parse_float_array(char *text, float *values, int values_len_expected)
2591{
2592 /* can parse max 4 floats for now */
2593 BLI_assert(0 <= values_len_expected && values_len_expected <= 4);
2594
2595 float v[5];
2596 const int values_len_actual = sscanf(
2597 text, "[%f, %f, %f, %f, %f]", &v[0], &v[1], &v[2], &v[3], &v[4]);
2598
2599 if (values_len_actual == values_len_expected) {
2600 memcpy(values, v, sizeof(float) * values_len_expected);
2601 return true;
2602 }
2603 return false;
2604}
2605
2607 uiBut *but,
2609 char *buf_paste)
2610{
2611 const int values_len = get_but_property_array_length(but);
2612 if (values_len > 4) {
2613 /* not supported for now */
2614 return;
2615 }
2616
2617 blender::Array<float, 16> values(values_len);
2618
2619 if (parse_float_array(buf_paste, values.data(), values_len)) {
2620 ui_but_set_float_array(C, but, data, values.data(), values_len);
2621 }
2622 else {
2623 WM_global_report(RPT_ERROR, "Expected an array of numbers: [n, n, ...]");
2624 }
2625}
2626
2627static void ui_but_copy_numeric_value(uiBut *but, char *output, int output_maxncpy)
2628{
2629 /* Get many decimal places, then strip trailing zeros.
2630 * NOTE: too high values start to give strange results. */
2631 ui_but_string_get_ex(but, output, output_maxncpy, UI_PRECISION_FLOAT_MAX, false, nullptr);
2633}
2634
2636 uiBut *but,
2638 char *buf_paste)
2639{
2640 double value;
2641 if (ui_but_string_eval_number(C, but, buf_paste, &value)) {
2643 data->value = value;
2644 ui_but_string_set(C, but, buf_paste);
2646 }
2647 else {
2648 WM_global_report(RPT_ERROR, "Expected a number");
2649 }
2650}
2651
2653 uiBut *but,
2655 char *buf_paste)
2656{
2657 float xyz[3];
2658 if (parse_float_array(buf_paste, xyz, 3)) {
2659 if (normalize_v3(xyz) == 0.0f) {
2660 /* better set Z up then have a zero vector */
2661 xyz[2] = 1.0;
2662 }
2663 ui_but_set_float_array(C, but, data, xyz, 3);
2664 }
2665 else {
2666 WM_global_report(RPT_ERROR, "Paste expected 3 numbers, formatted: '[n, n, n]'");
2667 }
2668}
2669
2670static void ui_but_copy_color(uiBut *but, char *output, int output_maxncpy)
2671{
2672 float rgba[4];
2673
2674 if (but->rnaprop && get_but_property_array_length(but) >= 4) {
2675 rgba[3] = RNA_property_float_get_index(&but->rnapoin, but->rnaprop, 3);
2676 }
2677 else {
2678 rgba[3] = 1.0f;
2679 }
2680
2681 ui_but_v3_get(but, rgba);
2682
2683 /* convert to linear color to do compatible copy between gamma and non-gamma */
2685 srgb_to_linearrgb_v3_v3(rgba, rgba);
2686 }
2687
2688 float_array_to_string(rgba, 4, output, output_maxncpy);
2689}
2690
2691static void ui_but_paste_color(bContext *C, uiBut *but, char *buf_paste)
2692{
2693 float rgba[4];
2694 if (parse_float_array(buf_paste, rgba, 4)) {
2695 if (but->rnaprop) {
2696 /* Assume linear colors in buffer. */
2698 linearrgb_to_srgb_v3_v3(rgba, rgba);
2699 }
2700
2701 /* Some color properties are RGB, not RGBA. */
2702 const int array_len = get_but_property_array_length(but);
2703 ui_but_set_float_array(C, but, nullptr, rgba, std::min(array_len, int(ARRAY_SIZE(rgba))));
2704 }
2705 }
2706 else {
2707 WM_global_report(RPT_ERROR, "Paste expected 4 numbers, formatted: '[n, n, n, n]'");
2708 }
2709}
2710
2711static void ui_but_copy_text(uiBut *but, char *output, int output_maxncpy)
2712{
2713 ui_but_string_get(but, output, output_maxncpy);
2714}
2715
2716static void ui_but_paste_text(bContext *C, uiBut *but, uiHandleButtonData *data, char *buf_paste)
2717{
2718 BLI_assert(but->active == data);
2720 ui_but_set_string_interactive(C, but, buf_paste);
2721}
2722
2724{
2725 if (but->poin != nullptr) {
2726 memcpy(&but_copypaste_coba, but->poin, sizeof(ColorBand));
2727 }
2728}
2729
2731{
2732 if (but_copypaste_coba.tot != 0 && but->poin != nullptr) {
2734 memcpy(data->coba, &but_copypaste_coba, sizeof(ColorBand));
2736 }
2737}
2738
2740{
2741 if (but->poin != nullptr) {
2745 }
2746}
2747
2757
2766
2776
2777static void ui_but_copy_operator(bContext *C, uiBut *but, char *output, int output_maxncpy)
2778{
2780
2781 std::string str = WM_operator_pystring_ex(C, nullptr, false, true, but->optype, opptr);
2782 BLI_strncpy_utf8(output, str.c_str(), output_maxncpy);
2783}
2784
2785static bool ui_but_copy_menu(uiBut *but, char *output, int output_maxncpy)
2786{
2787 MenuType *mt = UI_but_menutype_get(but);
2788 if (mt) {
2789 BLI_snprintf_utf8(output, output_maxncpy, "bpy.ops.wm.call_menu(name=\"%s\")", mt->idname);
2790 return true;
2791 }
2792 return false;
2793}
2794
2795static bool ui_but_copy_popover(uiBut *but, char *output, int output_maxncpy)
2796{
2798 if (pt) {
2799 BLI_snprintf_utf8(output, output_maxncpy, "bpy.ops.wm.call_panel(name=\"%s\")", pt->idname);
2800 return true;
2801 }
2802 return false;
2803}
2804
2806static bool ui_but_copy(bContext *C, uiBut *but, const bool copy_array)
2807{
2808 if (ui_but_contains_password(but)) {
2809 return false;
2810 }
2811
2812 /* Arbitrary large value (allow for paths: 'PATH_MAX') */
2813 char buf[4096] = {0};
2814 const int buf_maxncpy = sizeof(buf);
2815
2816 /* Left false for copying internal data (color-band for eg). */
2817 bool is_buf_set = false;
2818
2819 const bool has_required_data = !(but->poin == nullptr && but->rnapoin.data == nullptr);
2820
2821 switch (but->type) {
2822 case ButType::Num:
2823 case ButType::NumSlider:
2824 if (!has_required_data) {
2825 break;
2826 }
2827 if (copy_array && ui_but_has_array_value(but)) {
2828 ui_but_copy_numeric_array(but, buf, buf_maxncpy);
2829 }
2830 else {
2831 ui_but_copy_numeric_value(but, buf, buf_maxncpy);
2832 }
2833 is_buf_set = true;
2834 break;
2835
2836 case ButType::Unitvec:
2837 if (!has_required_data) {
2838 break;
2839 }
2840 ui_but_copy_numeric_array(but, buf, buf_maxncpy);
2841 is_buf_set = true;
2842 break;
2843
2844 case ButType::Color:
2845 if (!has_required_data) {
2846 break;
2847 }
2848 ui_but_copy_color(but, buf, buf_maxncpy);
2849 is_buf_set = true;
2850 break;
2851
2852 case ButType::Text:
2854 if (!has_required_data) {
2855 break;
2856 }
2857 ui_but_copy_text(but, buf, buf_maxncpy);
2858 is_buf_set = true;
2859 break;
2860
2861 case ButType::ColorBand:
2863 break;
2864
2865 case ButType::Curve:
2867 break;
2868
2871 break;
2872
2873 case ButType::But:
2874 if (!but->optype) {
2875 break;
2876 }
2877 ui_but_copy_operator(C, but, buf, buf_maxncpy);
2878 is_buf_set = true;
2879 break;
2880
2881 case ButType::Menu:
2882 case ButType::Pulldown:
2883 if (ui_but_copy_menu(but, buf, buf_maxncpy)) {
2884 is_buf_set = true;
2885 }
2886 break;
2887 case ButType::Popover:
2888 if (ui_but_copy_popover(but, buf, buf_maxncpy)) {
2889 is_buf_set = true;
2890 }
2891 break;
2892
2893 default:
2894 break;
2895 }
2896
2897 if (is_buf_set) {
2898 WM_clipboard_text_set(buf, false);
2899 }
2900 return is_buf_set;
2901}
2902
2903static void ui_but_paste(bContext *C, uiBut *but, uiHandleButtonData *data, const bool paste_array)
2904{
2905 BLI_assert((but->flag & UI_BUT_DISABLED) == 0); /* caller should check */
2906
2907 int buf_paste_len = 0;
2908 char *buf_paste;
2909 ui_but_get_pasted_text_from_clipboard(UI_but_is_utf8(but), &buf_paste, &buf_paste_len);
2910
2911 const bool has_required_data = !(but->poin == nullptr && but->rnapoin.data == nullptr);
2912
2913 switch (but->type) {
2914 case ButType::Num:
2915 case ButType::NumSlider:
2916 if (!has_required_data) {
2917 break;
2918 }
2919 if (paste_array && ui_but_has_array_value(but)) {
2920 ui_but_paste_numeric_array(C, but, data, buf_paste);
2921 }
2922 else {
2923 ui_but_paste_numeric_value(C, but, data, buf_paste);
2924 }
2925 break;
2926
2927 case ButType::Unitvec:
2928 if (!has_required_data) {
2929 break;
2930 }
2931 ui_but_paste_normalized_vector(C, but, data, buf_paste);
2932 break;
2933
2934 case ButType::Color:
2935 if (!has_required_data) {
2936 break;
2937 }
2938 ui_but_paste_color(C, but, buf_paste);
2939 break;
2940
2941 case ButType::Text:
2943 if (!has_required_data) {
2944 break;
2945 }
2946 ui_but_paste_text(C, but, data, buf_paste);
2947 break;
2948
2949 case ButType::ColorBand:
2951 break;
2952
2953 case ButType::Curve:
2955 break;
2956
2959 break;
2960
2961 default:
2962 break;
2963 }
2964
2965 MEM_freeN(buf_paste);
2966}
2967
2973
2975
2976/* -------------------------------------------------------------------- */
2988
2990{
2991 const char *butstr = (but->editstr) ? but->editstr : but->drawstr.c_str();
2992 const char *strpos = butstr;
2993 const char *str_end = butstr + strlen(butstr);
2994 for (int i = 0; i < pos; i++) {
2995 strpos = BLI_str_find_next_char_utf8(strpos, str_end);
2996 }
2997
2998 return (strpos - butstr);
2999}
3000
3002{
3003 const char *butstr = (but->editstr) ? but->editstr : but->drawstr.c_str();
3004 return BLI_strnlen_utf8(butstr, pos);
3005}
3006
3008 uiBut *but,
3009 const bool restore)
3010{
3011 if (!(but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_PASSWORD)) {
3012 return;
3013 }
3014
3015 char *butstr = (but->editstr) ? but->editstr : but->drawstr.data();
3016
3017 if (restore) {
3018 /* restore original string */
3019 BLI_strncpy(butstr, password_str, UI_MAX_PASSWORD_STR);
3020
3021 /* remap cursor positions */
3022 if (but->pos >= 0) {
3023 but->pos = ui_text_position_from_hidden(but, but->pos);
3024 but->selsta = ui_text_position_from_hidden(but, but->selsta);
3025 but->selend = ui_text_position_from_hidden(but, but->selend);
3026 }
3027 }
3028 else {
3029 /* convert text to hidden text using asterisks (e.g. pass -> ****) */
3030 const size_t len = BLI_strlen_utf8(butstr);
3031
3032 /* remap cursor positions */
3033 if (but->pos >= 0) {
3034 but->pos = ui_text_position_to_hidden(but, but->pos);
3035 but->selsta = ui_text_position_to_hidden(but, but->selsta);
3036 but->selend = ui_text_position_to_hidden(but, but->selend);
3037 }
3038
3039 /* save original string */
3040 BLI_strncpy(password_str, butstr, UI_MAX_PASSWORD_STR);
3041 memset(butstr, '*', len);
3042 butstr[len] = '\0';
3043 }
3044}
3045
3047
3048/* -------------------------------------------------------------------- */
3051
3052void ui_but_set_string_interactive(bContext *C, uiBut *but, const char *value)
3053{
3054 /* Caller should check. */
3055 BLI_assert((but->flag & UI_BUT_DISABLED) == 0);
3056
3058 ui_textedit_string_set(but, but->active->text_edit, value);
3059
3060 if (but->type == ButType::SearchMenu && but->active) {
3061 but->changed = true;
3062 ui_searchbox_update(C, but->active->searchbox, but, true);
3063 }
3064
3066}
3067
3069{
3070 if (!but->active) {
3071 return;
3072 }
3073
3074 /* most likely nullptr, but let's check, and give it temp zero string */
3075 if (!but->active->text_edit.edit_string) {
3076 but->active->text_edit.edit_string = MEM_calloc_arrayN<char>(1, "temp str");
3077 }
3078 but->active->text_edit.edit_string[0] = 0;
3079
3080 ui_apply_but_TEX(C, but, but->active);
3082}
3083
3085 uiTextEdit &text_edit,
3086 int str_maxncpy)
3087{
3088 BLI_assert(text_edit.is_str_dynamic);
3089 BLI_assert(text_edit.edit_string == but->editstr);
3090
3091 if (str_maxncpy > text_edit.max_string_size) {
3092 text_edit.edit_string = but->editstr = static_cast<char *>(
3093 MEM_reallocN(text_edit.edit_string, sizeof(char) * str_maxncpy));
3094 text_edit.max_string_size = str_maxncpy;
3095 }
3096}
3097
3098static void ui_textedit_string_set(uiBut *but, uiTextEdit &text_edit, const char *str)
3099{
3100 if (text_edit.is_str_dynamic) {
3101 ui_textedit_string_ensure_max_length(but, text_edit, strlen(str) + 1);
3102 }
3103
3104 if (UI_but_is_utf8(but)) {
3105 BLI_strncpy_utf8(text_edit.edit_string, str, text_edit.max_string_size);
3106 }
3107 else {
3108 BLI_strncpy(text_edit.edit_string, str, text_edit.max_string_size);
3109 }
3110}
3111
3112static bool ui_textedit_delete_selection(uiBut *but, uiTextEdit &text_edit)
3113{
3114 char *str = text_edit.edit_string;
3115 const int len = strlen(str);
3116 bool changed = false;
3117 if (but->selsta != but->selend && len) {
3118 memmove(str + but->selsta, str + but->selend, (len - but->selend) + 1);
3119 changed = true;
3120 }
3121
3122 if (but->ofs > but->selsta) {
3123 /* Decrease the offset by the amount of the selection that is hidden. Without
3124 * this adjustment, pasting text that doesn't fit in the text field would leave
3125 * the pasted text scrolled out of the view (to the left), see: #134491. */
3126 but->ofs -= (but->ofs - but->selsta);
3127 }
3128
3129 but->pos = but->selend = but->selsta;
3130 return changed;
3131}
3132
3138static void ui_textedit_set_cursor_pos(uiBut *but, const ARegion *region, const float x)
3139{
3140 /* XXX pass on as arg. */
3141 uiFontStyle fstyle = UI_style_get()->widget;
3142 const float aspect = but->block->aspect;
3143
3144 float startx = but->rect.xmin;
3145 float starty_dummy = 0.0f;
3146 char password_str[UI_MAX_PASSWORD_STR];
3147 /* treat 'str_last' as null terminator for str, no need to modify in-place */
3148 const char *str = but->editstr, *str_last;
3149
3150 ui_block_to_window_fl(region, but->block, &startx, &starty_dummy);
3151
3152 ui_fontscale(&fstyle.points, aspect);
3153
3154 UI_fontstyle_set(&fstyle);
3155
3156 ui_but_text_password_hide(password_str, but, false);
3157
3159 if (but->flag & UI_HAS_ICON) {
3160 startx += UI_ICON_SIZE / aspect;
3161 }
3162 }
3163 startx -= U.pixelsize / aspect;
3164 if (!(but->drawflag & UI_BUT_NO_TEXT_PADDING)) {
3165 startx += UI_TEXT_MARGIN_X * U.widget_unit / aspect;
3166 }
3167
3168 /* mouse dragged outside the widget to the left */
3169 if (x < startx) {
3170 int i = but->ofs;
3171
3172 str_last = &str[but->ofs];
3173
3174 while (i > 0) {
3175 if (BLI_str_cursor_step_prev_utf8(str, but->ofs, &i)) {
3176 /* 0.25 == scale factor for less sensitivity */
3177 if (BLF_width(fstyle.uifont_id, str + i, (str_last - str) - i) > (startx - x) * 0.25f) {
3178 break;
3179 }
3180 }
3181 else {
3182 break; /* unlikely but possible */
3183 }
3184 }
3185 but->ofs = i;
3186 but->pos = but->ofs;
3187 }
3188 /* mouse inside the widget, mouse coords mapped in widget space */
3189 else {
3190 but->pos = but->ofs +
3192 fstyle.uifont_id, str + but->ofs, strlen(str + but->ofs), int(x - startx));
3193 }
3194
3195 ui_but_text_password_hide(password_str, but, true);
3196}
3197
3199{
3200 ui_textedit_set_cursor_pos(but, data->region, x);
3201
3202 but->selsta = but->pos;
3203 but->selend = data->text_edit.sel_pos_init;
3204 if (but->selend < but->selsta) {
3205 std::swap(but->selsta, but->selend);
3206 }
3207
3208 ui_but_update(but);
3209}
3210
3216static bool ui_textedit_insert_buf(uiBut *but, uiTextEdit &text_edit, const char *buf, int buf_len)
3217{
3218 int len = strlen(text_edit.edit_string);
3219 const int str_maxncpy_new = len - (but->selend - but->selsta) + 1;
3220 bool changed = false;
3221
3222 if (text_edit.is_str_dynamic) {
3223 ui_textedit_string_ensure_max_length(but, text_edit, str_maxncpy_new + buf_len);
3224 }
3225
3226 if (str_maxncpy_new <= text_edit.max_string_size) {
3227 char *str = text_edit.edit_string;
3228 size_t step = buf_len;
3229
3230 /* type over the current selection */
3231 if ((but->selend - but->selsta) > 0) {
3232 changed = ui_textedit_delete_selection(but, text_edit);
3233 len = strlen(str);
3234 }
3235
3236 if ((len + step >= text_edit.max_string_size) && (text_edit.max_string_size - (len + 1) > 0)) {
3237 if (UI_but_is_utf8(but)) {
3238 /* Shorten 'step' to a UTF8 aligned size that fits. */
3239 BLI_strnlen_utf8_ex(buf, text_edit.max_string_size - (len + 1), &step);
3240 }
3241 else {
3242 step = text_edit.max_string_size - (len + 1);
3243 }
3244 }
3245
3246 if (step && (len + step < text_edit.max_string_size)) {
3247 memmove(&str[but->pos + step], &str[but->pos], (len + 1) - but->pos);
3248 memcpy(&str[but->pos], buf, step * sizeof(char));
3249 but->pos += step;
3250 changed = true;
3251 }
3252 }
3253
3254 return changed;
3255}
3256
3257#ifdef WITH_INPUT_IME
3258static bool ui_textedit_insert_ascii(uiBut *but, uiHandleButtonData *data, const char ascii)
3259{
3260 BLI_assert(isascii(ascii));
3261 const char buf[2] = {ascii, '\0'};
3262 return ui_textedit_insert_buf(but, data->text_edit, buf, sizeof(buf) - 1);
3263}
3264#endif
3265
3266static void ui_textedit_move(uiBut *but,
3267 uiTextEdit &text_edit,
3268 eStrCursorJumpDirection direction,
3269 const bool select,
3271{
3272 const char *str = text_edit.edit_string;
3273 const int len = strlen(str);
3274 const int pos_prev = but->pos;
3275 const bool has_sel = (but->selend - but->selsta) > 0;
3276
3277 ui_but_update(but);
3278
3279 /* special case, quit selection and set cursor */
3280 if (has_sel && !select) {
3281 if (jump == STRCUR_JUMP_ALL) {
3282 but->selsta = but->selend = but->pos = direction ? len : 0;
3283 }
3284 else {
3285 if (direction) {
3286 but->selsta = but->pos = but->selend;
3287 }
3288 else {
3289 but->pos = but->selend = but->selsta;
3290 }
3291 }
3292 text_edit.sel_pos_init = but->pos;
3293 }
3294 else {
3295 int pos_i = but->pos;
3296 BLI_str_cursor_step_utf8(str, len, &pos_i, direction, jump, true);
3297 but->pos = pos_i;
3298
3299 if (select) {
3300 if (has_sel == false) {
3301 /* Holding shift but with no previous selection. */
3302 but->selsta = but->pos;
3303 but->selend = pos_prev;
3304 }
3305 else if (but->selsta == pos_prev) {
3306 /* Previous selection, extending start position. */
3307 but->selsta = but->pos;
3308 }
3309 else {
3310 /* Previous selection, extending end position. */
3311 but->selend = but->pos;
3312 }
3313 }
3314 if (but->selend < but->selsta) {
3315 std::swap(but->selsta, but->selend);
3316 }
3317 }
3318}
3319
3320static bool ui_textedit_delete(uiBut *but,
3321 uiTextEdit &text_edit,
3322 eStrCursorJumpDirection direction,
3324{
3325 char *str = text_edit.edit_string;
3326 const int len = strlen(str);
3327
3328 bool changed = false;
3329
3330 if (jump == STRCUR_JUMP_ALL) {
3331 if (len) {
3332 changed = true;
3333 }
3334 str[0] = '\0';
3335 but->pos = 0;
3336 }
3337 else if (direction) { /* delete */
3338 if ((but->selend - but->selsta) > 0) {
3339 changed = ui_textedit_delete_selection(but, text_edit);
3340 }
3341 else if (but->pos >= 0 && but->pos < len) {
3342 int pos = but->pos;
3343 int step;
3344 BLI_str_cursor_step_utf8(str, len, &pos, direction, jump, true);
3345 step = pos - but->pos;
3346 memmove(&str[but->pos], &str[but->pos + step], (len + 1) - (but->pos + step));
3347 changed = true;
3348 }
3349 }
3350 else { /* backspace */
3351 if (len != 0) {
3352 if ((but->selend - but->selsta) > 0) {
3353 changed = ui_textedit_delete_selection(but, text_edit);
3354 }
3355 else if (but->pos > 0) {
3356 int pos = but->pos;
3357 int step;
3358
3359 BLI_str_cursor_step_utf8(str, len, &pos, direction, jump, true);
3360 step = but->pos - pos;
3361 memmove(&str[but->pos - step], &str[but->pos], (len + 1) - but->pos);
3362 but->pos -= step;
3363 changed = true;
3364 }
3365 }
3366 }
3367
3368 return changed;
3369}
3370
3372{
3373 char *str = data->text_edit.edit_string;
3374
3375 int changed;
3376 if (data->searchbox) {
3377 changed = ui_searchbox_autocomplete(C, data->searchbox, but, data->text_edit.edit_string);
3378 }
3379 else {
3380 changed = but->autocomplete_func(C, str, but->autofunc_arg);
3381 }
3382
3383 but->pos = strlen(str);
3384 but->selsta = but->selend = but->pos;
3385
3386 return changed;
3387}
3388
3389/* mode for ui_textedit_copypaste() */
3390enum {
3394};
3395
3396static bool ui_textedit_copypaste(uiBut *but, uiTextEdit &text_edit, const int mode)
3397{
3398 bool changed = false;
3399
3400 /* paste */
3401 if (mode == UI_TEXTEDIT_PASTE) {
3402 /* extract the first line from the clipboard */
3403 int buf_len;
3404 char *pbuf = WM_clipboard_text_get_firstline(false, UI_but_is_utf8(but), &buf_len);
3405
3406 if (pbuf) {
3407 ui_textedit_insert_buf(but, text_edit, pbuf, buf_len);
3408
3409 changed = true;
3410
3411 MEM_freeN(pbuf);
3412 }
3413 }
3414 /* cut & copy */
3415 else if (ELEM(mode, UI_TEXTEDIT_COPY, UI_TEXTEDIT_CUT)) {
3416 /* copy the contents to the copypaste buffer */
3417 const int sellen = but->selend - but->selsta;
3418 char *buf = MEM_malloc_arrayN<char>((sellen + 1), "ui_textedit_copypaste");
3419
3420 memcpy(buf, text_edit.edit_string + but->selsta, sellen);
3421 buf[sellen] = '\0';
3422
3423 WM_clipboard_text_set(buf, false);
3424 MEM_freeN(buf);
3425
3426 /* for cut only, delete the selection afterwards */
3427 if (mode == UI_TEXTEDIT_CUT) {
3428 if ((but->selend - but->selsta) > 0) {
3429 changed = ui_textedit_delete_selection(but, text_edit);
3430 }
3431 }
3432 }
3433
3434 return changed;
3435}
3436
3437#ifdef WITH_INPUT_IME
3438/* Enable IME, and setup #uiBut IME data. */
3439static void ui_textedit_ime_begin(wmWindow *win, uiBut * /*but*/)
3440{
3441 /* XXX Is this really needed? */
3442 int x, y;
3443
3444 BLI_assert(win->runtime->ime_data == nullptr);
3445
3446 /* enable IME and position to cursor, it's a trick */
3447 x = win->eventstate->xy[0];
3448 /* flip y and move down a bit, prevent the IME panel cover the edit button */
3449 y = win->eventstate->xy[1] - 12;
3450
3451 wm_window_IME_begin(win, x, y, 0, 0, true);
3452}
3453
3454/* Disable IME, and clear #uiBut IME data. */
3455static void ui_textedit_ime_end(wmWindow *win, uiBut * /*but*/)
3456{
3457 wm_window_IME_end(win);
3458}
3459
3460void ui_but_ime_reposition(uiBut *but, int x, int y, bool complete)
3461{
3462 BLI_assert(but->active || but->semi_modal_state);
3464
3465 ui_region_to_window(data->region, &x, &y);
3466 wm_window_IME_begin(data->window, x, y - 4, 0, 0, complete);
3467}
3468
3469const wmIMEData *ui_but_ime_data_get(uiBut *but)
3470{
3472
3473 if (data && data->window && data->window->runtime->ime_data_is_composing) {
3474 return data->window->runtime->ime_data;
3475 }
3476 return nullptr;
3477}
3478#endif /* WITH_INPUT_IME */
3479
3481{
3482 uiTextEdit &text_edit = data->text_edit;
3483 wmWindow *win = data->window;
3484 const bool is_num_but = ELEM(but->type, ButType::Num, ButType::NumSlider);
3485 bool no_zero_strip = false;
3486
3487 MEM_SAFE_FREE(text_edit.edit_string);
3488
3490
3491#if defined(__APPLE__)
3492 const int ctrl_icon = ICON_KEY_COMMAND;
3493#else
3494 const int ctrl_icon = ICON_EVENT_CTRL;
3495#endif
3496
3497 status.item(IFACE_("Confirm"), ICON_EVENT_RETURN);
3498 status.item(IFACE_("Cancel"), ICON_EVENT_ESC);
3499
3500 if (!is_num_but) {
3501 status.item(IFACE_("Select All"), ctrl_icon, ICON_EVENT_A);
3502 status.item(IFACE_("Copy"), ctrl_icon, ICON_EVENT_C);
3503 status.item(IFACE_("Paste"), ctrl_icon, ICON_EVENT_V);
3504 }
3505
3506 if (but->autocomplete_func || data->searchbox) {
3507 status.item(IFACE_("Autocomplete"), ICON_EVENT_TAB);
3508 }
3509
3510#ifdef USE_DRAG_MULTINUM
3511 /* this can happen from multi-drag */
3512 if (data->applied_interactive) {
3513 /* remove any small changes so canceling edit doesn't restore invalid value: #40538 */
3514 data->cancel = true;
3515 ui_apply_but(C, but->block, but, data, true);
3516 data->cancel = false;
3517
3518 data->applied_interactive = false;
3519 }
3520#endif
3521
3522#ifdef USE_ALLSELECT
3523 if (is_num_but) {
3524 if (IS_ALLSELECT_EVENT(win->eventstate)) {
3525 data->select_others.is_enabled = true;
3526 data->select_others.is_copy = true;
3527 }
3528 }
3529#endif
3530
3531 /* retrieve string */
3533 if (text_edit.max_string_size != 0) {
3534 text_edit.edit_string = MEM_calloc_arrayN<char>(text_edit.max_string_size, "textedit str");
3535 /* We do not want to truncate precision to default here, it's nice to show value,
3536 * not to edit it - way too much precision is lost then. */
3538 text_edit.edit_string,
3539 text_edit.max_string_size,
3541 true,
3542 &no_zero_strip);
3543 }
3544 else {
3545 text_edit.is_str_dynamic = true;
3546 text_edit.edit_string = ui_but_string_get_dynamic(but, &text_edit.max_string_size);
3547 }
3548
3549 if (ui_but_is_float(but) && !ui_but_is_unit(but) &&
3550 !ui_but_anim_expression_get(but, nullptr, 0) && !no_zero_strip)
3551 {
3552 BLI_str_rstrip_float_zero(text_edit.edit_string, '\0');
3553 }
3554
3555 if (is_num_but) {
3556 BLI_assert(text_edit.is_str_dynamic == false);
3558
3560 }
3561
3562 /* won't change from now on */
3563 const int len = strlen(text_edit.edit_string);
3564
3565 text_edit.original_string = BLI_strdupn(text_edit.edit_string, len);
3566 text_edit.sel_pos_init = 0;
3567
3568 /* set cursor pos to the end of the text */
3569 but->editstr = text_edit.edit_string;
3570 but->pos = len;
3571 if (bool(but->flag2 & UI_BUT2_ACTIVATE_ON_INIT_NO_SELECT)) {
3572 but->selsta = len;
3573 }
3574 else {
3575 but->selsta = 0;
3576 }
3577 but->selend = len;
3578
3579 /* Initialize undo history tracking. */
3581 ui_textedit_undo_push(text_edit.undo_stack_text, but->editstr, but->pos);
3582
3583 /* optional searchbox */
3584 if (but->type == ButType::SearchMenu) {
3585 uiButSearch *search_but = (uiButSearch *)but;
3586
3587 data->searchbox = search_but->popup_create_fn(C, data->region, search_but);
3588 ui_searchbox_update(C, data->searchbox, but, true); /* true = reset */
3589 }
3590
3591 /* reset alert flag (avoid confusion, will refresh on exit) */
3592 but->flag &= ~UI_BUT_REDALERT;
3593
3594 ui_but_update(but);
3595
3596 /* Make sure the edited button is in view. */
3597 if (data->searchbox) {
3598 /* Popup blocks don't support moving after creation, so don't change the view for them. */
3599 }
3601 /* Layout isn't resolved yet (may happen when activating while drawing through
3602 * #UI_but_active_only()), so can't move it into view yet. This causes
3603 * #ui_but_update_view_for_active() to run after the layout is resolved. */
3604 but->changed = true;
3605 }
3606 else if ((but->block->flag & UI_BLOCK_CLIP_EVENTS) == 0) {
3607 /* Blocks with UI_BLOCK_CLIP_EVENTS are overlapping their region, so scrolling
3608 * that region to ensure it is in view can't work and causes issues. #97530 */
3609 UI_but_ensure_in_view(C, data->region, but);
3610 }
3611
3613
3614 /* Temporarily turn off window auto-focus on platforms that support it. */
3615 GHOST_SetAutoFocus(false);
3616
3617#ifdef WITH_INPUT_IME
3618 if (!is_num_but) {
3619 ui_textedit_ime_begin(win, but);
3620 }
3621#endif
3622}
3623
3625{
3626 uiTextEdit &text_edit = data->text_edit;
3627 wmWindow *win = data->window;
3628
3629 ED_workspace_status_text(C, nullptr);
3630
3631 if (but) {
3632 if (UI_but_is_utf8(but)) {
3633 const int strip = BLI_str_utf8_invalid_strip(but->editstr, strlen(but->editstr));
3634 /* Strip non-UTF8 characters unless buttons support this.
3635 * This should never happen as all text input should be valid UTF8,
3636 * there is a small chance existing data contains invalid sequences.
3637 * This could check could be made into an assertion if `but->editstr`
3638 * is valid UTF8 when #ui_textedit_begin assigns the string. */
3639 if (strip) {
3640 CLOG_INFO_NOCHECK(&LOG, "%s: invalid utf8 - stripped chars %d", __func__, strip);
3641 }
3642 }
3643
3644 if (data->searchbox) {
3645 if (data->cancel == false) {
3647 uiButSearch *but_search = (uiButSearch *)but;
3648
3649 if ((ui_searchbox_apply(but, data->searchbox) == false) &&
3650 (ui_searchbox_find_index(data->searchbox, but->editstr) == -1) &&
3651 !but_search->results_are_suggestions)
3652 {
3653
3654 if (but->flag & UI_BUT_VALUE_CLEAR) {
3655 /* It is valid for _VALUE_CLEAR flavor to have no active element
3656 * (it's a valid way to unlink). */
3657 but->editstr[0] = '\0';
3658 }
3659 data->cancel = true;
3660
3661 /* ensure menu (popup) too is closed! */
3662 data->escapecancel = true;
3663
3664 WM_global_reportf(RPT_ERROR, "Failed to find '%s'", but->editstr);
3666 }
3667 }
3668
3669 ui_searchbox_free(C, data->searchbox);
3670 data->searchbox = nullptr;
3671 }
3672
3673 but->editstr = nullptr;
3674 but->pos = -1;
3675 }
3676
3678
3679 /* Turn back on the auto-focusing of windows. */
3680 GHOST_SetAutoFocus(true);
3681
3682 /* Free text undo history text blocks. */
3684 text_edit.undo_stack_text = nullptr;
3685
3686#ifdef WITH_INPUT_IME
3687 /* See #wm_window_IME_end code-comments for details. */
3688# ifdef __APPLE__
3689 if (win->runtime->ime_data)
3690# endif
3691 {
3692 ui_textedit_ime_end(win, but);
3693 }
3694#endif
3695}
3696
3698{
3699 /* Label and round-box can overlap real buttons (backdrops...). */
3700 if (ELEM(actbut->type,
3706 {
3707 return;
3708 }
3709
3710 for (int64_t i = block->but_index(actbut) + 1; i < block->buttons.size(); i++) {
3711 uiBut *but = block->buttons[i].get();
3712 if (ui_but_is_editable_as_text(but)) {
3713 if (!(but->flag & (UI_BUT_DISABLED | UI_HIDDEN))) {
3714 data->postbut = but;
3716 return;
3717 }
3718 }
3719 }
3720 for (const std::unique_ptr<uiBut> &but : block->buttons) {
3721 if (but.get() == actbut) {
3722 break;
3723 }
3724 if (ui_but_is_editable_as_text(but.get())) {
3725 if (!(but->flag & (UI_BUT_DISABLED | UI_HIDDEN))) {
3726 data->postbut = but.get();
3728 return;
3729 }
3730 }
3731 }
3732}
3733
3735{
3736 /* Label and round-box can overlap real buttons (backdrops...). */
3737 if (ELEM(actbut->type,
3743 {
3744 return;
3745 }
3746
3747 for (int i = block->but_index(actbut) - 1; i >= 0; i--) {
3748 uiBut *but = block->buttons[i].get();
3749 if (ui_but_is_editable_as_text(but)) {
3750 if (!(but->flag & (UI_BUT_DISABLED | UI_HIDDEN))) {
3751 data->postbut = but;
3753 return;
3754 }
3755 }
3756 }
3757 for (int i = block->buttons.size() - 1; i >= 0; i--) {
3758 uiBut *but = block->buttons[i].get();
3759 if (but == actbut) {
3760 break;
3761 }
3762 if (ui_but_is_editable_as_text(but)) {
3763 if (!(but->flag & (UI_BUT_DISABLED | UI_HIDDEN))) {
3764 data->postbut = but;
3766 return;
3767 }
3768 }
3769 }
3770}
3771
3776{
3777#if defined(__APPLE__)
3778 if (event->modifier & KM_OSKEY) {
3779 return STRCUR_JUMP_ALL;
3780 }
3781 if (event->modifier & KM_ALT) {
3782 return STRCUR_JUMP_DELIM;
3783 }
3784#else
3785 if (event->modifier & KM_CTRL) {
3786 return STRCUR_JUMP_DELIM;
3787 }
3788#endif
3789 return STRCUR_JUMP_NONE;
3790}
3791
3793 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
3794{
3795 uiTextEdit &text_edit = data->text_edit;
3796 int retval = WM_UI_HANDLER_CONTINUE;
3797 bool changed = false, inbox = false, update = false, skip_undo_push = false;
3798
3799#ifdef WITH_INPUT_IME
3800 wmWindow *win = CTX_wm_window(C);
3801 const wmIMEData *ime_data = win->runtime->ime_data;
3802 const bool is_ime_composing = ime_data && win->runtime->ime_data_is_composing;
3803#else
3804 const bool is_ime_composing = false;
3805#endif
3806
3807 switch (event->type) {
3808 case MOUSEMOVE:
3809 case MOUSEPAN:
3810 if (data->searchbox) {
3811#ifdef USE_KEYNAV_LIMIT
3812 if ((event->type == MOUSEMOVE) &&
3813 ui_mouse_motion_keynav_test(&data->searchbox_keynav_state, event))
3814 {
3815 /* pass */
3816 }
3817 else {
3818 ui_searchbox_event(C, data->searchbox, but, data->region, event);
3819 }
3820#else
3821 ui_searchbox_event(C, data->searchbox, but, data->region, event);
3822#endif
3823 }
3825
3826 break;
3827 case RIGHTMOUSE:
3828 case EVT_ESCKEY:
3829 /* Don't consume cancel events (would usually end text editing), let menu code handle it. */
3830 if (data->is_semi_modal) {
3831 break;
3832 }
3833 if (event->val == KM_PRESS) {
3834 /* Support search context menu. */
3835 if (event->type == RIGHTMOUSE) {
3836 if (data->searchbox) {
3837 if (ui_searchbox_event(C, data->searchbox, but, data->region, event)) {
3838 /* Only break if the event was handled. */
3839 break;
3840 }
3841 }
3842 }
3843
3844#ifdef WITH_INPUT_IME
3845 /* skips button handling since it is not wanted */
3846 if (is_ime_composing) {
3847 break;
3848 }
3849#endif
3850 data->cancel = true;
3851 data->escapecancel = true;
3853 retval = WM_UI_HANDLER_BREAK;
3854 }
3855 break;
3856 case LEFTMOUSE: {
3857 /* Allow clicks on extra icons while editing. */
3858 if (ui_do_but_extra_operator_icon(C, but, data, event)) {
3859 break;
3860 }
3861
3862 const bool had_selection = but->selsta != but->selend;
3863
3864 /* exit on LMB only on RELEASE for searchbox, to mimic other popups,
3865 * and allow multiple menu levels */
3866 if (data->searchbox) {
3867 inbox = ui_searchbox_inside(data->searchbox, event->xy);
3868 }
3869
3870 bool is_press_in_button = false;
3871 if (ELEM(event->val, KM_PRESS, KM_DBL_CLICK)) {
3872 float mx = event->xy[0];
3873 float my = event->xy[1];
3874 ui_window_to_block_fl(data->region, block, &mx, &my);
3875
3876 if (ui_but_contains_pt(but, mx, my)) {
3877 is_press_in_button = true;
3878 }
3879 }
3880
3881 /* for double click: we do a press again for when you first click on button
3882 * (selects all text, no cursor pos) */
3883 if (ELEM(event->val, KM_PRESS, KM_DBL_CLICK)) {
3884 if (is_press_in_button) {
3885 ui_textedit_set_cursor_pos(but, data->region, event->xy[0]);
3886 but->selsta = but->selend = but->pos;
3887 text_edit.sel_pos_init = but->pos;
3888
3890 retval = WM_UI_HANDLER_BREAK;
3891 }
3892 else if (inbox == false && !data->is_semi_modal) {
3893 /* if searchbox, click outside will cancel */
3894 if (data->searchbox) {
3895 data->cancel = data->escapecancel = true;
3896 }
3898 retval = WM_UI_HANDLER_BREAK;
3899 }
3900 }
3901
3902 /* only select a word in button if there was no selection before */
3903 if (event->val == KM_DBL_CLICK && had_selection == false) {
3904 if (is_press_in_button) {
3905 const int str_len = strlen(text_edit.edit_string);
3906 /* This may not be necessary, additional check to ensure `pos` is never out of range,
3907 * since negative values aren't acceptable, see: #113154. */
3908 CLAMP(but->pos, 0, str_len);
3909
3910 int selsta, selend;
3912 text_edit.edit_string, str_len, but->pos, &selsta, &selend);
3913 but->pos = short(selend);
3914 but->selsta = short(selsta);
3915 but->selend = short(selend);
3916 /* Anchor selection to the left side unless the last word. */
3917 text_edit.sel_pos_init = ((selend == str_len) && (selsta != 0)) ? selend : selsta;
3918 retval = WM_UI_HANDLER_BREAK;
3919 changed = true;
3920 }
3921 }
3922 else if (inbox && !data->is_semi_modal) {
3923 /* if we allow activation on key press,
3924 * it gives problems launching operators #35713. */
3925 if (event->val == KM_RELEASE) {
3927 retval = WM_UI_HANDLER_BREAK;
3928 }
3929 }
3930 break;
3931 }
3932 default: {
3933 break;
3934 }
3935 }
3936
3937 if (event->val == KM_PRESS && !is_ime_composing) {
3938 switch (event->type) {
3939 case EVT_VKEY:
3940 case EVT_XKEY:
3941 case EVT_CKEY:
3942#if defined(__APPLE__)
3943 if (ELEM(event->modifier, KM_OSKEY, KM_CTRL))
3944#else
3945 if (event->modifier == KM_CTRL)
3946#endif
3947 {
3948 if (event->type == EVT_VKEY) {
3949 changed = ui_textedit_copypaste(but, text_edit, UI_TEXTEDIT_PASTE);
3950 }
3951 else if (event->type == EVT_CKEY) {
3952 changed = ui_textedit_copypaste(but, text_edit, UI_TEXTEDIT_COPY);
3953 }
3954 else if (event->type == EVT_XKEY) {
3955 changed = ui_textedit_copypaste(but, text_edit, UI_TEXTEDIT_CUT);
3956 }
3957
3958 retval = WM_UI_HANDLER_BREAK;
3959 }
3960 break;
3961 case EVT_RIGHTARROWKEY:
3962 case EVT_LEFTARROWKEY: {
3963 const eStrCursorJumpDirection direction = (event->type == EVT_RIGHTARROWKEY) ?
3967 ui_textedit_move(but, text_edit, direction, event->modifier & KM_SHIFT, jump);
3968 retval = WM_UI_HANDLER_BREAK;
3969 break;
3970 }
3971 case WHEELDOWNMOUSE:
3972 case EVT_DOWNARROWKEY:
3973 if (data->searchbox) {
3974#ifdef USE_KEYNAV_LIMIT
3975 ui_mouse_motion_keynav_init(&data->searchbox_keynav_state, event);
3976#endif
3977 ui_searchbox_event(C, data->searchbox, but, data->region, event);
3978 break;
3979 }
3980 if (event->type == WHEELDOWNMOUSE) {
3981 break;
3982 }
3984 case EVT_ENDKEY:
3986 but, text_edit, STRCUR_DIR_NEXT, event->modifier & KM_SHIFT, STRCUR_JUMP_ALL);
3987 retval = WM_UI_HANDLER_BREAK;
3988 break;
3989 case WHEELUPMOUSE:
3990 case EVT_UPARROWKEY:
3991 if (data->searchbox) {
3992#ifdef USE_KEYNAV_LIMIT
3993 ui_mouse_motion_keynav_init(&data->searchbox_keynav_state, event);
3994#endif
3995 ui_searchbox_event(C, data->searchbox, but, data->region, event);
3996 break;
3997 }
3998 if (event->type == WHEELUPMOUSE) {
3999 break;
4000 }
4002 case EVT_HOMEKEY:
4004 but, text_edit, STRCUR_DIR_PREV, event->modifier & KM_SHIFT, STRCUR_JUMP_ALL);
4005 retval = WM_UI_HANDLER_BREAK;
4006 break;
4007 case EVT_PADENTER:
4008 case EVT_RETKEY:
4010 retval = WM_UI_HANDLER_BREAK;
4011 break;
4012 case EVT_DELKEY:
4013 case EVT_BACKSPACEKEY: {
4014 const eStrCursorJumpDirection direction = (event->type == EVT_DELKEY) ? STRCUR_DIR_NEXT :
4017 changed = ui_textedit_delete(but, text_edit, direction, jump);
4018 retval = WM_UI_HANDLER_BREAK;
4019 break;
4020 }
4021
4022 case EVT_AKEY:
4023
4024 /* Ctrl-A: Select all. */
4025#if defined(__APPLE__)
4026 /* OSX uses Command-A system-wide, so add it. */
4027 if (ELEM(event->modifier, KM_OSKEY, KM_CTRL))
4028#else
4029 if (event->modifier == KM_CTRL)
4030#endif
4031 {
4032 ui_textedit_move(but, text_edit, STRCUR_DIR_PREV, false, STRCUR_JUMP_ALL);
4033 ui_textedit_move(but, text_edit, STRCUR_DIR_NEXT, true, STRCUR_JUMP_ALL);
4034 retval = WM_UI_HANDLER_BREAK;
4035 }
4036 break;
4037
4038 case EVT_TABKEY:
4039 /* There is a key conflict here, we can't tab with auto-complete. */
4040 if (but->autocomplete_func || data->searchbox) {
4041 const int autocomplete = ui_textedit_autocomplete(C, but, data);
4042 changed = autocomplete != AUTOCOMPLETE_NO_MATCH;
4043
4044 if (autocomplete == AUTOCOMPLETE_FULL_MATCH) {
4046 }
4047 }
4048 else if ((event->modifier & ~KM_SHIFT) == 0) {
4049 /* Use standard keys for cycling through buttons Tab, Shift-Tab to reverse. */
4050 if (event->modifier & KM_SHIFT) {
4051 ui_textedit_prev_but(block, but, data);
4052 }
4053 else {
4054 ui_textedit_next_but(block, but, data);
4055 }
4057 }
4058 retval = WM_UI_HANDLER_BREAK;
4059 break;
4060 case EVT_ZKEY: {
4061 /* Ctrl-Z or Ctrl-Shift-Z: Undo/Redo (allowing for OS-Key on Apple). */
4062
4063 const bool is_redo = (event->modifier & KM_SHIFT);
4064 if (
4065#if defined(__APPLE__)
4066 ((event->modifier & KM_OSKEY) && ((event->modifier & (KM_ALT | KM_CTRL)) == 0)) ||
4067#endif
4068 ((event->modifier & KM_CTRL) && ((event->modifier & (KM_ALT | KM_OSKEY)) == 0)))
4069 {
4070 int undo_pos;
4071 const char *undo_str = ui_textedit_undo(
4072 text_edit.undo_stack_text, is_redo ? 1 : -1, &undo_pos);
4073 if (undo_str != nullptr) {
4074 ui_textedit_string_set(but, text_edit, undo_str);
4075
4076 /* Set the cursor & clear selection. */
4077 but->pos = undo_pos;
4078 but->selsta = but->pos;
4079 but->selend = but->pos;
4080 changed = true;
4081 }
4082 retval = WM_UI_HANDLER_BREAK;
4083 skip_undo_push = true;
4084 }
4085 break;
4086 }
4087 default: {
4088 break;
4089 }
4090 }
4091
4092 if ((event->utf8_buf[0]) && (retval == WM_UI_HANDLER_CONTINUE)
4093#ifdef WITH_INPUT_IME
4094 && !is_ime_composing && !WM_event_is_ime_switch(event)
4095#endif
4096 )
4097 {
4098 char utf8_buf_override[2] = {'\0', '\0'};
4099 const char *utf8_buf = event->utf8_buf;
4100
4101 /* Exception that's useful for number buttons, some keyboard
4102 * numpads have a comma instead of a period. */
4103 if (ELEM(but->type, ButType::Num, ButType::NumSlider)) { /* Could use `data->min`. */
4104 if ((event->type == EVT_PADPERIOD) && (utf8_buf[0] == ',')) {
4105 utf8_buf_override[0] = '.';
4106 utf8_buf = utf8_buf_override;
4107 }
4108 }
4109
4110 if (utf8_buf[0]) {
4111 const int utf8_buf_len = BLI_str_utf8_size_or_error(utf8_buf);
4112 BLI_assert(utf8_buf_len != -1);
4113 changed = ui_textedit_insert_buf(but, text_edit, utf8_buf, utf8_buf_len);
4114 }
4115
4116 retval = WM_UI_HANDLER_BREAK;
4117 }
4118 /* textbutton with this flag: do live update (e.g. for search buttons) */
4119 if (but->flag & UI_BUT_TEXTEDIT_UPDATE) {
4120 update = true;
4121 }
4122 }
4123
4124#ifdef WITH_INPUT_IME
4125 if (event->type == WM_IME_COMPOSITE_START) {
4126 changed = true;
4127 if (but->selend > but->selsta) {
4128 ui_textedit_delete_selection(but, text_edit);
4129 }
4130 }
4131 else if (event->type == WM_IME_COMPOSITE_EVENT) {
4132 changed = true;
4133 if (ime_data->result.size()) {
4135 STREQ(ime_data->result.c_str(), "\xE3\x80\x82"))
4136 {
4137 /* Convert Ideographic Full Stop (U+3002) to decimal point when entering numbers. */
4138 ui_textedit_insert_ascii(but, data, '.');
4139 }
4140 else {
4141 ui_textedit_insert_buf(but, text_edit, ime_data->result.c_str(), ime_data->result.size());
4142 }
4143 }
4144 }
4145 else if (event->type == WM_IME_COMPOSITE_END) {
4146 changed = true;
4147 }
4148#endif
4149
4150 if (changed) {
4151 /* The undo stack may be nullptr if an event exits editing. */
4152 if ((skip_undo_push == false) && (text_edit.undo_stack_text != nullptr)) {
4153 ui_textedit_undo_push(text_edit.undo_stack_text, text_edit.edit_string, but->pos);
4154 }
4155
4156 /* only do live update when but flag request it (UI_BUT_TEXTEDIT_UPDATE). */
4157 if (update && data->interactive) {
4158 ui_apply_but(C, block, but, data, true);
4159 }
4160 else {
4162 }
4163 but->changed = true;
4164
4165 if (data->searchbox) {
4166 ui_searchbox_update(C, data->searchbox, but, true); /* true = reset */
4167 }
4168 }
4169
4170 if (changed || (retval == WM_UI_HANDLER_BREAK)) {
4171 ED_region_tag_redraw(data->region);
4172 if (!data->searchbox) {
4173 /* In case of popup regions, tag for popup refreshing too (contents may have changed). Not
4174 * done for search-boxes, since they have their own update handling. */
4176 }
4177 }
4178
4179 /* Swallow all events unless semi-modal handling is requested. */
4180 return data->is_semi_modal ? retval : WM_UI_HANDLER_BREAK;
4181}
4182
4184 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
4185{
4186 int retval = WM_UI_HANDLER_CONTINUE;
4187
4188 switch (event->type) {
4189 case MOUSEMOVE: {
4190 int mx = event->xy[0];
4191 int my = event->xy[1];
4192 ui_window_to_block(data->region, block, &mx, &my);
4193
4194 ui_textedit_set_cursor_select(but, data, event->xy[0]);
4195 retval = WM_UI_HANDLER_BREAK;
4196 break;
4197 }
4198 case LEFTMOUSE:
4199 if (event->val == KM_RELEASE) {
4201 }
4202 retval = WM_UI_HANDLER_BREAK;
4203 break;
4204 default: {
4205 break;
4206 }
4207 }
4208
4209 if (retval == WM_UI_HANDLER_BREAK) {
4210 ui_but_update(but);
4211 ED_region_tag_redraw(data->region);
4212 }
4213
4214 return retval;
4215}
4216
4218
4219/* -------------------------------------------------------------------- */
4222
4224{
4225 data->startvalue = ui_but_value_get(but);
4226 data->origvalue = data->startvalue;
4227 data->value = data->origvalue;
4228}
4229
4231{
4232 if (but->type == ButType::Curve) {
4233 uiButCurveMapping *but_cumap = (uiButCurveMapping *)but;
4234 but_cumap->edit_cumap = (CurveMapping *)but->poin;
4235 }
4236 else if (but->type == ButType::CurveProfile) {
4237 uiButCurveProfile *but_profile = (uiButCurveProfile *)but;
4238 but_profile->edit_profile = (CurveProfile *)but->poin;
4239 }
4240 else if (but->type == ButType::ColorBand) {
4241 uiButColorBand *but_coba = (uiButColorBand *)but;
4242 data->coba = (ColorBand *)but->poin;
4243 but_coba->edit_coba = data->coba;
4244 }
4246 {
4247 ui_but_v3_get(but, data->origvec);
4248 copy_v3_v3(data->vec, data->origvec);
4249 but->editvec = data->vec;
4250 }
4251 else {
4253 but->editval = &data->value;
4254
4255 float softmin = but->softmin;
4256 float softmax = but->softmax;
4257 float softrange = softmax - softmin;
4258 const PropertyScaleType scale_type = ui_but_scale_type(but);
4259
4260 float log_min = (scale_type == PROP_SCALE_LOG) ? max_ff(softmin, UI_PROP_SCALE_LOG_MIN) : 0.0f;
4261
4262 if ((but->type == ButType::Num) && (ui_but_is_cursor_warp(but) == false)) {
4263 uiButNumber *number_but = (uiButNumber *)but;
4264
4265 if (scale_type == PROP_SCALE_LOG) {
4266 log_min = max_ff(log_min, powf(10, -number_but->precision) * 0.5f);
4267 }
4268 /* Use a minimum so we have a predictable range,
4269 * otherwise some float buttons get a large range. */
4270 const float value_step_float_min = 0.1f;
4271 const bool is_float = ui_but_is_float(but);
4272 const double value_step = is_float ?
4273 double(number_but->step_size * UI_PRECISION_FLOAT_SCALE) :
4274 int(number_but->step_size);
4275 const float drag_map_softrange_max = UI_DRAG_MAP_SOFT_RANGE_PIXEL_MAX * UI_SCALE_FAC;
4276 const float softrange_max = min_ff(
4277 softrange,
4278 2 * (is_float ? min_ff(value_step, value_step_float_min) *
4279 (drag_map_softrange_max / value_step_float_min) :
4280 drag_map_softrange_max));
4281
4282 if (softrange > softrange_max) {
4283 /* Center around the value, keeping in the real soft min/max range. */
4284 softmin = data->origvalue - (softrange_max / 2);
4285 softmax = data->origvalue + (softrange_max / 2);
4286 if (!isfinite(softmin)) {
4287 softmin = (data->origvalue > 0.0f ? FLT_MAX : -FLT_MAX);
4288 }
4289 if (!isfinite(softmax)) {
4290 softmax = (data->origvalue > 0.0f ? FLT_MAX : -FLT_MAX);
4291 }
4292
4293 if (softmin < but->softmin) {
4294 softmin = but->softmin;
4295 softmax = softmin + softrange_max;
4296 }
4297 else if (softmax > but->softmax) {
4298 softmax = but->softmax;
4299 softmin = softmax - softrange_max;
4300 }
4301
4302 /* Can happen at extreme values. */
4303 if (UNLIKELY(softmin == softmax)) {
4304 if (data->origvalue > 0.0) {
4305 softmin = nextafterf(softmin, -FLT_MAX);
4306 }
4307 else {
4308 softmax = nextafterf(softmax, FLT_MAX);
4309 }
4310 }
4311
4312 softrange = softmax - softmin;
4313 }
4314 }
4315
4316 if (softrange == 0.0f) {
4317 data->dragfstart = 0.0f;
4318 }
4319 else {
4320 switch (scale_type) {
4321 case PROP_SCALE_LINEAR: {
4322 data->dragfstart = (float(data->value) - softmin) / softrange;
4323 break;
4324 }
4325 case PROP_SCALE_LOG: {
4326 BLI_assert(log_min != 0.0f);
4327 const float base = softmax / log_min;
4328 data->dragfstart = logf(float(data->value) / log_min) / logf(base);
4329 break;
4330 }
4331 case PROP_SCALE_CUBIC: {
4332 const float cubic_min = cube_f(softmin);
4333 const float cubic_max = cube_f(softmax);
4334 const float cubic_range = cubic_max - cubic_min;
4335 const float f = (float(data->value) - softmin) * cubic_range / softrange + cubic_min;
4336 data->dragfstart = (cbrtf(f) - softmin) / softrange;
4337 break;
4338 }
4339 }
4340 }
4341 data->dragf = data->dragfstart;
4342
4343 data->drag_map_soft_min = softmin;
4344 data->drag_map_soft_max = softmax;
4345 }
4346
4347 data->dragchange = false;
4348 data->draglock = true;
4349}
4350
4352{
4353 but->editval = nullptr;
4354 but->editvec = nullptr;
4355 if (but->type == ButType::ColorBand) {
4356 uiButColorBand *but_coba = (uiButColorBand *)but;
4357 but_coba->edit_coba = nullptr;
4358 }
4359 else if (but->type == ButType::Curve) {
4360 uiButCurveMapping *but_cumap = (uiButCurveMapping *)but;
4361 but_cumap->edit_cumap = nullptr;
4362 }
4363 else if (but->type == ButType::CurveProfile) {
4364 uiButCurveProfile *but_profile = (uiButCurveProfile *)but;
4365 but_profile->edit_profile = nullptr;
4366 }
4367 data->dragstartx = 0;
4368 data->draglastx = 0;
4369 data->dragchange = false;
4370 data->dragcbd = nullptr;
4371 data->dragsel = 0;
4372}
4373
4375{
4376 if (data->interactive) {
4377 ui_apply_but(C, block, but, data, true);
4378 }
4379 else {
4380 ui_but_update(but);
4381 }
4382
4383 ED_region_tag_redraw(data->region);
4384}
4385
4387{
4388 but->active->apply_through_extra_icon = true;
4389
4390 if (but->active->interactive) {
4391 ui_apply_but(C, but->block, but, but->active, true);
4392 }
4395 op_icon->optype_params->optype,
4396 op_icon->optype_params->opcontext,
4397 op_icon->optype_params->opptr,
4398 nullptr,
4399 "");
4400
4401 /* Force recreation of extra operator icons (pseudo update). */
4403
4405}
4406
4408
4409/* -------------------------------------------------------------------- */
4412
4414{
4415 uiBlockCreateFunc func = nullptr;
4416 uiBlockHandleCreateFunc handlefunc = nullptr;
4417 uiMenuCreateFunc menufunc = nullptr;
4418 uiMenuCreateFunc popoverfunc = nullptr;
4419 /* The checks for the panel type being null are for exceptional cases where script
4420 * authors intentionally unregister built-in panels for example.
4421 * While this should only ever happen rarely, it shouldn't crash, see #144716. */
4422 PanelType *popover_panel_type = nullptr;
4423 void *arg = nullptr;
4424
4425 if (but->type != ButType::Pulldown) {
4426 /* Clear the status bar. */
4428 status.item(" ", ICON_NONE);
4429 }
4430
4431 switch (but->type) {
4432 case ButType::Block:
4433 case ButType::Pulldown:
4434 if (but->menu_create_func) {
4435 menufunc = but->menu_create_func;
4436 arg = but->poin;
4437 }
4438 else {
4439 func = but->block_create_func;
4440 arg = but->poin ? but->poin : but->func_argN;
4441 }
4442 break;
4443 case ButType::Menu:
4445 if (ui_but_menu_draw_as_popover(but)) {
4446 const char *idname = static_cast<const char *>(but->func_argN);
4447 popover_panel_type = WM_paneltype_find(idname, false);
4448 }
4449
4450 if (popover_panel_type) {
4451 popoverfunc = but->menu_create_func;
4452 }
4453 else {
4454 menufunc = but->menu_create_func;
4455 arg = but->poin;
4456 }
4457 break;
4458 case ButType::Popover:
4460 popoverfunc = but->menu_create_func;
4461 popover_panel_type = reinterpret_cast<PanelType *>(but->poin);
4462 break;
4463 case ButType::Color:
4464 ui_but_v3_get(but, data->origvec);
4465 copy_v3_v3(data->vec, data->origvec);
4466 but->editvec = data->vec;
4467
4468 if (ui_but_menu_draw_as_popover(but)) {
4469 const char *idname = static_cast<const char *>(but->func_argN);
4470 popover_panel_type = WM_paneltype_find(idname, false);
4471 }
4472
4473 if (popover_panel_type) {
4474 popoverfunc = but->menu_create_func;
4475 }
4476 else {
4477 handlefunc = ui_block_func_COLOR;
4478 }
4479 arg = but;
4480 break;
4481
4482 /* quiet warnings for unhandled types */
4483 default:
4484 break;
4485 }
4486
4487 if (func || handlefunc) {
4489 C, data->region, but, func, handlefunc, arg, nullptr, false);
4490 if (but->block->handle) {
4491 data->menu->popup = but->block->handle->popup;
4492 }
4493 }
4494 else if (menufunc) {
4495 data->menu = ui_popup_menu_create(C, data->region, but, menufunc, arg);
4496 if (MenuType *mt = UI_but_menutype_get(but)) {
4497 STRNCPY_UTF8(data->menu->menu_idname, mt->idname);
4498 }
4499 if (but->block->handle) {
4500 data->menu->popup = but->block->handle->popup;
4501 }
4502 }
4503 else if (popoverfunc) {
4504 data->menu = ui_popover_panel_create(C, data->region, but, popoverfunc, popover_panel_type);
4505 if (but->block->handle) {
4506 data->menu->popup = but->block->handle->popup;
4507 }
4508 }
4509
4510#ifdef USE_ALLSELECT
4511 {
4512 if (IS_ALLSELECT_EVENT(data->window->eventstate)) {
4513 data->select_others.is_enabled = true;
4514 }
4515 }
4516#endif
4517
4518 /* Force new region handler to run, in case that needs to activate some state (e.g. to handle
4519 * #UI_BUT2_FORCE_SEMI_MODAL_ACTIVE). */
4521
4522 /* this makes adjacent blocks auto open from now on */
4523 // if (but->block->auto_open == 0) {
4524 // but->block->auto_open = 1;
4525 //}
4526}
4527
4529{
4530 if (but) {
4531 but->editval = nullptr;
4532 but->editvec = nullptr;
4533
4535 }
4536
4537 ED_workspace_status_text(C, nullptr);
4538
4539 if (data->menu) {
4540 ui_popup_block_free(C, data->menu);
4541 data->menu = nullptr;
4542 }
4543}
4544
4546{
4548
4549 if (data && data->menu) {
4550 return data->menu->direction;
4551 }
4552
4553 return 0;
4554}
4555
4561 uiBut *but,
4563 const wmEvent *event,
4564 uiButtonActivateType activate_type)
4565{
4566 ARegion *region = data->region;
4567 uiBut *labelbut = ui_but_find_mouse_over_ex(region, event->xy, true, false, nullptr, nullptr);
4568
4569 if (labelbut && labelbut->type == ButType::Text && !(labelbut->flag & UI_BUT_DISABLED)) {
4570 /* Exit list-row. */
4571 data->cancel = true;
4572 button_activate_exit(C, but, data, false, false);
4573
4574 /* Activate the text button. */
4575 button_activate_init(C, region, labelbut, activate_type);
4576
4577 return labelbut;
4578 }
4579 return nullptr;
4580}
4581
4583
4584/* -------------------------------------------------------------------- */
4587
4589 ARegion *region,
4590 const wmEvent *event)
4591{
4593 return nullptr;
4594 }
4595
4596 int x = event->xy[0], y = event->xy[1];
4597 ui_window_to_block(region, but->block, &x, &y);
4598 if (!BLI_rctf_isect_pt(&but->rect, x, y)) {
4599 return nullptr;
4600 }
4601
4602 const float icon_size = 0.8f * BLI_rctf_size_y(&but->rect); /* ICON_SIZE_FROM_BUTRECT */
4603 float xmax = but->rect.xmax;
4604 /* Same as in 'widget_draw_extra_icons', icon padding from the right edge. */
4605 xmax -= 0.2 * icon_size;
4606
4607 /* Handle the padding space from the right edge as the last button. */
4608 if (x > xmax) {
4609 return static_cast<uiButExtraOpIcon *>(but->extra_op_icons.last);
4610 }
4611
4612 /* Inverse order, from right to left. */
4614 if ((x > (xmax - icon_size)) && x <= xmax) {
4615 return op_icon;
4616 }
4617 xmax -= icon_size;
4618 }
4619
4620 return nullptr;
4621}
4622
4624 uiBut *but,
4626 const wmEvent *event)
4627{
4628 uiButExtraOpIcon *op_icon = ui_but_extra_operator_icon_mouse_over_get(but, data->region, event);
4629
4630 if (!op_icon) {
4631 return false;
4632 }
4633
4634 /* Only act on release, avoids some glitches. */
4635 if (event->val != KM_RELEASE) {
4636 /* Still swallow events on the icon. */
4637 return true;
4638 }
4639
4640 ED_region_tag_redraw(data->region);
4642
4643 ui_but_extra_operator_icon_apply(C, but, op_icon);
4644 /* NOTE: 'but', 'data' may now be freed, don't access. */
4645
4646 return true;
4647}
4648
4651 const wmEvent *event)
4652{
4653 uiButExtraOpIcon *old_highlighted = nullptr;
4654
4655 /* Unset highlighting of all first. */
4657 if (op_icon->highlighted) {
4658 old_highlighted = op_icon;
4659 }
4660 op_icon->highlighted = false;
4661 }
4662
4663 uiButExtraOpIcon *hovered = ui_but_extra_operator_icon_mouse_over_get(but, data->region, event);
4664
4665 if (hovered) {
4666 hovered->highlighted = true;
4667 }
4668
4669 if (old_highlighted != hovered) {
4671 }
4672}
4673
4674#ifdef USE_DRAG_TOGGLE
4675/* Shared by any button that supports drag-toggle. */
4677 bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event, int *r_retval)
4678{
4679 if (data->state == BUTTON_STATE_HIGHLIGHT) {
4680 if (event->type == LEFTMOUSE && event->val == KM_PRESS && ui_but_is_drag_toggle(but)) {
4681 ui_apply_but(C, but->block, but, data, true);
4683 data->dragstartx = event->xy[0];
4684 data->dragstarty = event->xy[1];
4685 *r_retval = WM_UI_HANDLER_BREAK;
4686 return true;
4687 }
4688 }
4689 else if (data->state == BUTTON_STATE_WAIT_DRAG) {
4690 /* NOTE: the 'BUTTON_STATE_WAIT_DRAG' part of 'ui_do_but_EXIT' could be refactored into
4691 * its own function */
4692 data->applied = false;
4693 *r_retval = ui_do_but_EXIT(C, but, data, event);
4694 return true;
4695 }
4696 return false;
4697}
4698#endif /* USE_DRAG_TOGGLE */
4699
4700static int ui_do_but_BUT(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
4701{
4702#ifdef USE_DRAG_TOGGLE
4703 {
4704 int retval;
4705 if (ui_do_but_ANY_drag_toggle(C, but, data, event, &retval)) {
4706 return retval;
4707 }
4708 }
4709#endif
4710
4711 if (data->state == BUTTON_STATE_HIGHLIGHT) {
4712 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
4714 return WM_UI_HANDLER_BREAK;
4715 }
4716 if (event->type == LEFTMOUSE && event->val == KM_RELEASE && but->block->handle) {
4717 /* regular buttons will be 'UI_SELECT', menu items 'UI_HOVER' */
4718 if (!(but->flag & (UI_SELECT | UI_HOVER))) {
4719 data->cancel = true;
4720 }
4722 return WM_UI_HANDLER_BREAK;
4723 }
4724 if (ELEM(event->type, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) {
4726 return WM_UI_HANDLER_BREAK;
4727 }
4728 }
4729 else if (data->state == BUTTON_STATE_WAIT_RELEASE) {
4730 if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
4731 if (!(but->flag & UI_SELECT)) {
4732 data->cancel = true;
4733 }
4735 return WM_UI_HANDLER_BREAK;
4736 }
4737 }
4738
4740}
4741
4743 uiBut *but,
4745 const wmEvent *event)
4746{
4747 uiButHotkeyEvent *hotkey_but = (uiButHotkeyEvent *)but;
4749
4750 if (data->state == BUTTON_STATE_HIGHLIGHT) {
4752 (event->val == KM_PRESS))
4753 {
4754 but->drawstr.clear();
4755 hotkey_but->modifier_key = wmEventModifierFlag(0);
4757 return WM_UI_HANDLER_BREAK;
4758 }
4759 }
4760 else if (data->state == BUTTON_STATE_WAIT_KEY_EVENT) {
4761 if (ISMOUSE_MOTION(event->type)) {
4763 }
4764 if (event->type == EVT_UNKNOWNKEY) {
4765 WM_global_report(RPT_WARNING, "Unsupported key: Unknown");
4767 }
4768 if (event->type == EVT_CAPSLOCKKEY) {
4769 WM_global_report(RPT_WARNING, "Unsupported key: CapsLock");
4771 }
4772
4773 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
4774 /* only cancel if click outside the button */
4775 if (ui_but_contains_point_px(but, but->active->region, event->xy) == false) {
4776 data->cancel = true;
4777 /* Close the containing popup (if any). */
4778 data->escapecancel = true;
4780 return WM_UI_HANDLER_BREAK;
4781 }
4782 }
4783
4784 /* always set */
4785 hotkey_but->modifier_key = event->modifier;
4786
4787 ui_but_update(but);
4788 ED_region_tag_redraw(data->region);
4789
4790 if (event->val == KM_PRESS) {
4791 if (ISHOTKEY(event->type) && (event->type != EVT_ESCKEY)) {
4792 if (WM_key_event_string(event->type, false)[0]) {
4793 ui_but_value_set(but, event->type);
4794 }
4795 else {
4796 data->cancel = true;
4797 }
4798
4800 return WM_UI_HANDLER_BREAK;
4801 }
4802 if (event->type == EVT_ESCKEY) {
4803 if (event->val == KM_PRESS) {
4804 data->cancel = true;
4805 data->escapecancel = true;
4807 }
4808 }
4809 }
4810 }
4811
4813}
4814
4816 uiBut *but,
4818 const wmEvent *event)
4819{
4820 if (data->state == BUTTON_STATE_HIGHLIGHT) {
4821 if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) {
4823 return WM_UI_HANDLER_BREAK;
4824 }
4825 }
4826 else if (data->state == BUTTON_STATE_WAIT_KEY_EVENT) {
4827 if (ISMOUSE_MOTION(event->type)) {
4829 }
4830
4831 if (event->val == KM_PRESS) {
4832 if (WM_key_event_string(event->type, false)[0]) {
4833 ui_but_value_set(but, event->type);
4834 }
4835 else {
4836 data->cancel = true;
4837 }
4838
4840 }
4841 }
4842
4844}
4845
4846static int ui_do_but_TAB(
4847 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
4848{
4849 const bool is_property = (but->rnaprop != nullptr);
4850
4851#ifdef USE_DRAG_TOGGLE
4852 if (is_property) {
4853 int retval;
4854 if (ui_do_but_ANY_drag_toggle(C, but, data, event, &retval)) {
4855 return retval;
4856 }
4857 }
4858#endif
4859
4860 if (data->state == BUTTON_STATE_HIGHLIGHT) {
4861 const int rna_type = but->rnaprop ? RNA_property_type(but->rnaprop) : 0;
4862
4863 if (is_property && ELEM(rna_type, PROP_POINTER, PROP_STRING) &&
4864 (but->custom_data != nullptr) && (event->type == LEFTMOUSE) &&
4865 ((event->val == KM_DBL_CLICK) || (event->modifier & KM_CTRL)))
4866 {
4868 return WM_UI_HANDLER_BREAK;
4869 }
4870 if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY)) {
4871 const int event_val = (is_property) ? KM_PRESS : KM_CLICK;
4872 if (event->val == event_val) {
4874 return WM_UI_HANDLER_BREAK;
4875 }
4876 }
4877 }
4878 else if (data->state == BUTTON_STATE_TEXT_EDITING) {
4879 ui_do_but_textedit(C, block, but, data, event);
4880 return WM_UI_HANDLER_BREAK;
4881 }
4882 else if (data->state == BUTTON_STATE_TEXT_SELECTING) {
4883 ui_do_but_textedit_select(C, block, but, data, event);
4884 return WM_UI_HANDLER_BREAK;
4885 }
4886
4888}
4889
4895 uiBut *but,
4897 const int inc_value)
4898{
4899 /* The allocated string only increases in length by 1,
4900 * only support incrementing by one. */
4901 BLI_assert(ELEM(inc_value, -1, 1));
4902
4903 if (data->state != BUTTON_STATE_HIGHLIGHT) {
4904 /* This function assumes the mouse is only hovering over the input. */
4906 }
4907
4908 /* Retrieve the string. */
4909 char *but_string;
4910 int str_maxncpy = ui_but_string_get_maxncpy(but);
4911 bool no_zero_strip = false;
4912 if (str_maxncpy != 0) {
4913 but_string = MEM_calloc_arrayN<char>(str_maxncpy, __func__);
4915 but, but_string, str_maxncpy, UI_PRECISION_FLOAT_MAX, true, &no_zero_strip);
4916 }
4917 else {
4918 but_string = ui_but_string_get_dynamic(but, &str_maxncpy);
4919 }
4920
4921 if (but_string[0] == '\0') {
4922 /* Don't append a number to an empty string. */
4923 MEM_freeN(but_string);
4925 }
4926
4927 /* More space needed for an added digit. */
4928 str_maxncpy += 1;
4929 char *head = MEM_calloc_arrayN<char>(str_maxncpy, __func__);
4930 char *tail = MEM_calloc_arrayN<char>(str_maxncpy, __func__);
4931 ushort digits;
4932
4933 /* Decode the string, parsing head, digits, tail. */
4934 int num = BLI_path_sequence_decode(but_string, head, str_maxncpy, tail, str_maxncpy, &digits);
4935 MEM_freeN(but_string);
4936 if (num == 0 && digits == 0) {
4938 }
4939
4940 /* Increase or decrease the value. */
4941 num += inc_value;
4942
4943 /* Encode the new string with the changed value. */
4944 char *string = MEM_calloc_arrayN<char>(str_maxncpy, __func__);
4945 BLI_path_sequence_encode(string, str_maxncpy, head, tail, digits, num);
4946
4947 /* Save this new string to the button. */
4948 ui_but_set_string_interactive(C, but, string);
4949
4950 /* Free the strings. */
4951 MEM_freeN(string);
4952 MEM_freeN(head);
4953 MEM_freeN(tail);
4954
4955 return WM_UI_HANDLER_BREAK;
4956}
4957
4958static int ui_do_but_TEX(
4959 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
4960{
4961 if (data->state == BUTTON_STATE_HIGHLIGHT) {
4963 event->val == KM_PRESS)
4964 {
4965 if (ELEM(event->type, EVT_PADENTER, EVT_RETKEY) && !UI_but_is_utf8(but)) {
4966 /* Pass, allow file-selector, enter to execute. */
4967 }
4968 else if (ELEM(but->emboss,
4971 (event->modifier != KM_CTRL))
4972 {
4973 /* Pass. */
4974 }
4975 else {
4976 if (!ui_but_extra_operator_icon_mouse_over_get(but, data->region, event)) {
4978 }
4979 return WM_UI_HANDLER_BREAK;
4980 }
4981 }
4982 else if (ELEM(event->type, WHEELUPMOUSE, WHEELDOWNMOUSE) && (event->modifier & KM_CTRL)) {
4983 if ((but->type == ButType::SearchMenu) && but->func_argN &&
4984 (static_cast<uiButSearch *>(but)->arg == but->func_argN))
4985 {
4986 /* Disable value cycling for search buttons with an allocated search data argument. This
4987 * causes issues because the search data is moved to the "afterfuncs", but search updating
4988 * requires it again. See #147539. */
4989 }
4990 else {
4991 const int inc_value = (event->type == WHEELUPMOUSE) ? 1 : -1;
4992 return ui_do_but_text_value_cycle(C, but, data, inc_value);
4993 }
4994 }
4995 }
4996 else if (data->state == BUTTON_STATE_TEXT_EDITING) {
4997 return ui_do_but_textedit(C, block, but, data, event);
4998 }
4999 else if (data->state == BUTTON_STATE_TEXT_SELECTING) {
5000 return ui_do_but_textedit_select(C, block, but, data, event);
5001 }
5002
5004}
5005
5007 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
5008{
5009 /* unlink icon is on right */
5011 /* doing this on KM_PRESS calls eyedropper after clicking unlink icon */
5012 if ((event->val == KM_RELEASE) && ui_do_but_extra_operator_icon(C, but, data, event)) {
5013 return WM_UI_HANDLER_BREAK;
5014 }
5015 }
5016 return ui_do_but_TEX(C, block, but, data, event);
5017}
5018
5019static int ui_do_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
5020{
5021#ifdef USE_DRAG_TOGGLE
5022 {
5023 int retval;
5024 if (ui_do_but_ANY_drag_toggle(C, but, data, event, &retval)) {
5025 return retval;
5026 }
5027 }
5028#endif
5029
5030 if (data->state == BUTTON_STATE_HIGHLIGHT) {
5031 bool do_activate = false;
5032 if (ELEM(event->type, EVT_PADENTER, EVT_RETKEY)) {
5033 if (event->val == KM_PRESS) {
5034 do_activate = true;
5035 }
5036 }
5037 else if (event->type == LEFTMOUSE) {
5038 if (ui_block_is_menu(but->block)) {
5039 /* Behave like other menu items. */
5040 do_activate = (event->val == KM_RELEASE);
5041 }
5042 else if (!ui_do_but_extra_operator_icon(C, but, data, event)) {
5043 /* Also use double-clicks to prevent fast clicks to leak to other handlers (#76481). */
5044 do_activate = ELEM(event->val, KM_PRESS, KM_DBL_CLICK);
5045 }
5046 }
5047
5048 if (do_activate) {
5050 return WM_UI_HANDLER_BREAK;
5051 }
5052 if (ELEM(event->type, MOUSEPAN, WHEELDOWNMOUSE, WHEELUPMOUSE) && (event->modifier & KM_CTRL)) {
5053 if (ELEM(but->type,
5061 {
5062 /* Support Ctrl-Wheel to cycle toggles and check-boxes. */
5064 return WM_UI_HANDLER_BREAK;
5065 }
5066 else if (but->type == ButType::Row) {
5067 /* Support Ctrl-Wheel to cycle values on expanded enum rows. */
5068 int type = event->type;
5069 int val = event->val;
5070
5071 /* Convert pan to scroll-wheel. */
5072 if (type == MOUSEPAN) {
5073 ui_pan_to_scroll(event, &type, &val);
5074
5075 if (type == MOUSEPAN) {
5076 return WM_UI_HANDLER_BREAK;
5077 }
5078 }
5079
5080 const int direction = (type == WHEELDOWNMOUSE) ? -1 : 1;
5081 uiBut *but_select = ui_but_find_select_in_enum(but, direction);
5082 if (but_select) {
5083 uiBut *but_other = (direction == -1) ? but_select->block->next_but(but_select) :
5084 but_select->block->prev_but(but_select);
5085 if (but_other && ui_but_find_select_in_enum__cmp(but, but_other)) {
5086 ARegion *region = data->region;
5087
5088 data->cancel = true;
5089 button_activate_exit(C, but, data, false, false);
5090
5091 /* Activate the text button. */
5092 button_activate_init(C, region, but_other, BUTTON_ACTIVATE_OVER);
5093 data = but_other->active;
5094 if (data) {
5095 ui_apply_but(C, but->block, but_other, but_other->active, true);
5096 button_activate_exit(C, but_other, data, false, false);
5097
5098 /* restore active button */
5100 }
5101 else {
5102 /* shouldn't happen */
5103 BLI_assert(0);
5104 }
5105 }
5106 }
5107 return WM_UI_HANDLER_BREAK;
5108 }
5109 }
5110 }
5112}
5113
5122 ARegion *region,
5123 uiButViewItem *but,
5124 const bool close_popup = true)
5125{
5126
5127 /* For popups. Other abstract view instances correctly calls the select operator, see:
5128 * #141235. */
5129 if (but->context) {
5130 CTX_store_set(C, but->context);
5131 }
5132 but->view_item->activate(*C);
5133 CTX_store_set(C, nullptr);
5134
5137
5138 if (close_popup && !UI_view_item_popup_keep_open(*but->view_item)) {
5140 }
5141}
5142
5144 uiBut *but,
5146 const wmEvent *event)
5147{
5148 uiButViewItem *view_item_but = (uiButViewItem *)but;
5149 BLI_assert(view_item_but->type == ButType::ViewItem);
5150
5151 if (data->state == BUTTON_STATE_HIGHLIGHT) {
5152 if (event->type == LEFTMOUSE) {
5153 switch (event->val) {
5154 case KM_PRESS:
5155 /* Extra icons have priority, don't mess with them. */
5156 if (ui_but_extra_operator_icon_mouse_over_get(but, data->region, event)) {
5157 return WM_UI_HANDLER_BREAK;
5158 }
5159
5160 if (ui_block_is_popup_any(but->block)) {
5161 /* TODO(!147047): This should be handled in selection operator. */
5162 force_activate_view_item_but(C, data->region, view_item_but, false);
5163 return WM_UI_HANDLER_BREAK;
5164 }
5165
5166 if (UI_view_item_supports_drag(*view_item_but->view_item)) {
5168 data->dragstartx = event->xy[0];
5169 data->dragstarty = event->xy[1];
5170 }
5171
5172 /* Always continue for drag and drop handling. Also for cases where keymap items are
5173 * registered to add custom activate or drag operators (the pose library does this for
5174 * example). */
5176 case KM_DBL_CLICK:
5177 if (UI_view_item_can_rename(*view_item_but->view_item)) {
5178 data->cancel = true;
5179 UI_view_item_begin_rename(*view_item_but->view_item);
5180 ED_region_tag_redraw(data->region);
5182 return WM_UI_HANDLER_BREAK;
5183 }
5185 }
5186 }
5187 }
5188 else if (data->state == BUTTON_STATE_WAIT_DRAG) {
5189 /* Let "default" button handling take care of the drag logic. */
5190 return ui_do_but_EXIT(C, but, data, event);
5191 }
5192
5194}
5195
5197{
5198 if (data->state == BUTTON_STATE_HIGHLIGHT) {
5199
5200 /* First handle click on icon-drag type button. */
5201 if ((event->type == LEFTMOUSE) && (event->val == KM_PRESS) && ui_but_drag_is_draggable(but)) {
5202 if (ui_but_contains_point_px_icon(but, data->region, event)) {
5203
5204 /* tell the button to wait and keep checking further events to
5205 * see if it should start dragging */
5207 data->dragstartx = event->xy[0];
5208 data->dragstarty = event->xy[1];
5210 }
5211 }
5212#ifdef USE_DRAG_TOGGLE
5213 if ((event->type == LEFTMOUSE) && (event->val == KM_PRESS) && ui_but_is_drag_toggle(but)) {
5215 data->dragstartx = event->xy[0];
5216 data->dragstarty = event->xy[1];
5218 }
5219#endif
5220
5221 if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) {
5223 /* XXX: (a bit ugly) Special case handling for file-browser drag buttons (icon and filename
5224 * label). */
5225 if (ui_but_drag_is_draggable(but) && ui_but_contains_point_px_icon(but, data->region, event))
5226 {
5228 }
5229 const uiBut *view_but = ui_view_item_find_mouse_over(data->region, event->xy);
5230 if (view_but) {
5232 }
5234 return ret;
5235 }
5236 }
5237 else if (data->state == BUTTON_STATE_WAIT_DRAG) {
5238
5239 /* this function also ends state */
5240 if (ui_but_drag_init(C, but, data, event)) {
5241 return WM_UI_HANDLER_BREAK;
5242 }
5243
5244 /* If the mouse has been pressed and released, getting to
5245 * this point without triggering a drag, then clear the
5246 * drag state for this button and continue to pass on the event */
5247 if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
5250 }
5251
5252 /* while waiting for a drag to be triggered, always block
5253 * other events from getting handled */
5254 return WM_UI_HANDLER_BREAK;
5255 }
5256
5258}
5259
5260/* var names match ui_numedit_but_NUM */
5262 uiBut *but, float tempf, float softmin, float softmax, const enum eSnapType snap)
5263{
5264 if (tempf == softmin || tempf == softmax || snap == SNAP_OFF) {
5265 /* pass */
5266 }
5267 else {
5268 const PropertyScaleType scale_type = ui_but_scale_type(but);
5269 float softrange = softmax - softmin;
5270 float fac = 1.0f;
5271
5272 if (ui_but_is_unit(but)) {
5273 const UnitSettings *unit = but->block->unit;
5274 const int unit_type = RNA_SUBTYPE_UNIT_VALUE(UI_but_unit_type_get(but));
5275
5276 if (BKE_unit_is_valid(unit->system, unit_type)) {
5277 fac = float(BKE_unit_base_scalar(unit->system, unit_type));
5278 if (ELEM(unit_type, B_UNIT_LENGTH, B_UNIT_AREA, B_UNIT_VOLUME)) {
5279 fac /= unit->scale_length;
5280 }
5281 }
5282 }
5283
5284 if (fac != 1.0f) {
5285 /* snap in unit-space */
5286 tempf /= fac;
5287 // softmin /= fac; /* UNUSED */
5288 // softmax /= fac; /* UNUSED */
5289 softrange /= fac;
5290 }
5291
5292 /* workaround, too high snapping values */
5293 /* snapping by 10's for float buttons is quite annoying (location, scale...),
5294 * but allow for rotations */
5295 if (softrange >= 21.0f) {
5296 const UnitSettings *unit = but->block->unit;
5297 const int unit_type = UI_but_unit_type_get(but);
5298 if ((unit_type == PROP_UNIT_ROTATION) && (unit->system_rotation != USER_UNIT_ROT_RADIANS)) {
5299 /* Pass (degrees). */
5300 }
5301 else {
5302 softrange = 20.0f;
5303 }
5304 }
5305
5307 switch (scale_type) {
5308 case PROP_SCALE_LINEAR:
5309 case PROP_SCALE_CUBIC: {
5310 const float snap_fac = (snap == SNAP_ON_SMALL ? 0.1f : 1.0f);
5311 if (softrange < 2.10f) {
5312 tempf = roundf(tempf * 10.0f / snap_fac) * 0.1f * snap_fac;
5313 }
5314 else if (softrange < 21.0f) {
5315 tempf = roundf(tempf / snap_fac) * snap_fac;
5316 }
5317 else {
5318 tempf = roundf(tempf * 0.1f / snap_fac) * 10.0f * snap_fac;
5319 }
5320 break;
5321 }
5322 case PROP_SCALE_LOG: {
5323 const float snap_fac = powf(10.0f,
5324 roundf(log10f(tempf) + UI_PROP_SCALE_LOG_SNAP_OFFSET) -
5325 (snap == SNAP_ON_SMALL ? 2.0f : 1.0f));
5326 tempf = roundf(tempf / snap_fac) * snap_fac;
5327 break;
5328 }
5329 }
5330
5331 if (fac != 1.0f) {
5332 tempf *= fac;
5333 }
5334 }
5335
5336 return tempf;
5337}
5338
5339static float ui_numedit_apply_snap(int temp,
5340 float softmin,
5341 float softmax,
5342 const enum eSnapType snap)
5343{
5344 if (ELEM(temp, softmin, softmax)) {
5345 return temp;
5346 }
5347
5348 switch (snap) {
5349 case SNAP_OFF:
5350 break;
5351 case SNAP_ON:
5352 temp = 10 * (temp / 10);
5353 break;
5354 case SNAP_ON_SMALL:
5355 temp = 100 * (temp / 100);
5356 break;
5357 }
5358
5359 return temp;
5360}
5361
5364 int mx,
5365 blender::FunctionRef<int()> drag_threshold_fn,
5366 const bool is_motion,
5367 const enum eSnapType snap,
5368 float fac)
5369{
5370 float deler, tempf;
5371 int lvalue, temp;
5372 bool changed = false;
5373 const bool is_float = ui_but_is_float(but);
5374 const PropertyScaleType scale_type = ui_but_scale_type(but);
5375
5376 /* prevent unwanted drag adjustments, test motion so modifier keys refresh. */
5377 if ((is_motion || data->draglock) &&
5378 (ui_but_dragedit_update_mval(data, mx, drag_threshold_fn) == false))
5379 {
5380 return changed;
5381 }
5382
5384 static_cast<bContext *>(but->block->evil_C), but->block, data, false);
5385
5386 if (ui_but_is_cursor_warp(but)) {
5387 const float softmin = but->softmin;
5388 const float softmax = but->softmax;
5389 const float softrange = softmax - softmin;
5390
5391 const float log_min = (scale_type == PROP_SCALE_LOG) ?
5393 powf(10, -but->precision) * 0.5f) :
5394 0;
5395
5396 /* Mouse location isn't screen clamped to the screen so use a linear mapping
5397 * 2px == 1-int, or 1px == 1-ClickStep */
5398 if (is_float) {
5399 fac *= 0.01f * but->step_size;
5400 switch (scale_type) {
5401 case PROP_SCALE_LINEAR: {
5402 tempf = float(data->startvalue) + float(mx - data->dragstartx) * fac;
5403 break;
5404 }
5405 case PROP_SCALE_LOG: {
5406 const float startvalue = max_ff(float(data->startvalue), log_min);
5407 tempf = expf(float(mx - data->dragstartx) * fac) * startvalue;
5408 if (tempf <= log_min) {
5409 tempf = 0.0f;
5410 }
5411 break;
5412 }
5413 case PROP_SCALE_CUBIC: {
5414 tempf = cbrtf(float(data->startvalue)) + float(mx - data->dragstartx) * fac;
5415 tempf *= tempf * tempf;
5416 break;
5417 }
5418 }
5419
5420 tempf = ui_numedit_apply_snapf(but, tempf, softmin, softmax, snap);
5421
5422#if 1 /* fake moving the click start, nicer for dragging back after passing the limit */
5423 switch (scale_type) {
5424 case PROP_SCALE_LINEAR: {
5425 if (tempf < softmin) {
5426 data->dragstartx -= (softmin - tempf) / fac;
5427 tempf = softmin;
5428 }
5429 else if (tempf > softmax) {
5430 data->dragstartx -= (softmax - tempf) / fac;
5431 tempf = softmax;
5432 }
5433 break;
5434 }
5435 case PROP_SCALE_LOG: {
5436 const float startvalue = max_ff(float(data->startvalue), log_min);
5437 if (tempf < log_min) {
5438 data->dragstartx -= logf(log_min / startvalue) / fac - float(mx - data->dragstartx);
5439 tempf = softmin;
5440 }
5441 else if (tempf > softmax) {
5442 data->dragstartx -= logf(softmax / startvalue) / fac - float(mx - data->dragstartx);
5443 tempf = softmax;
5444 }
5445 break;
5446 }
5447 case PROP_SCALE_CUBIC: {
5448 if (tempf < softmin) {
5449 data->dragstartx = mx - int((cbrtf(softmin) - cbrtf(float(data->startvalue))) / fac);
5450 tempf = softmin;
5451 }
5452 else if (tempf > softmax) {
5453 data->dragstartx = mx - int((cbrtf(softmax) - cbrtf(float(data->startvalue))) / fac);
5454 tempf = softmax;
5455 }
5456 break;
5457 }
5458 }
5459#else
5460 CLAMP(tempf, softmin, softmax);
5461#endif
5462
5463 if (tempf != float(data->value)) {
5464 data->dragchange = true;
5465 data->value = tempf;
5466 changed = true;
5467 }
5468 }
5469 else {
5470 if (softrange > 256) {
5471 fac = 1.0;
5472 } /* 1px == 1 */
5473 else if (softrange > 32) {
5474 fac = 1.0 / 2.0;
5475 } /* 2px == 1 */
5476 else {
5477 fac = 1.0 / 16.0;
5478 } /* 16px == 1? */
5479
5480 temp = data->startvalue + ((double(mx) - data->dragstartx) * double(fac));
5481 temp = ui_numedit_apply_snap(temp, softmin, softmax, snap);
5482
5483#if 1 /* fake moving the click start, nicer for dragging back after passing the limit */
5484 if (temp < softmin) {
5485 data->dragstartx -= (softmin - temp) / fac;
5486 temp = softmin;
5487 }
5488 else if (temp > softmax) {
5489 data->dragstartx += (temp - softmax) / fac;
5490 temp = softmax;
5491 }
5492#else
5493 CLAMP(temp, softmin, softmax);
5494#endif
5495
5496 if (temp != data->value) {
5497 data->dragchange = true;
5498 data->value = temp;
5499 changed = true;
5500 }
5501 }
5502
5503 data->draglastx = mx;
5504 }
5505 else {
5506 /* Use 'but->softmin', 'but->softmax' when clamping values. */
5507 const float softmin = data->drag_map_soft_min;
5508 const float softmax = data->drag_map_soft_max;
5509 const float softrange = softmax - softmin;
5510
5511 float non_linear_range_limit;
5512 float non_linear_pixel_map;
5513 float non_linear_scale;
5514
5515 /* Use a non-linear mapping of the mouse drag especially for large floats
5516 * (normal behavior) */
5517 deler = 500;
5518 if (is_float) {
5519 /* not needed for smaller float buttons */
5520 non_linear_range_limit = 11.0f;
5521 non_linear_pixel_map = 500.0f;
5522 }
5523 else {
5524 /* only scale large int buttons */
5525 non_linear_range_limit = 129.0f;
5526 /* Larger for ints, we don't need to fine tune them. */
5527 non_linear_pixel_map = 250.0f;
5528
5529 /* prevent large ranges from getting too out of control */
5530 if (softrange > 600) {
5531 deler = powf(softrange, 0.75f);
5532 }
5533 else if (softrange < 25) {
5534 deler = 50.0;
5535 }
5536 else if (softrange < 100) {
5537 deler = 100.0;
5538 }
5539 }
5540 deler /= fac;
5541
5542 if (softrange > non_linear_range_limit) {
5543 non_linear_scale = float(abs(mx - data->dragstartx)) / non_linear_pixel_map;
5544 }
5545 else {
5546 non_linear_scale = 1.0f;
5547 }
5548
5549 if (is_float == false) {
5550 /* at minimum, moving cursor 2 pixels should change an int button. */
5551 CLAMP_MIN(non_linear_scale, 0.5f * UI_SCALE_FAC);
5552 }
5553
5554 data->dragf += (float(mx - data->draglastx) / deler) * non_linear_scale;
5555
5556 if (but->softmin == softmin) {
5557 CLAMP_MIN(data->dragf, 0.0f);
5558 }
5559 if (but->softmax == softmax) {
5560 CLAMP_MAX(data->dragf, 1.0f);
5561 }
5562
5563 data->draglastx = mx;
5564
5565 switch (scale_type) {
5566 case PROP_SCALE_LINEAR: {
5567 tempf = (softmin + data->dragf * softrange);
5568 break;
5569 }
5570 case PROP_SCALE_LOG: {
5571 const float log_min = max_ff(max_ff(softmin, UI_PROP_SCALE_LOG_MIN),
5572 powf(10.0f, -but->precision) * 0.5f);
5573 const float base = softmax / log_min;
5574 tempf = powf(base, data->dragf) * log_min;
5575 if (tempf <= log_min) {
5576 tempf = 0.0f;
5577 }
5578 break;
5579 }
5580 case PROP_SCALE_CUBIC: {
5581 tempf = (softmin + data->dragf * softrange);
5582 tempf *= tempf * tempf;
5583 float cubic_min = softmin * softmin * softmin;
5584 float cubic_max = softmax * softmax * softmax;
5585 tempf = (tempf - cubic_min) / (cubic_max - cubic_min) * softrange + softmin;
5586 break;
5587 }
5588 }
5589
5590 if (!is_float) {
5591 temp = round_fl_to_int(tempf);
5592
5593 temp = ui_numedit_apply_snap(temp, but->softmin, but->softmax, snap);
5594
5595 CLAMP(temp, but->softmin, but->softmax);
5596 lvalue = int(data->value);
5597
5598 if (temp != lvalue) {
5599 data->dragchange = true;
5600 data->value = double(temp);
5601 changed = true;
5602 }
5603 }
5604 else {
5605 temp = 0;
5606 tempf = ui_numedit_apply_snapf(but, tempf, but->softmin, but->softmax, snap);
5607
5608 CLAMP(tempf, but->softmin, but->softmax);
5609
5610 if (tempf != float(data->value)) {
5611 data->dragchange = true;
5612 data->value = tempf;
5613 changed = true;
5614 }
5615 }
5616 }
5617
5618 return changed;
5619}
5620
5622{
5623 const int oldflag = but->drawflag;
5625
5627 if (!data) {
5628 return;
5629 }
5630
5631 /* Ignore once we start dragging. */
5632 if (data->dragchange == false) {
5633 const float handle_width = min_ff(BLI_rctf_size_x(&but->rect) / 3,
5634 BLI_rctf_size_y(&but->rect) * 0.7f);
5635 /* we can click on the side arrows to increment/decrement,
5636 * or click inside to edit the value directly */
5637 int mx = data->window->eventstate->xy[0];
5638 int my = data->window->eventstate->xy[1];
5639 ui_window_to_block(data->region, but->block, &mx, &my);
5640
5641 if (mx < (but->rect.xmin + handle_width)) {
5643 }
5644 else if (mx > (but->rect.xmax - handle_width)) {
5646 }
5647 }
5648
5649 /* Don't change the cursor once pressed. */
5650 if ((but->flag & UI_SELECT) == 0) {
5651 if ((but->drawflag & UI_BUT_HOVER_LEFT) || (but->drawflag & UI_BUT_HOVER_RIGHT)) {
5652 if (data->changed_cursor) {
5654 data->changed_cursor = false;
5655 }
5656 }
5657 else {
5658 if (data->changed_cursor == false) {
5660 data->changed_cursor = true;
5661 }
5662 }
5663 }
5664
5665 if (but->drawflag != oldflag) {
5666 ED_region_tag_redraw(data->region);
5667 }
5668}
5669
5670static int ui_do_but_NUM(
5671 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
5672{
5673 uiButNumber *number_but = (uiButNumber *)but;
5674 int click = 0;
5675 int retval = WM_UI_HANDLER_CONTINUE;
5676
5677 /* mouse location scaled to fit the UI */
5678 int mx = event->xy[0];
5679 int my = event->xy[1];
5680 /* mouse location kept at screen pixel coords */
5681 const int screen_mx = event->xy[0];
5682
5683 /* Defer evaluation as it's rarely needed. */
5684 auto drag_threshold_fn = [event]() -> int { return WM_event_drag_threshold(event); };
5685
5686 BLI_assert(but->type == ButType::Num);
5687
5688 ui_window_to_block(data->region, block, &mx, &my);
5690
5691 if (data->state == BUTTON_STATE_HIGHLIGHT) {
5692 int type = event->type, val = event->val;
5693
5694 if (type == MOUSEPAN) {
5695 ui_pan_to_scroll(event, &type, &val);
5696 }
5697
5698 /* XXX hardcoded keymap check.... */
5699 if (type == MOUSEPAN && (event->modifier & KM_CTRL)) {
5700 /* allow accumulating values, otherwise scrolling gets preference */
5701 retval = WM_UI_HANDLER_BREAK;
5702 }
5703 else if (type == WHEELDOWNMOUSE && (event->modifier & KM_CTRL)) {
5704 mx = but->rect.xmin;
5707 click = 1;
5708 }
5709 else if ((type == WHEELUPMOUSE) && (event->modifier & KM_CTRL)) {
5710 mx = but->rect.xmax;
5713 click = 1;
5714 }
5715 else if (event->val == KM_PRESS) {
5716 if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && (event->modifier & KM_CTRL)) {
5718 retval = WM_UI_HANDLER_BREAK;
5719 }
5720 else if (event->type == LEFTMOUSE) {
5721 data->dragstartx = data->draglastx = ui_but_is_cursor_warp(but) ? screen_mx : mx;
5723 retval = WM_UI_HANDLER_BREAK;
5724 }
5725 else if (ELEM(event->type, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) {
5726 click = 1;
5727 }
5728 else if (event->type == EVT_BUT_OPEN) {
5729 /* Handle UI_but_focus_on_enter_event. */
5731 retval = WM_UI_HANDLER_BREAK;
5732 }
5733 else if (event->type == EVT_MINUSKEY && event->val == KM_PRESS) {
5735 data->value = -data->value;
5737 retval = WM_UI_HANDLER_BREAK;
5738 }
5739
5740#ifdef USE_DRAG_MULTINUM
5741 copy_v2_v2_int(data->multi_data.drag_start, event->xy);
5742#endif
5743 }
5744 }
5745 else if (data->state == BUTTON_STATE_NUM_EDITING) {
5746 if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) {
5747 if (event->val == KM_PRESS) {
5748 data->cancel = true;
5749 data->escapecancel = true;
5751 }
5752 }
5753 else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
5754 if (data->dragchange) {
5755#ifdef USE_DRAG_MULTINUM
5756 /* If we started multi-button but didn't drag, then edit. */
5757 if (data->multi_data.init == uiHandleButtonMulti::INIT_SETUP) {
5758 click = 1;
5759 }
5760 else
5761#endif
5762 {
5764 }
5765 }
5766 else {
5767 click = 1;
5768 }
5769 }
5770 else if ((event->type == MOUSEMOVE) || ui_event_is_snap(event)) {
5771 const bool is_motion = (event->type == MOUSEMOVE);
5772 const enum eSnapType snap = ui_event_to_snap(event);
5773 float fac;
5774
5775#ifdef USE_DRAG_MULTINUM
5776 data->multi_data.drag_dir[0] += abs(data->draglastx - mx);
5777 data->multi_data.drag_dir[1] += abs(data->draglasty - my);
5778#endif
5779
5780 fac = 1.0f;
5781 if (event->modifier & KM_SHIFT) {
5782 fac /= 10.0f;
5783 }
5784
5785 if (ui_numedit_but_NUM(number_but,
5786 data,
5787 (ui_but_is_cursor_warp(but) ? screen_mx : mx),
5788 drag_threshold_fn,
5789 is_motion,
5790 snap,
5791 fac))
5792 {
5793 ui_numedit_apply(C, block, but, data);
5794 }
5795#ifdef USE_DRAG_MULTINUM
5796 else if (data->multi_data.has_mbuts) {
5797 if (data->multi_data.init == uiHandleButtonMulti::INIT_ENABLE) {
5799 }
5800 }
5801#endif
5802 }
5803 retval = WM_UI_HANDLER_BREAK;
5804 }
5805 else if (data->state == BUTTON_STATE_TEXT_EDITING) {
5806 ui_do_but_textedit(C, block, but, data, event);
5807 retval = WM_UI_HANDLER_BREAK;
5808 }
5809 else if (data->state == BUTTON_STATE_TEXT_SELECTING) {
5810 ui_do_but_textedit_select(C, block, but, data, event);
5811 retval = WM_UI_HANDLER_BREAK;
5812 }
5813
5814 if (click) {
5815 /* we can click on the side arrows to increment/decrement,
5816 * or click inside to edit the value directly */
5817
5818 if (!ui_but_is_float(but)) {
5819 /* Integer Value. */
5822
5823 const int value_step = int(number_but->step_size);
5824 BLI_assert(value_step > 0);
5825 const int softmin = round_fl_to_int_clamp(but->softmin);
5826 const int softmax = round_fl_to_int_clamp(but->softmax);
5827 const double value_test = (but->drawflag & UI_BUT_HOVER_LEFT) ?
5828 double(max_ii(softmin, int(data->value) - value_step)) :
5829 double(min_ii(softmax, int(data->value) + value_step));
5830 if (value_test != data->value) {
5831 data->value = double(value_test);
5832 }
5833 else {
5834 data->cancel = true;
5835 }
5837 }
5838 else {
5840 }
5841 }
5842 else {
5843 /* Float Value. */
5845 const PropertyScaleType scale_type = ui_but_scale_type(but);
5846
5848
5849 double value_step;
5850 if (scale_type == PROP_SCALE_LOG) {
5851 double precision = (roundf(log10f(data->value) + UI_PROP_SCALE_LOG_SNAP_OFFSET) - 1.0f) +
5852 log10f(number_but->step_size);
5853 /* Non-finite when `data->value` is zero. */
5854 if (UNLIKELY(!isfinite(precision))) {
5855 precision = -FLT_MAX; /* Ignore this value. */
5856 }
5857 value_step = powf(10.0f, max_ff(precision, -number_but->precision));
5858 }
5859 else {
5860 value_step = double(number_but->step_size * UI_PRECISION_FLOAT_SCALE);
5861 }
5862 BLI_assert(value_step > 0.0f);
5863 const double value_test =
5864 (but->drawflag & UI_BUT_HOVER_LEFT) ?
5865 double(max_ff(but->softmin, float(data->value - value_step))) :
5866 double(min_ff(but->softmax, float(data->value + value_step)));
5867 if (value_test != data->value) {
5868 data->value = value_test;
5869 }
5870 else {
5871 data->cancel = true;
5872 }
5874 }
5875 else {
5877 }
5878 }
5879
5880 retval = WM_UI_HANDLER_BREAK;
5881 }
5882
5883 data->draglastx = mx;
5884 data->draglasty = my;
5885
5886 return retval;
5887}
5888
5889static bool ui_numedit_but_SLI(uiBut *but,
5891 int mx,
5892 blender::FunctionRef<int()> drag_threshold_fn,
5893 const bool is_horizontal,
5894 const bool is_motion,
5895 const bool snap,
5896 const bool shift)
5897{
5898 float cursor_x_range, f, tempf, softmin, softmax, softrange;
5899 int temp, lvalue;
5900 bool changed = false;
5901 float mx_fl, my_fl;
5902
5903 /* prevent unwanted drag adjustments, test motion so modifier keys refresh. */
5904 if ((but->type != ButType::Scroll) && (is_motion || data->draglock) &&
5905 (ui_but_dragedit_update_mval(data, mx, drag_threshold_fn) == false))
5906 {
5907 return changed;
5908 }
5909
5911 static_cast<bContext *>(but->block->evil_C), but->block, data, false);
5912
5913 const PropertyScaleType scale_type = ui_but_scale_type(but);
5914
5915 softmin = but->softmin;
5916 softmax = but->softmax;
5917 softrange = softmax - softmin;
5918
5919 /* yes, 'mx' as both x/y is intentional */
5920 ui_mouse_scale_warp(data, mx, mx, &mx_fl, &my_fl, shift);
5921
5922 if (but->type == ButType::NumSlider) {
5923 cursor_x_range = BLI_rctf_size_x(&but->rect);
5924 }
5925 else if (but->type == ButType::Scroll) {
5926 uiButScrollBar *scroll_but = reinterpret_cast<uiButScrollBar *>(but);
5927 const float size = (is_horizontal) ? BLI_rctf_size_x(&but->rect) :
5928 -BLI_rctf_size_y(&but->rect);
5929 cursor_x_range = size * (but->softmax - but->softmin) /
5930 (but->softmax - but->softmin + scroll_but->visual_height);
5931 }
5932 else {
5933 const float ofs = (BLI_rctf_size_y(&but->rect) / 2.0f);
5934 cursor_x_range = (BLI_rctf_size_x(&but->rect) - ofs);
5935 }
5936
5937 f = (mx_fl - data->dragstartx) / cursor_x_range + data->dragfstart;
5938 CLAMP(f, 0.0f, 1.0f);
5939
5940 /* deal with mouse correction */
5941#ifdef USE_CONT_MOUSE_CORRECT
5942 if (ui_but_is_cursor_warp(but)) {
5943 /* OK but can go outside bounds */
5944 if (is_horizontal) {
5945 data->ungrab_mval[0] = but->rect.xmin + (f * cursor_x_range);
5946 data->ungrab_mval[1] = BLI_rctf_cent_y(&but->rect);
5947 }
5948 else {
5949 data->ungrab_mval[1] = but->rect.ymin + (f * cursor_x_range);
5950 data->ungrab_mval[0] = BLI_rctf_cent_x(&but->rect);
5951 }
5952 BLI_rctf_clamp_pt_v(&but->rect, data->ungrab_mval);
5953 }
5954#endif
5955 /* done correcting mouse */
5956
5957 switch (scale_type) {
5958 case PROP_SCALE_LINEAR: {
5959 tempf = softmin + f * softrange;
5960 break;
5961 }
5962 case PROP_SCALE_LOG: {
5963 tempf = powf(softmax / softmin, f) * softmin;
5964 break;
5965 }
5966 case PROP_SCALE_CUBIC: {
5967 const float cubicmin = cube_f(softmin);
5968 const float cubicmax = cube_f(softmax);
5969 const float cubicrange = cubicmax - cubicmin;
5970 tempf = cube_f(softmin + f * softrange);
5971 tempf = (tempf - cubicmin) / cubicrange * softrange + softmin;
5972 break;
5973 }
5974 }
5975 temp = round_fl_to_int(tempf);
5976
5977 if (snap) {
5978 if (ELEM(tempf, softmin, softmax)) {
5979 /* pass */
5980 }
5981 else if (ui_but_is_float(but)) {
5982
5983 if (shift) {
5984 if (ELEM(tempf, softmin, softmax)) {
5985 }
5986 else if (softrange < 2.10f) {
5987 tempf = roundf(tempf * 100.0f) * 0.01f;
5988 }
5989 else if (softrange < 21.0f) {
5990 tempf = roundf(tempf * 10.0f) * 0.1f;
5991 }
5992 else {
5993 tempf = roundf(tempf);
5994 }
5995 }
5996 else {
5997 if (softrange < 2.10f) {
5998 tempf = roundf(tempf * 10.0f) * 0.1f;
5999 }
6000 else if (softrange < 21.0f) {
6001 tempf = roundf(tempf);
6002 }
6003 else {
6004 tempf = roundf(tempf * 0.1f) * 10.0f;
6005 }
6006 }
6007 }
6008 else {
6009 temp = 10 * (temp / 10);
6010 tempf = temp;
6011 }
6012 }
6013
6014 if (!ui_but_is_float(but)) {
6015 lvalue = round(data->value);
6016
6017 CLAMP(temp, softmin, softmax);
6018
6019 if (temp != lvalue) {
6020 data->value = temp;
6021 data->dragchange = true;
6022 changed = true;
6023 }
6024 }
6025 else {
6026 CLAMP(tempf, softmin, softmax);
6027
6028 if (tempf != float(data->value)) {
6029 data->value = tempf;
6030 data->dragchange = true;
6031 changed = true;
6032 }
6033 }
6034
6035 return changed;
6036}
6037
6038static int ui_do_but_SLI(
6039 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
6040{
6041 int click = 0;
6042 int retval = WM_UI_HANDLER_CONTINUE;
6043
6044 int mx = event->xy[0];
6045 int my = event->xy[1];
6046 ui_window_to_block(data->region, block, &mx, &my);
6047
6048 /* Defer evaluation as it's rarely needed. */
6049 auto drag_threshold_fn = [event]() -> int { return WM_event_drag_threshold(event); };
6050
6051 if (data->state == BUTTON_STATE_HIGHLIGHT) {
6052 int type = event->type, val = event->val;
6053
6054 if (type == MOUSEPAN) {
6055 ui_pan_to_scroll(event, &type, &val);
6056 }
6057
6058 /* XXX hardcoded keymap check.... */
6059 if ((type == MOUSEPAN) && (event->modifier & KM_CTRL)) {
6060 /* allow accumulating values, otherwise scrolling gets preference */
6061 retval = WM_UI_HANDLER_BREAK;
6062 }
6063 else if ((type == WHEELDOWNMOUSE) && (event->modifier & KM_CTRL)) {
6064 mx = but->rect.xmin;
6065 click = 2;
6066 }
6067 else if ((type == WHEELUPMOUSE) && (event->modifier & KM_CTRL)) {
6068 mx = but->rect.xmax;
6069 click = 2;
6070 }
6071 else if (event->val == KM_PRESS) {
6072 if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && (event->modifier & KM_CTRL)) {
6074 retval = WM_UI_HANDLER_BREAK;
6075 }
6076#ifndef USE_ALLSELECT
6077 /* alt-click on sides to get "arrows" like in ButType::Num buttons,
6078 * and match wheel usage above */
6079 else if ((event->type == LEFTMOUSE) && (event->modifier & KM_ALT)) {
6080 int halfpos = BLI_rctf_cent_x(&but->rect);
6081 click = 2;
6082 if (mx < halfpos) {
6083 mx = but->rect.xmin;
6084 }
6085 else {
6086 mx = but->rect.xmax;
6087 }
6088 }
6089#endif
6090 else if (event->type == LEFTMOUSE) {
6091 data->dragstartx = mx;
6092 data->draglastx = mx;
6094 retval = WM_UI_HANDLER_BREAK;
6095 }
6096 else if (ELEM(event->type, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) {
6097 click = 1;
6098 }
6099 else if (event->type == EVT_MINUSKEY && event->val == KM_PRESS) {
6101 data->value = -data->value;
6103 retval = WM_UI_HANDLER_BREAK;
6104 }
6105 }
6106#ifdef USE_DRAG_MULTINUM
6107 copy_v2_v2_int(data->multi_data.drag_start, event->xy);
6108#endif
6109 }
6110 else if (data->state == BUTTON_STATE_NUM_EDITING) {
6111 if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) {
6112 if (event->val == KM_PRESS) {
6113 data->cancel = true;
6114 data->escapecancel = true;
6116 }
6117 }
6118 else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
6119 if (data->dragchange) {
6120#ifdef USE_DRAG_MULTINUM
6121 /* If we started multi-button but didn't drag, then edit. */
6122 if (data->multi_data.init == uiHandleButtonMulti::INIT_SETUP) {
6123 click = 1;
6124 }
6125 else
6126#endif
6127 {
6129 }
6130 }
6131 else {
6132#ifdef USE_CONT_MOUSE_CORRECT
6133 /* reset! */
6134 copy_v2_fl(data->ungrab_mval, FLT_MAX);
6135#endif
6136 click = 1;
6137 }
6138 }
6139 else if ((event->type == MOUSEMOVE) || ui_event_is_snap(event)) {
6140 const bool is_motion = (event->type == MOUSEMOVE);
6141#ifdef USE_DRAG_MULTINUM
6142 data->multi_data.drag_dir[0] += abs(data->draglastx - mx);
6143 data->multi_data.drag_dir[1] += abs(data->draglasty - my);
6144#endif
6145 if (ui_numedit_but_SLI(but,
6146 data,
6147 mx,
6148 drag_threshold_fn,
6149 true,
6150 is_motion,
6151 event->modifier & KM_CTRL,
6152 event->modifier & KM_SHIFT))
6153 {
6154 ui_numedit_apply(C, block, but, data);
6155 }
6156
6157#ifdef USE_DRAG_MULTINUM
6158 else if (data->multi_data.has_mbuts) {
6159 if (data->multi_data.init == uiHandleButtonMulti::INIT_ENABLE) {
6161 }
6162 }
6163#endif
6164 }
6165 retval = WM_UI_HANDLER_BREAK;
6166 }
6167 else if (data->state == BUTTON_STATE_TEXT_EDITING) {
6168 ui_do_but_textedit(C, block, but, data, event);
6169 retval = WM_UI_HANDLER_BREAK;
6170 }
6171 else if (data->state == BUTTON_STATE_TEXT_SELECTING) {
6172 ui_do_but_textedit_select(C, block, but, data, event);
6173 retval = WM_UI_HANDLER_BREAK;
6174 }
6175
6176 if (click) {
6177 if (click == 2) {
6178 const PropertyScaleType scale_type = ui_but_scale_type(but);
6179
6180 /* nudge slider to the left or right */
6181 float f, tempf, softmin, softmax, softrange;
6182 int temp;
6183
6185
6186 softmin = but->softmin;
6187 softmax = but->softmax;
6188 softrange = softmax - softmin;
6189
6190 tempf = data->value;
6191 temp = int(data->value);
6192
6193#if 0
6194 if (but->type == SLI) {
6195 /* same as below */
6196 f = float(mx - but->rect.xmin) / (BLI_rctf_size_x(&but->rect));
6197 }
6198 else
6199#endif
6200 {
6201 f = float(mx - but->rect.xmin) / BLI_rctf_size_x(&but->rect);
6202 }
6203
6204 if (scale_type == PROP_SCALE_LOG) {
6205 f = powf(softmax / softmin, f) * softmin;
6206 }
6207 else {
6208 f = softmin + f * softrange;
6209 }
6210
6211 if (!ui_but_is_float(but)) {
6212 int value_step = 1;
6213 if (f < temp) {
6214 temp -= value_step;
6215 }
6216 else {
6217 temp += value_step;
6218 }
6219
6220 if (temp >= softmin && temp <= softmax) {
6221 data->value = temp;
6222 }
6223 else {
6224 data->cancel = true;
6225 }
6226 }
6227 else {
6228 if (tempf >= softmin && tempf <= softmax) {
6229 float value_step;
6230 if (scale_type == PROP_SCALE_LOG) {
6231 value_step = powf(10.0f, roundf(log10f(tempf) + UI_PROP_SCALE_LOG_SNAP_OFFSET) - 1.0f);
6232 }
6233 else {
6234 value_step = 0.01f;
6235 }
6236
6237 if (f < tempf) {
6238 tempf -= value_step;
6239 }
6240 else {
6241 tempf += value_step;
6242 }
6243
6244 CLAMP(tempf, softmin, softmax);
6245 data->value = tempf;
6246 }
6247 else {
6248 data->cancel = true;
6249 }
6250 }
6251
6253 retval = WM_UI_HANDLER_BREAK;
6254 }
6255 else {
6256 /* edit the value directly */
6258 retval = WM_UI_HANDLER_BREAK;
6259 }
6260 }
6261
6262 data->draglastx = mx;
6263 data->draglasty = my;
6264
6265 return retval;
6266}
6267
6269 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
6270{
6271 int retval = WM_UI_HANDLER_CONTINUE;
6272 const bool horizontal = (BLI_rctf_size_x(&but->rect) > BLI_rctf_size_y(&but->rect));
6273
6274 int mx = event->xy[0];
6275 int my = event->xy[1];
6276 ui_window_to_block(data->region, block, &mx, &my);
6277
6278 /* Defer evaluation as it's rarely needed. */
6279 auto drag_threshold_fn = [event]() -> int { return WM_event_drag_threshold(event); };
6280
6281 if (data->state == BUTTON_STATE_HIGHLIGHT) {
6282 if (event->val == KM_PRESS) {
6283 if (event->type == LEFTMOUSE) {
6284 if (horizontal) {
6285 data->dragstartx = mx;
6286 data->draglastx = mx;
6287 }
6288 else {
6289 data->dragstartx = my;
6290 data->draglastx = my;
6291 }
6293 retval = WM_UI_HANDLER_BREAK;
6294 }
6295 }
6296 }
6297 else if (data->state == BUTTON_STATE_NUM_EDITING) {
6298 if (event->type == EVT_ESCKEY) {
6299 if (event->val == KM_PRESS) {
6300 data->cancel = true;
6301 data->escapecancel = true;
6303 }
6304 }
6305 else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
6307 }
6308 else if (event->type == MOUSEMOVE) {
6309 const bool is_motion = true;
6310 if (ui_numedit_but_SLI(but,
6311 data,
6312 (horizontal) ? mx : my,
6313 drag_threshold_fn,
6314 horizontal,
6315 is_motion,
6316 false,
6317 false))
6318 {
6319 /* Scroll-bars in popups need UI layout refresh to update the right items to show. */
6320 if (ui_block_is_popup_any(but->block)) {
6322 }
6323 ui_numedit_apply(C, block, but, data);
6324 }
6325 }
6326
6327 retval = WM_UI_HANDLER_BREAK;
6328 }
6329
6330 return retval;
6331}
6332
6334 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
6335{
6336 int retval = WM_UI_HANDLER_CONTINUE;
6337 const bool horizontal = (BLI_rctf_size_x(&but->rect) < BLI_rctf_size_y(&but->rect));
6338
6339 /* NOTE: Having to store org point in window space and recompute it to block "space" each time
6340 * is not ideal, but this is a way to hack around behavior of ui_window_to_block(), which
6341 * returns different results when the block is inside a panel or not...
6342 * See #37739.
6343 */
6344
6345 int mx = event->xy[0];
6346 int my = event->xy[1];
6347 ui_window_to_block(data->region, block, &mx, &my);
6348
6349 if (data->state == BUTTON_STATE_HIGHLIGHT) {
6350 if (event->val == KM_PRESS) {
6351 if (event->type == LEFTMOUSE) {
6352 data->dragstartx = event->xy[0];
6353 data->dragstarty = event->xy[1];
6355 retval = WM_UI_HANDLER_BREAK;
6356 }
6357 }
6358 }
6359 else if (data->state == BUTTON_STATE_NUM_EDITING) {
6360 if (event->type == EVT_ESCKEY) {
6361 if (event->val == KM_PRESS) {
6362 data->cancel = true;
6363 data->escapecancel = true;
6365 }
6366 }
6367 else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
6369 }
6370 else if (event->type == MOUSEMOVE) {
6371 int dragstartx = data->dragstartx;
6372 int dragstarty = data->dragstarty;
6373 ui_window_to_block(data->region, block, &dragstartx, &dragstarty);
6374 data->value = data->origvalue + (horizontal ? mx - dragstartx : dragstarty - my);
6375 ui_numedit_apply(C, block, but, data);
6376 }
6377
6378 retval = WM_UI_HANDLER_BREAK;
6379 }
6380
6381 return retval;
6382}
6383
6385 uiBut *but,
6387 const wmEvent *event)
6388{
6389 if (data->state == BUTTON_STATE_HIGHLIGHT) {
6390 /* hack to pass on ctrl+click and double click to overlapping text
6391 * editing field for editing list item names
6392 */
6393 if ((ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && (event->val == KM_PRESS) &&
6394 (event->modifier & KM_CTRL)) ||
6395 (event->type == LEFTMOUSE && event->val == KM_DBL_CLICK))
6396 {
6398 C, but, data, event, BUTTON_ACTIVATE_TEXT_EDITING);
6399 if (labelbut) {
6400 /* Nothing else to do. */
6401 return WM_UI_HANDLER_BREAK;
6402 }
6403 }
6404 }
6405
6406 return ui_do_but_EXIT(C, but, data, event);
6407}
6408
6410{
6411 if (data->state == BUTTON_STATE_HIGHLIGHT) {
6412
6413 /* First handle click on icon-drag type button. */
6414 if (event->type == LEFTMOUSE && ui_but_drag_is_draggable(but) && event->val == KM_PRESS) {
6415 if (ui_but_contains_point_px_icon(but, data->region, event)) {
6417 data->dragstartx = event->xy[0];
6418 data->dragstarty = event->xy[1];
6419 return WM_UI_HANDLER_BREAK;
6420 }
6421 }
6422#ifdef USE_DRAG_TOGGLE
6423 if (event->type == LEFTMOUSE && event->val == KM_PRESS && ui_but_is_drag_toggle(but)) {
6425 data->dragstartx = event->xy[0];
6426 data->dragstarty = event->xy[1];
6427 return WM_UI_HANDLER_BREAK;
6428 }
6429#endif
6430 /* regular open menu */
6431 if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) {
6433 return WM_UI_HANDLER_BREAK;
6434 }
6435 if (ui_but_supports_cycling(but)) {
6436 if (ELEM(event->type, MOUSEPAN, WHEELDOWNMOUSE, WHEELUPMOUSE) && (event->modifier & KM_CTRL))
6437 {
6438 int type = event->type;
6439 int val = event->val;
6440
6441 /* Convert pan to scroll-wheel. */
6442 if (type == MOUSEPAN) {
6443 ui_pan_to_scroll(event, &type, &val);
6444
6445 if (type == MOUSEPAN) {
6446 return WM_UI_HANDLER_BREAK;
6447 }
6448 }
6449
6450 const int direction = (type == WHEELDOWNMOUSE) ? 1 : -1;
6451
6452 data->value = ui_but_menu_step(but, direction);
6453
6455 ui_apply_but(C, but->block, but, data, true);
6456
6457 /* Button's state need to be changed to EXIT so moving mouse away from this mouse
6458 * wouldn't lead to cancel changes made to this button, but changing state to EXIT also
6459 * makes no button active for a while which leads to triggering operator when doing fast
6460 * scrolling mouse wheel. using post activate stuff from button allows to make button be
6461 * active again after checking for all that mouse leave and cancel stuff, so quick
6462 * scroll wouldn't be an issue anymore. Same goes for scrolling wheel in another
6463 * direction below (sergey).
6464 */
6465 data->postbut = but;
6466 data->posttype = BUTTON_ACTIVATE_OVER;
6467
6468 /* without this, a new interface that draws as result of the menu change
6469 * won't register that the mouse is over it, eg:
6470 * Alt+MouseWheel over the render slots, without this,
6471 * the slot menu fails to switch a second time.
6472 *
6473 * The active state of the button could be maintained some other way
6474 * and remove this mouse-move event.
6475 */
6477
6478 return WM_UI_HANDLER_BREAK;
6479 }
6480 }
6481 }
6482 else if (data->state == BUTTON_STATE_WAIT_DRAG) {
6483
6484 /* this function also ends state */
6485 if (ui_but_drag_init(C, but, data, event)) {
6486 return WM_UI_HANDLER_BREAK;
6487 }
6488
6489 /* outside icon quit, not needed if drag activated */
6490 if (0 == ui_but_contains_point_px_icon(but, data->region, event)) {
6492 data->cancel = true;
6493 return WM_UI_HANDLER_BREAK;
6494 }
6495
6496 if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
6498 return WM_UI_HANDLER_BREAK;
6499 }
6500 }
6501
6503}
6504
6506 uiBut *but, uiHandleButtonData *data, int mx, int my, const enum eSnapType snap)
6507{
6508 float mrad;
6509 bool changed = true;
6510
6511 /* button is presumed square */
6512 /* if mouse moves outside of sphere, it does negative normal */
6513
6514 /* note that both data->vec and data->origvec should be normalized
6515 * else we'll get a harmless but annoying jump when first clicking */
6516
6517 float *fp = data->origvec;
6518 const float rad = BLI_rctf_size_x(&but->rect);
6519 const float radsq = rad * rad;
6520
6521 int mdx, mdy;
6522 if (fp[2] > 0.0f) {
6523 mdx = (rad * fp[0]);
6524 mdy = (rad * fp[1]);
6525 }
6526 else if (fp[2] > -1.0f) {
6527 mrad = rad / sqrtf(fp[0] * fp[0] + fp[1] * fp[1]);
6528
6529 mdx = 2.0f * mrad * fp[0] - (rad * fp[0]);
6530 mdy = 2.0f * mrad * fp[1] - (rad * fp[1]);
6531 }
6532 else {
6533 mdx = mdy = 0;
6534 }
6535
6536 float dx = float(mx + mdx - data->dragstartx);
6537 float dy = float(my + mdy - data->dragstarty);
6538
6539 fp = data->vec;
6540 mrad = dx * dx + dy * dy;
6541 if (mrad < radsq) { /* inner circle */
6542 fp[0] = dx;
6543 fp[1] = dy;
6544 fp[2] = sqrtf(radsq - dx * dx - dy * dy);
6545 }
6546 else { /* outer circle */
6547
6548 mrad = rad / sqrtf(mrad); /* veclen */
6549
6550 dx *= (2.0f * mrad - 1.0f);
6551 dy *= (2.0f * mrad - 1.0f);
6552
6553 mrad = dx * dx + dy * dy;
6554 if (mrad < radsq) {
6555 fp[0] = dx;
6556 fp[1] = dy;
6557 fp[2] = -sqrtf(radsq - dx * dx - dy * dy);
6558 }
6559 }
6560 normalize_v3(fp);
6561
6562 if (snap != SNAP_OFF) {
6563 const int snap_steps = (snap == SNAP_ON) ? 4 : 12; /* 45 or 15 degree increments */
6564 const float snap_steps_angle = M_PI / snap_steps;
6565 float angle, angle_snap;
6566
6567 /* round each axis of 'fp' to the next increment
6568 * do this in "angle" space - this gives increments of same size */
6569 for (int i = 0; i < 3; i++) {
6570 angle = asinf(fp[i]);
6571 angle_snap = roundf(angle / snap_steps_angle) * snap_steps_angle;
6572 fp[i] = sinf(angle_snap);
6573 }
6574 normalize_v3(fp);
6575 changed = !compare_v3v3(fp, data->origvec, FLT_EPSILON);
6576 }
6577
6578 data->draglastx = mx;
6579 data->draglasty = my;
6580
6581 return changed;
6582}
6583
6584static void ui_palette_set_active(uiButColor *color_but)
6585{
6586 if (color_but->is_pallete_color) {
6587 Palette *palette = (Palette *)color_but->rnapoin.owner_id;
6588 const PaletteColor *color = static_cast<const PaletteColor *>(color_but->rnapoin.data);
6589 palette->active_color = BLI_findindex(&palette->colors, color);
6590 }
6591}
6592
6594{
6596 uiButColor *color_but = (uiButColor *)but;
6597
6598 if (data->state == BUTTON_STATE_HIGHLIGHT) {
6599 /* First handle click on icon-drag type button. */
6600 if (event->type == LEFTMOUSE && ui_but_drag_is_draggable(but) && event->val == KM_PRESS) {
6601 ui_palette_set_active(color_but);
6602 if (ui_but_contains_point_px_icon(but, data->region, event)) {
6604 data->dragstartx = event->xy[0];
6605 data->dragstarty = event->xy[1];
6606 return WM_UI_HANDLER_BREAK;
6607 }
6608 }
6609#ifdef USE_DRAG_TOGGLE
6610 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
6611 ui_palette_set_active(color_but);
6613 data->dragstartx = event->xy[0];
6614 data->dragstarty = event->xy[1];
6615 return WM_UI_HANDLER_BREAK;
6616 }
6617#endif
6618 /* regular open menu */
6619 if (ELEM(event->type, LEFTMOUSE, EVT_PADENTER, EVT_RETKEY) && event->val == KM_PRESS) {
6620 ui_palette_set_active(color_but);
6622 return WM_UI_HANDLER_BREAK;
6623 }
6624 if (ELEM(event->type, MOUSEPAN, WHEELDOWNMOUSE, WHEELUPMOUSE) && (event->modifier & KM_CTRL)) {
6625 ColorPicker *cpicker = static_cast<ColorPicker *>(but->custom_data);
6626 float hsv_static[3] = {0.0f};
6627 float *hsv = cpicker ? cpicker->hsv_perceptual : hsv_static;
6628 float col[3];
6629
6630 ui_but_v3_get(but, col);
6632
6633 if (event->type == WHEELDOWNMOUSE) {
6634 hsv[2] = clamp_f(hsv[2] - 0.05f, 0.0f, 1.0f);
6635 }
6636 else if (event->type == WHEELUPMOUSE) {
6637 hsv[2] = clamp_f(hsv[2] + 0.05f, 0.0f, 1.0f);
6638 }
6639 else {
6640 const float fac = 0.005 * (event->xy[1] - event->prev_xy[1]);
6641 hsv[2] = clamp_f(hsv[2] + fac, 0.0f, 1.0f);
6642 }
6643
6644 hsv_to_rgb_v(hsv, data->vec);
6645 ui_but_v3_set(but, data->vec);
6646
6648 ui_apply_but(C, but->block, but, data, true);
6649 return WM_UI_HANDLER_BREAK;
6650 }
6651 if (color_but->is_pallete_color && (event->type == EVT_DELKEY) && (event->val == KM_PRESS)) {
6652 Palette *palette = (Palette *)but->rnapoin.owner_id;
6653 PaletteColor *color = static_cast<PaletteColor *>(but->rnapoin.data);
6654
6655 BKE_palette_color_remove(palette, color);
6656
6658
6659 /* this is risky. it works OK for now,
6660 * but if it gives trouble we should delay execution */
6661 but->rnapoin = PointerRNA_NULL;
6662 but->rnaprop = nullptr;
6663
6664 return WM_UI_HANDLER_BREAK;
6665 }
6666 }
6667 else if (data->state == BUTTON_STATE_WAIT_DRAG) {
6668
6669 /* this function also ends state */
6670 if (ui_but_drag_init(C, but, data, event)) {
6671 return WM_UI_HANDLER_BREAK;
6672 }
6673
6674 /* outside icon quit, not needed if drag activated */
6675 if (0 == ui_but_contains_point_px_icon(but, data->region, event)) {
6677 data->cancel = true;
6678 return WM_UI_HANDLER_BREAK;
6679 }
6680
6681 if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
6682 if (color_but->is_pallete_color) {
6683 if ((event->modifier & KM_CTRL) == 0) {
6684 float color[3];
6686 if (paint != nullptr) {
6687 Brush *brush = BKE_paint_brush(paint);
6688
6689 if (brush == nullptr) {
6690 /* Pass. */
6691 }
6692 else if (brush->flag & BRUSH_USE_GRADIENT) {
6693 float *target = &brush->gradient->data[brush->gradient->cur].r;
6694
6698 }
6699 else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) {
6701 }
6703 }
6704 else {
6705 bool updated = false;
6706
6709 &but->rnapoin, but->rnaprop, color, ARRAY_SIZE(color));
6711 BKE_brush_color_set(paint, brush, color);
6712 updated = true;
6713 }
6714 else if (but->rnaprop && RNA_property_subtype(but->rnaprop) == PROP_COLOR) {
6716 &but->rnapoin, but->rnaprop, color, ARRAY_SIZE(color));
6717 BKE_brush_color_set(paint, brush, color);
6718 updated = true;
6719 }
6720
6721 if (updated) {
6722 PropertyRNA *brush_color_prop;
6723
6724 PointerRNA brush_ptr = RNA_id_pointer_create(&brush->id);
6725 brush_color_prop = RNA_struct_find_property(&brush_ptr, "color");
6726 RNA_property_update(C, &brush_ptr, brush_color_prop);
6727 }
6728 }
6729 }
6730
6732 }
6733 else {
6735 }
6736 }
6737 else {
6739 }
6740 return WM_UI_HANDLER_BREAK;
6741 }
6742 }
6743
6745}
6746
6748 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
6749{
6750 int mx = event->xy[0];
6751 int my = event->xy[1];
6752 ui_window_to_block(data->region, block, &mx, &my);
6753
6754 if (data->state == BUTTON_STATE_HIGHLIGHT) {
6755 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
6756 const enum eSnapType snap = ui_event_to_snap(event);
6757 data->dragstartx = mx;
6758 data->dragstarty = my;
6759 data->draglastx = mx;
6760 data->draglasty = my;
6762
6763 /* also do drag the first time */
6764 if (ui_numedit_but_UNITVEC(but, data, mx, my, snap)) {
6765 ui_numedit_apply(C, block, but, data);
6766 }
6767
6768 return WM_UI_HANDLER_BREAK;
6769 }
6770 }
6771 else if (data->state == BUTTON_STATE_NUM_EDITING) {
6772 if ((event->type == MOUSEMOVE) || ui_event_is_snap(event)) {
6773 if (mx != data->draglastx || my != data->draglasty || event->type != MOUSEMOVE) {
6774 const enum eSnapType snap = ui_event_to_snap(event);
6775 if (ui_numedit_but_UNITVEC(but, data, mx, my, snap)) {
6776 ui_numedit_apply(C, block, but, data);
6777 }
6778 }
6779 }
6780 else if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) {
6781 if (event->val == KM_PRESS) {
6782 data->cancel = true;
6783 data->escapecancel = true;
6785 }
6786 }
6787 else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
6789 }
6790
6791 return WM_UI_HANDLER_BREAK;
6792 }
6793
6795}
6796
6797/* scales a vector so no axis exceeds max
6798 * (could become BLI_math func) */
6799static void clamp_axis_max_v3(float v[3], const float max)
6800{
6801 const float v_max = max_fff(v[0], v[1], v[2]);
6802 if (v_max > max) {
6803 mul_v3_fl(v, max / v_max);
6804 v[0] = std::min(v[0], max);
6805 v[1] = std::min(v[1], max);
6806 v[2] = std::min(v[2], max);
6807 }
6808}
6809
6811 const float rgb[3],
6812 float hsv[3])
6813{
6814 if (hsv_but->gradient_type == UI_GRAD_L_ALT) {
6815 rgb_to_hsl_compat_v(rgb, hsv);
6816 }
6817 else {
6818 rgb_to_hsv_compat_v(rgb, hsv);
6819 }
6820}
6821
6823 const float rgb[3],
6824 float hsv[3])
6825{
6826 if (hsv_but->gradient_type == UI_GRAD_L_ALT) {
6827 rgb_to_hsl_v(rgb, hsv);
6828 }
6829 else {
6830 rgb_to_hsv_v(rgb, hsv);
6831 }
6832}
6833
6835 const float hsv[3],
6836 float rgb[3])
6837{
6838 if (hsv_but->gradient_type == UI_GRAD_L_ALT) {
6839 hsl_to_rgb_v(hsv, rgb);
6840 }
6841 else {
6842 hsv_to_rgb_v(hsv, rgb);
6843 }
6844}
6845
6848 int mx,
6849 int my,
6850 const enum eSnapType snap,
6851 const bool shift)
6852{
6853 const uiButHSVCube *hsv_but = (uiButHSVCube *)but;
6854 ColorPicker *cpicker = static_cast<ColorPicker *>(but->custom_data);
6855 float *hsv = cpicker->hsv_perceptual;
6856 float rgb[3];
6857 float x, y;
6858 float mx_fl, my_fl;
6859 const bool changed = true;
6860
6861 ui_mouse_scale_warp(data, mx, my, &mx_fl, &my_fl, shift);
6862
6863#ifdef USE_CONT_MOUSE_CORRECT
6864 if (ui_but_is_cursor_warp(but)) {
6865 /* OK but can go outside bounds */
6866 data->ungrab_mval[0] = mx_fl;
6867 data->ungrab_mval[1] = my_fl;
6868 BLI_rctf_clamp_pt_v(&but->rect, data->ungrab_mval);
6869 }
6870#endif
6871
6872 /* Always start from original value to avoid numerical drift. */
6873 copy_v3_v3(rgb, data->origvec);
6875
6876 ui_rgb_to_color_picker_HSVCUBE_compat_v(hsv_but, rgb, hsv);
6877
6878 /* only apply the delta motion, not absolute */
6879 if (shift) {
6880 rcti rect_i;
6881 float xpos, ypos, hsvo[3];
6882
6883 BLI_rcti_rctf_copy(&rect_i, &but->rect);
6884
6885 /* calculate original hsv again */
6886 copy_v3_v3(rgb, data->origvec);
6888
6889 copy_v3_v3(hsvo, hsv);
6890
6891 ui_rgb_to_color_picker_HSVCUBE_compat_v(hsv_but, rgb, hsvo);
6892
6893 /* and original position */
6894 ui_hsvcube_pos_from_vals(hsv_but, &rect_i, hsvo, &xpos, &ypos);
6895
6896 mx_fl = xpos - (data->dragstartx - mx_fl);
6897 my_fl = ypos - (data->dragstarty - my_fl);
6898 }
6899
6900 /* relative position within box */
6901 x = (mx_fl - but->rect.xmin) / BLI_rctf_size_x(&but->rect);
6902 y = (my_fl - but->rect.ymin) / BLI_rctf_size_y(&but->rect);
6903 CLAMP(x, 0.0f, 1.0f);
6904 CLAMP(y, 0.0f, 1.0f);
6905
6906 switch (hsv_but->gradient_type) {
6907 case UI_GRAD_SV:
6908 hsv[1] = x;
6909 hsv[2] = y;
6910 break;
6911 case UI_GRAD_HV:
6912 hsv[0] = x;
6913 hsv[2] = y;
6914 break;
6915 case UI_GRAD_HS:
6916 hsv[0] = x;
6917 hsv[1] = y;
6918 break;
6919 case UI_GRAD_H:
6920 hsv[0] = x;
6921 break;
6922 case UI_GRAD_S:
6923 hsv[1] = x;
6924 break;
6925 case UI_GRAD_V:
6926 hsv[2] = x;
6927 break;
6928 case UI_GRAD_L_ALT:
6929 hsv[2] = y;
6930 break;
6931 case UI_GRAD_V_ALT: {
6932 /* vertical 'value' strip */
6933 const float min = but->softmin, max = but->softmax;
6934 /* exception only for value strip - use the range set in but->min/max */
6935 hsv[2] = y * (max - min) + min;
6936 break;
6937 }
6938 default:
6939 BLI_assert(0);
6940 break;
6941 }
6942
6943 if (snap != SNAP_OFF) {
6945 ui_color_snap_hue(snap, &hsv[0]);
6946 }
6947 }
6948
6949 ui_color_picker_to_rgb_HSVCUBE_v(hsv_but, hsv, rgb);
6951
6952 /* clamp because with color conversion we can exceed range #34295. */
6953 if (hsv_but->gradient_type == UI_GRAD_V_ALT) {
6954 clamp_axis_max_v3(rgb, but->softmax);
6955 }
6956
6957 copy_v3_v3(data->vec, rgb);
6958
6959 data->draglastx = mx;
6960 data->draglasty = my;
6961
6962 return changed;
6963}
6964
6965#ifdef WITH_INPUT_NDOF
6966static void ui_ndofedit_but_HSVCUBE(uiButHSVCube *hsv_but,
6968 const wmNDOFMotionData &ndof,
6969 const enum eSnapType snap,
6970 const bool shift)
6971{
6972 ColorPicker *cpicker = static_cast<ColorPicker *>(hsv_but->custom_data);
6973 float *hsv = cpicker->hsv_perceptual;
6974 const float hsv_v_max = max_ff(hsv[2], hsv_but->softmax);
6975 float rgb[3];
6976 const float sensitivity = (shift ? 0.15f : 0.3f) * ndof.time_delta;
6977
6978 ui_but_v3_get(hsv_but, rgb);
6980 ui_rgb_to_color_picker_HSVCUBE_compat_v(hsv_but, rgb, hsv);
6981
6982 switch (hsv_but->gradient_type) {
6983 case UI_GRAD_SV:
6984 hsv[1] += ndof.rvec[2] * sensitivity;
6985 hsv[2] += ndof.rvec[0] * sensitivity;
6986 break;
6987 case UI_GRAD_HV:
6988 hsv[0] += ndof.rvec[2] * sensitivity;
6989 hsv[2] += ndof.rvec[0] * sensitivity;
6990 break;
6991 case UI_GRAD_HS:
6992 hsv[0] += ndof.rvec[2] * sensitivity;
6993 hsv[1] += ndof.rvec[0] * sensitivity;
6994 break;
6995 case UI_GRAD_H:
6996 hsv[0] += ndof.rvec[2] * sensitivity;
6997 break;
6998 case UI_GRAD_S:
6999 hsv[1] += ndof.rvec[2] * sensitivity;
7000 break;
7001 case UI_GRAD_V:
7002 hsv[2] += ndof.rvec[2] * sensitivity;
7003 break;
7004 case UI_GRAD_V_ALT:
7005 case UI_GRAD_L_ALT:
7006 /* vertical 'value' strip */
7007
7008 /* exception only for value strip - use the range set in but->min/max */
7009 hsv[2] += ndof.rvec[0] * sensitivity;
7010
7011 CLAMP(hsv[2], hsv_but->softmin, hsv_but->softmax);
7012 break;
7013 default:
7014 BLI_assert_msg(0, "invalid hsv type");
7015 break;
7016 }
7017
7018 if (snap != SNAP_OFF) {
7019 if (ELEM(hsv_but->gradient_type, UI_GRAD_HV, UI_GRAD_HS, UI_GRAD_H)) {
7020 ui_color_snap_hue(snap, &hsv[0]);
7021 }
7022 }
7023
7024 /* ndof specific: the changes above aren't clamping */
7025 hsv_clamp_v(hsv, hsv_v_max);
7026
7027 ui_color_picker_to_rgb_HSVCUBE_v(hsv_but, hsv, rgb);
7029
7030 copy_v3_v3(data->vec, rgb);
7031 ui_but_v3_set(hsv_but, data->vec);
7032}
7033#endif /* WITH_INPUT_NDOF */
7034
7036 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
7037{
7038 uiButHSVCube *hsv_but = (uiButHSVCube *)but;
7039 int mx = event->xy[0];
7040 int my = event->xy[1];
7041 ui_window_to_block(data->region, block, &mx, &my);
7042
7043 if (data->state == BUTTON_STATE_HIGHLIGHT) {
7044 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
7045 const enum eSnapType snap = ui_event_to_snap(event);
7046
7047 data->dragstartx = mx;
7048 data->dragstarty = my;
7049 data->draglastx = mx;
7050 data->draglasty = my;
7052
7053 /* also do drag the first time */
7054 if (ui_numedit_but_HSVCUBE(but, data, mx, my, snap, event->modifier & KM_SHIFT)) {
7055 ui_numedit_apply(C, block, but, data);
7056 }
7057
7058 return WM_UI_HANDLER_BREAK;
7059 }
7060#ifdef WITH_INPUT_NDOF
7061 if (event->type == NDOF_MOTION) {
7062 const wmNDOFMotionData &ndof = *static_cast<const wmNDOFMotionData *>(event->customdata);
7063 const enum eSnapType snap = ui_event_to_snap(event);
7064
7065 ui_ndofedit_but_HSVCUBE(hsv_but, data, ndof, snap, event->modifier & KM_SHIFT);
7066
7068 ui_apply_but(C, but->block, but, data, true);
7069
7070 return WM_UI_HANDLER_BREAK;
7071 }
7072#endif /* WITH_INPUT_NDOF */
7073 /* XXX hardcoded keymap check.... */
7074 if (event->type == EVT_BACKSPACEKEY && event->val == KM_PRESS) {
7076 int len;
7077
7078 /* reset only value */
7079
7081 if (ELEM(len, 3, 4)) {
7082 float rgb[3], def_hsv[3];
7083 float def[4];
7084 ColorPicker *cpicker = static_cast<ColorPicker *>(but->custom_data);
7085 float *hsv = cpicker->hsv_perceptual;
7086
7088 ui_rgb_to_color_picker_HSVCUBE_v(hsv_but, def, def_hsv);
7089
7090 ui_but_v3_get(but, rgb);
7091 ui_rgb_to_color_picker_HSVCUBE_compat_v(hsv_but, rgb, hsv);
7092
7093 def_hsv[0] = hsv[0];
7094 def_hsv[1] = hsv[1];
7095
7096 ui_color_picker_to_rgb_HSVCUBE_v(hsv_but, def_hsv, rgb);
7097 ui_but_v3_set(but, rgb);
7098 ui_apply_but_func(C, but);
7099
7100 RNA_property_update(C, &but->rnapoin, but->rnaprop);
7101 return WM_UI_HANDLER_BREAK;
7102 }
7103 }
7104 }
7105 }
7106 else if (data->state == BUTTON_STATE_NUM_EDITING) {
7107 if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) {
7108 if (event->val == KM_PRESS) {
7109 data->cancel = true;
7110 data->escapecancel = true;
7112 }
7113 }
7114 else if ((event->type == MOUSEMOVE) || ui_event_is_snap(event)) {
7115 if (mx != data->draglastx || my != data->draglasty || event->type != MOUSEMOVE) {
7116 const enum eSnapType snap = ui_event_to_snap(event);
7117
7118 if (ui_numedit_but_HSVCUBE(but, data, mx, my, snap, event->modifier & KM_SHIFT)) {
7119 ui_numedit_apply(C, block, but, data);
7120 }
7121 }
7122 }
7123 else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
7125 }
7126
7127 return WM_UI_HANDLER_BREAK;
7128 }
7129
7131}
7132
7135 float mx,
7136 float my,
7137 const enum eSnapType snap,
7138 const bool shift)
7139{
7140 const bool changed = true;
7141 ColorPicker *cpicker = static_cast<ColorPicker *>(but->custom_data);
7142 float *hsv = cpicker->hsv_perceptual;
7143
7144 float mx_fl, my_fl;
7145 ui_mouse_scale_warp(data, mx, my, &mx_fl, &my_fl, shift);
7146
7147#ifdef USE_CONT_MOUSE_CORRECT
7148 if (ui_but_is_cursor_warp(but)) {
7149 /* OK but can go outside bounds */
7150 data->ungrab_mval[0] = mx_fl;
7151 data->ungrab_mval[1] = my_fl;
7152 { /* clamp */
7153 const float radius = min_ff(BLI_rctf_size_x(&but->rect), BLI_rctf_size_y(&but->rect)) / 2.0f;
7154 const float cent[2] = {BLI_rctf_cent_x(&but->rect), BLI_rctf_cent_y(&but->rect)};
7155 const float len = len_v2v2(cent, data->ungrab_mval);
7156 if (len > radius) {
7157 dist_ensure_v2_v2fl(data->ungrab_mval, cent, radius);
7158 }
7159 }
7160 }
7161#endif
7162
7163 rcti rect;
7164 BLI_rcti_rctf_copy(&rect, &but->rect);
7165
7166 /* Always start from original value to avoid numerical drift. */
7167 float rgb[3];
7168 copy_v3_v3(rgb, data->origvec);
7171
7172 /* exception, when using color wheel in 'locked' value state:
7173 * allow choosing a hue for black values, by giving a tiny increment */
7174 if (cpicker->use_color_lock) {
7175 if (U.color_picker_type == USER_CP_CIRCLE_HSV) { /* lock */
7176 if (hsv[2] == 0.0f) {
7177 hsv[2] = 0.0001f;
7178 }
7179 }
7180 else {
7181 if (hsv[2] == 0.0f) {
7182 hsv[2] = 0.0001f;
7183 }
7184 hsv[2] = std::min(hsv[2], 0.9999f);
7185 }
7186 }
7187
7188 /* only apply the delta motion, not absolute */
7189 if (shift) {
7190 float xpos, ypos, hsvo[3], rgbo[3];
7191
7192 /* calculate original hsv again */
7193 copy_v3_v3(hsvo, hsv);
7194 copy_v3_v3(rgbo, data->origvec);
7197
7198 /* and original position */
7199 ui_hsvcircle_pos_from_vals(cpicker, &rect, hsvo, &xpos, &ypos);
7200
7201 mx_fl = xpos - (data->dragstartx - mx_fl);
7202 my_fl = ypos - (data->dragstarty - my_fl);
7203 }
7204
7205 ui_hsvcircle_vals_from_pos(&rect, mx_fl, my_fl, hsv, hsv + 1);
7206
7207 if ((cpicker->use_color_cubic) && (U.color_picker_type == USER_CP_CIRCLE_HSV)) {
7208 hsv[1] = 1.0f - sqrt3f(1.0f - hsv[1]);
7209 }
7210
7211 if (snap != SNAP_OFF) {
7212 ui_color_snap_hue(snap, &hsv[0]);
7213 }
7214
7216
7217 if (cpicker->use_luminosity_lock) {
7218 if (!is_zero_v3(rgb)) {
7220 }
7221 }
7222
7224 ui_but_v3_set(but, rgb);
7225
7226 data->draglastx = mx;
7227 data->draglasty = my;
7228
7229 return changed;
7230}
7231
7232#ifdef WITH_INPUT_NDOF
7233static void ui_ndofedit_but_HSVCIRCLE(uiBut *but,
7235 const wmNDOFMotionData &ndof,
7236 const enum eSnapType snap,
7237 const bool shift)
7238{
7239 ColorPicker *cpicker = static_cast<ColorPicker *>(but->custom_data);
7240 float *hsv = cpicker->hsv_perceptual;
7241 float rgb[3];
7242 float phi, r, v[2];
7243 const float sensitivity = (shift ? 0.06f : 0.3f) * ndof.time_delta;
7244
7245 ui_but_v3_get(but, rgb);
7248
7249 /* Convert current color on hue/sat disc to circular coordinates phi, r */
7250 phi = fmodf(hsv[0] + 0.25f, 1.0f) * -2.0f * float(M_PI);
7251 r = hsv[1];
7252 // const float sqr = r > 0.0f ? sqrtf(r) : 1; /* UNUSED */
7253
7254 /* Convert to 2d vectors */
7255 v[0] = r * cosf(phi);
7256 v[1] = r * sinf(phi);
7257
7258 /* Use ndof device y and x rotation to move the vector in 2d space */
7259 v[0] += ndof.rvec[2] * sensitivity;
7260 v[1] += ndof.rvec[0] * sensitivity;
7261
7262 /* convert back to polar coords on circle */
7263 phi = atan2f(v[0], v[1]) / (2.0f * float(M_PI)) + 0.5f;
7264
7265 /* use ndof Y rotation to additionally rotate hue */
7266 phi += ndof.rvec[1] * sensitivity * 0.5f;
7267 r = len_v2(v);
7268
7269 /* convert back to hsv values, in range [0,1] */
7270 hsv[0] = phi;
7271 hsv[1] = r;
7272
7273 /* exception, when using color wheel in 'locked' value state:
7274 * allow choosing a hue for black values, by giving a tiny increment */
7275 if (cpicker->use_color_lock) {
7276 if (U.color_picker_type == USER_CP_CIRCLE_HSV) { /* lock */
7277 if (hsv[2] == 0.0f) {
7278 hsv[2] = 0.0001f;
7279 }
7280 }
7281 else {
7282 if (hsv[2] == 0.0f) {
7283 hsv[2] = 0.0001f;
7284 }
7285 if (hsv[2] == 1.0f) {
7286 hsv[2] = 0.9999f;
7287 }
7288 }
7289 }
7290
7291 if (snap != SNAP_OFF) {
7292 ui_color_snap_hue(snap, &hsv[0]);
7293 }
7294
7295 hsv_clamp_v(hsv, FLT_MAX);
7296
7298
7299 if (cpicker->use_luminosity_lock) {
7300 if (!is_zero_v3(data->vec)) {
7301 normalize_v3_length(data->vec, cpicker->luminosity_lock_value);
7302 }
7303 }
7304
7306 ui_but_v3_set(but, data->vec);
7307}
7308#endif /* WITH_INPUT_NDOF */
7309
7311 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
7312{
7313 ColorPicker *cpicker = static_cast<ColorPicker *>(but->custom_data);
7314 float *hsv = cpicker->hsv_perceptual;
7315 int mx = event->xy[0];
7316 int my = event->xy[1];
7317 ui_window_to_block(data->region, block, &mx, &my);
7318
7319 if (data->state == BUTTON_STATE_HIGHLIGHT) {
7320 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
7321 const enum eSnapType snap = ui_event_to_snap(event);
7322 data->dragstartx = mx;
7323 data->dragstarty = my;
7324 data->draglastx = mx;
7325 data->draglasty = my;
7327
7328 /* also do drag the first time */
7329 if (ui_numedit_but_HSVCIRCLE(but, data, mx, my, snap, event->modifier & KM_SHIFT)) {
7330 ui_numedit_apply(C, block, but, data);
7331 }
7332
7333 return WM_UI_HANDLER_BREAK;
7334 }
7335#ifdef WITH_INPUT_NDOF
7336 if (event->type == NDOF_MOTION) {
7337 const enum eSnapType snap = ui_event_to_snap(event);
7338 const wmNDOFMotionData &ndof = *static_cast<const wmNDOFMotionData *>(event->customdata);
7339
7340 ui_ndofedit_but_HSVCIRCLE(but, data, ndof, snap, event->modifier & KM_SHIFT);
7341
7343 ui_apply_but(C, but->block, but, data, true);
7344
7345 return WM_UI_HANDLER_BREAK;
7346 }
7347#endif /* WITH_INPUT_NDOF */
7348 /* XXX hardcoded keymap check.... */
7349 if (event->type == EVT_BACKSPACEKEY && event->val == KM_PRESS) {
7350 int len;
7351
7352 /* reset only saturation */
7353
7355 if (len >= 3) {
7356 float rgb[3], def_hsv[3];
7357 float *def = MEM_calloc_arrayN<float>(len, __func__);
7358
7360 ui_color_picker_hsv_to_rgb(def, def_hsv);
7361
7362 ui_but_v3_get(but, rgb);
7364
7365 def_hsv[0] = hsv[0];
7366 def_hsv[2] = hsv[2];
7367
7368 hsv_to_rgb_v(def_hsv, rgb);
7369 ui_but_v3_set(but, rgb);
7370 ui_apply_but_func(C, but);
7371
7372 RNA_property_update(C, &but->rnapoin, but->rnaprop);
7373
7374 MEM_freeN(def);
7375 }
7376 return WM_UI_HANDLER_BREAK;
7377 }
7378 }
7379 else if (data->state == BUTTON_STATE_NUM_EDITING) {
7380 if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) {
7381 if (event->val == KM_PRESS) {
7382 data->cancel = true;
7383 data->escapecancel = true;
7385 }
7386 }
7387 /* XXX hardcoded keymap check.... */
7388 else if (event->type == WHEELDOWNMOUSE) {
7389 hsv[2] = clamp_f(hsv[2] - 0.05f, 0.0f, 1.0f);
7390 ui_but_hsv_set(but); /* converts to rgb */
7391 ui_numedit_apply(C, block, but, data);
7392 }
7393 else if (event->type == WHEELUPMOUSE) {
7394 hsv[2] = clamp_f(hsv[2] + 0.05f, 0.0f, 1.0f);
7395 ui_but_hsv_set(but); /* converts to rgb */
7396 ui_numedit_apply(C, block, but, data);
7397 }
7398 else if ((event->type == MOUSEMOVE) || ui_event_is_snap(event)) {
7399 if (mx != data->draglastx || my != data->draglasty || event->type != MOUSEMOVE) {
7400 const enum eSnapType snap = ui_event_to_snap(event);
7401
7402 if (ui_numedit_but_HSVCIRCLE(but, data, mx, my, snap, event->modifier & KM_SHIFT)) {
7403 ui_numedit_apply(C, block, but, data);
7404 }
7405 }
7406 }
7407 else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
7409 }
7410 return WM_UI_HANDLER_BREAK;
7411 }
7412
7414}
7415
7417{
7418 bool changed = false;
7419
7420 if (data->draglastx == mx) {
7421 return changed;
7422 }
7423
7424 if (data->coba->tot == 0) {
7425 return changed;
7426 }
7427
7428 const float dx = float(mx - data->draglastx) / BLI_rctf_size_x(&but->rect);
7429 data->dragcbd->pos += dx;
7430 CLAMP(data->dragcbd->pos, 0.0f, 1.0f);
7431
7433 data->dragcbd = data->coba->data + data->coba->cur; /* because qsort */
7434
7435 data->draglastx = mx;
7436 changed = true;
7437
7438 return changed;
7439}
7440
7442 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
7443{
7444 int mx = event->xy[0];
7445 int my = event->xy[1];
7446 ui_window_to_block(data->region, block, &mx, &my);
7447
7448 if (data->state == BUTTON_STATE_HIGHLIGHT) {
7449 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
7450 ColorBand *coba = (ColorBand *)but->poin;
7451
7452 if (event->modifier & KM_CTRL) {
7453 /* insert new key on mouse location */
7454 const float pos = float(mx - but->rect.xmin) / BLI_rctf_size_x(&but->rect);
7457 }
7458 else {
7459 CBData *cbd;
7460 /* ignore zoom-level for mindist */
7461 int mindist = (50 * UI_SCALE_FAC) * block->aspect;
7462 int xco;
7463 data->dragstartx = mx;
7464 data->dragstarty = my;
7465 data->draglastx = mx;
7466 data->draglasty = my;
7467
7468 /* activate new key when mouse is close */
7469 int a;
7470 for (a = 0, cbd = coba->data; a < coba->tot; a++, cbd++) {
7471 xco = but->rect.xmin + (cbd->pos * BLI_rctf_size_x(&but->rect));
7472 xco = abs(xco - mx);
7473 if (a == coba->cur) {
7474 /* Selected one disadvantage. */
7475 xco += 5;
7476 }
7477 if (xco < mindist) {
7478 coba->cur = a;
7479 mindist = xco;
7480 }
7481 }
7482
7483 data->dragcbd = coba->data + coba->cur;
7484 data->dragfstart = data->dragcbd->pos;
7486 }
7487
7488 return WM_UI_HANDLER_BREAK;
7489 }
7490 }
7491 else if (data->state == BUTTON_STATE_NUM_EDITING) {
7492 if (event->type == MOUSEMOVE) {
7493 if (mx != data->draglastx || my != data->draglasty) {
7494 if (ui_numedit_but_COLORBAND(but, data, mx)) {
7495 ui_numedit_apply(C, block, but, data);
7496 }
7497 }
7498 }
7499 else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
7501 }
7502 else if (ELEM(event->type, EVT_ESCKEY, RIGHTMOUSE)) {
7503 if (event->val == KM_PRESS) {
7504 data->dragcbd->pos = data->dragfstart;
7506 data->cancel = true;
7507 data->escapecancel = true;
7509 }
7510 }
7511 return WM_UI_HANDLER_BREAK;
7512 }
7513
7515}
7516
7518 uiBut *but,
7520 int evtx,
7521 int evty,
7522 bool snap,
7523 const bool shift)
7524{
7525 CurveMapping *cumap = (CurveMapping *)but->poin;
7526 CurveMap *cuma = cumap->cm + cumap->cur;
7527 CurveMapPoint *cmp = cuma->curve;
7528 bool changed = false;
7529
7530 /* evtx evty and drag coords are absolute mouse-coords,
7531 * prevents errors when editing when layout changes. */
7532 int mx = evtx;
7533 int my = evty;
7534 ui_window_to_block(data->region, block, &mx, &my);
7535 int dragx = data->draglastx;
7536 int dragy = data->draglasty;
7537 ui_window_to_block(data->region, block, &dragx, &dragy);
7538
7539 const float zoomx = BLI_rctf_size_x(&but->rect) / BLI_rctf_size_x(&cumap->curr);
7540 const float zoomy = BLI_rctf_size_y(&but->rect) / BLI_rctf_size_y(&cumap->curr);
7541
7542 if (snap) {
7543 float d[2];
7544
7545 d[0] = mx - data->dragstartx;
7546 d[1] = my - data->dragstarty;
7547
7548 if (len_squared_v2(d) < (3.0f * 3.0f)) {
7549 snap = false;
7550 }
7551 }
7552
7553 float fx = (mx - dragx) / zoomx;
7554 float fy = (my - dragy) / zoomy;
7555
7556 if (data->dragsel != -1) {
7557 CurveMapPoint *cmp_last = nullptr;
7558 const float mval_factor = ui_mouse_scale_warp_factor(shift);
7559 bool moved_point = false; /* for ctrl grid, can't use orig coords because of sorting */
7560
7561 fx *= mval_factor;
7562 fy *= mval_factor;
7563
7564 for (int a = 0; a < cuma->totpoint; a++) {
7565 if (cmp[a].flag & CUMA_SELECT) {
7566 const float origx = cmp[a].x, origy = cmp[a].y;
7567 cmp[a].x += fx;
7568 cmp[a].y += fy;
7569 if (snap) {
7570 cmp[a].x = 0.125f * roundf(8.0f * cmp[a].x);
7571 cmp[a].y = 0.125f * roundf(8.0f * cmp[a].y);
7572 }
7573 if (cmp[a].x != origx || cmp[a].y != origy) {
7574 moved_point = true;
7575 }
7576
7577 cmp_last = &cmp[a];
7578 }
7579 }
7580
7581 BKE_curvemapping_changed(cumap, false);
7582
7583 if (moved_point) {
7584 data->draglastx = evtx;
7585 data->draglasty = evty;
7586 changed = true;
7587
7588#ifdef USE_CONT_MOUSE_CORRECT
7589 /* NOTE: using 'cmp_last' is weak since there may be multiple points selected,
7590 * but in practice this isn't really an issue */
7591 if (ui_but_is_cursor_warp(but)) {
7592 /* OK but can go outside bounds */
7593 data->ungrab_mval[0] = but->rect.xmin + ((cmp_last->x - cumap->curr.xmin) * zoomx);
7594 data->ungrab_mval[1] = but->rect.ymin + ((cmp_last->y - cumap->curr.ymin) * zoomy);
7595 BLI_rctf_clamp_pt_v(&but->rect, data->ungrab_mval);
7596 }
7597#endif
7598 }
7599
7600 data->dragchange = true; /* mark for selection */
7601 }
7602 else {
7603 /* clamp for clip */
7604 if (cumap->flag & CUMA_DO_CLIP) {
7605 if (cumap->curr.xmin - fx < cumap->clipr.xmin) {
7606 fx = cumap->curr.xmin - cumap->clipr.xmin;
7607 }
7608 else if (cumap->curr.xmax - fx > cumap->clipr.xmax) {
7609 fx = cumap->curr.xmax - cumap->clipr.xmax;
7610 }
7611 if (cumap->curr.ymin - fy < cumap->clipr.ymin) {
7612 fy = cumap->curr.ymin - cumap->clipr.ymin;
7613 }
7614 else if (cumap->curr.ymax - fy > cumap->clipr.ymax) {
7615 fy = cumap->curr.ymax - cumap->clipr.ymax;
7616 }
7617 }
7618
7619 cumap->curr.xmin -= fx;
7620 cumap->curr.ymin -= fy;
7621 cumap->curr.xmax -= fx;
7622 cumap->curr.ymax -= fy;
7623
7624 data->draglastx = evtx;
7625 data->draglasty = evty;
7626
7627 changed = true;
7628 }
7629
7630 return changed;
7631}
7632
7634 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
7635{
7636 bool changed = false;
7637 Scene *scene = CTX_data_scene(C);
7638 ViewLayer *view_layer = CTX_data_view_layer(C);
7639
7640 int mx = event->xy[0];
7641 int my = event->xy[1];
7642 ui_window_to_block(data->region, block, &mx, &my);
7643
7644 if (data->state == BUTTON_STATE_HIGHLIGHT) {
7645 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
7646 CurveMapping *cumap = (CurveMapping *)but->poin;
7647 CurveMap *cuma = cumap->cm + cumap->cur;
7648 const float m_xy[2] = {float(mx), float(my)};
7649 float dist_min_sq = square_f(UI_SCALE_FAC * 14.0f); /* 14 pixels radius */
7650 int sel = -1;
7651
7652 if (event->modifier & KM_CTRL) {
7653 float f_xy[2];
7654 BLI_rctf_transform_pt_v(&cumap->curr, &but->rect, f_xy, m_xy);
7655
7656 BKE_curvemap_insert(cuma, f_xy[0], f_xy[1]);
7657 BKE_curvemapping_changed(cumap, false);
7658 changed = true;
7659 }
7660
7661 /* check for selecting of a point */
7662 CurveMapPoint *cmp = cuma->curve; /* ctrl adds point, new malloc */
7663 for (int a = 0; a < cuma->totpoint; a++) {
7664 float f_xy[2];
7665 BLI_rctf_transform_pt_v(&but->rect, &cumap->curr, f_xy, &cmp[a].x);
7666 const float dist_sq = len_squared_v2v2(m_xy, f_xy);
7667 if (dist_sq < dist_min_sq) {
7668 sel = a;
7669 dist_min_sq = dist_sq;
7670 }
7671 }
7672
7673 if (sel == -1) {
7674 float f_xy[2], f_xy_prev[2];
7675
7676 /* if the click didn't select anything, check if it's clicked on the
7677 * curve itself, and if so, add a point */
7678 cmp = cuma->table;
7679
7680 BLI_rctf_transform_pt_v(&but->rect, &cumap->curr, f_xy, &cmp[0].x);
7681
7682 /* with 160px height 8px should translate to the old 0.05 coefficient at no zoom */
7683 dist_min_sq = square_f(UI_SCALE_FAC * 8.0f);
7684
7685 /* loop through the curve segment table and find what's near the mouse. */
7686 for (int i = 1; i <= CM_TABLE; i++) {
7687 copy_v2_v2(f_xy_prev, f_xy);
7688 BLI_rctf_transform_pt_v(&but->rect, &cumap->curr, f_xy, &cmp[i].x);
7689
7690 if (dist_squared_to_line_segment_v2(m_xy, f_xy_prev, f_xy) < dist_min_sq) {
7691 BLI_rctf_transform_pt_v(&cumap->curr, &but->rect, f_xy, m_xy);
7692
7693 BKE_curvemap_insert(cuma, f_xy[0], f_xy[1]);
7694 BKE_curvemapping_changed(cumap, false);
7695
7696 changed = true;
7697
7698 /* reset cmp back to the curve points again,
7699 * rather than drawing segments */
7700 cmp = cuma->curve;
7701
7702 /* find newly added point and make it 'sel' */
7703 for (int a = 0; a < cuma->totpoint; a++) {
7704 if (cmp[a].x == f_xy[0]) {
7705 sel = a;
7706 }
7707 }
7708 break;
7709 }
7710 }
7711 }
7712
7713 if (sel != -1) {
7714 /* ok, we move a point */
7715 /* deselect all if this one is deselect. except if we hold shift */
7716 if ((event->modifier & KM_SHIFT) == 0) {
7717 for (int a = 0; a < cuma->totpoint; a++) {
7718 cmp[a].flag &= ~CUMA_SELECT;
7719 }
7720 cmp[sel].flag |= CUMA_SELECT;
7721 }
7722 else {
7723 cmp[sel].flag ^= CUMA_SELECT;
7724 }
7725 }
7726 else {
7727 /* move the view */
7728 data->cancel = true;
7729 }
7730
7731 data->dragsel = sel;
7732
7733 data->dragstartx = event->xy[0];
7734 data->dragstarty = event->xy[1];
7735 data->draglastx = event->xy[0];
7736 data->draglasty = event->xy[1];
7737
7739 return WM_UI_HANDLER_BREAK;
7740 }
7741 }
7742 else if (data->state == BUTTON_STATE_NUM_EDITING) {
7743 if (event->type == MOUSEMOVE) {
7744 if (event->xy[0] != data->draglastx || event->xy[1] != data->draglasty) {
7745
7746 if (ui_numedit_but_CURVE(block,
7747 but,
7748 data,
7749 event->xy[0],
7750 event->xy[1],
7751 event->modifier & KM_CTRL,
7752 event->modifier & KM_SHIFT))
7753 {
7754 ui_numedit_apply(C, block, but, data);
7755 }
7756 }
7757 }
7758 else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
7759 if (data->dragsel != -1) {
7760 CurveMapping *cumap = (CurveMapping *)but->poin;
7761 CurveMap *cuma = cumap->cm + cumap->cur;
7762 CurveMapPoint *cmp = cuma->curve;
7763
7764 if (data->dragchange == false) {
7765 /* deselect all, select one */
7766 if ((event->modifier & KM_SHIFT) == 0) {
7767 for (int a = 0; a < cuma->totpoint; a++) {
7768 cmp[a].flag &= ~CUMA_SELECT;
7769 }
7770 cmp[data->dragsel].flag |= CUMA_SELECT;
7771 }
7772 }
7773 else {
7774 BKE_curvemapping_changed(cumap, true); /* remove doubles */
7775 BKE_paint_invalidate_cursor_overlay(scene, view_layer, cumap);
7776 }
7777 }
7778
7780 }
7781
7782 return WM_UI_HANDLER_BREAK;
7783 }
7784
7785 /* UNUSED but keep for now */
7786 (void)changed;
7787
7789}
7790
7791/* Same as ui_numedit_but_CURVE with some smaller changes. */
7793 uiBut *but,
7795 int evtx,
7796 int evty,
7797 bool snap,
7798 const bool shift)
7799{
7800 CurveProfile *profile = (CurveProfile *)but->poin;
7801 CurveProfilePoint *pts = profile->path;
7802 bool changed = false;
7803
7804 /* evtx evty and drag coords are absolute mouse-coords,
7805 * prevents errors when editing when layout changes. */
7806 int mx = evtx;
7807 int my = evty;
7808 ui_window_to_block(data->region, block, &mx, &my);
7809 int dragx = data->draglastx;
7810 int dragy = data->draglasty;
7811 ui_window_to_block(data->region, block, &dragx, &dragy);
7812
7813 const float zoomx = BLI_rctf_size_x(&but->rect) / BLI_rctf_size_x(&profile->view_rect);
7814 const float zoomy = BLI_rctf_size_y(&but->rect) / BLI_rctf_size_y(&profile->view_rect);
7815
7816 if (snap) {
7817 const float d[2] = {float(mx - data->dragstartx), float(data->dragstarty)};
7818 if (len_squared_v2(d) < (9.0f * UI_SCALE_FAC)) {
7819 snap = false;
7820 }
7821 }
7822
7823 float fx = (mx - dragx) / zoomx;
7824 float fy = (my - dragy) / zoomy;
7825
7826 if (data->dragsel != -1) {
7827 float last_x, last_y;
7828 const float mval_factor = ui_mouse_scale_warp_factor(shift);
7829 bool moved_point = false; /* for ctrl grid, can't use orig coords because of sorting */
7830
7831 fx *= mval_factor;
7832 fy *= mval_factor;
7833
7834 /* Move all selected points. */
7835 const float delta[2] = {fx, fy};
7836 for (int a = 0; a < profile->path_len; a++) {
7837 /* Don't move the last and first control points. */
7838 if (pts[a].flag & PROF_SELECT) {
7839 moved_point |= BKE_curveprofile_move_point(profile, &pts[a], snap, delta);
7840 last_x = pts[a].x;
7841 last_y = pts[a].y;
7842 }
7843 else {
7844 /* Move handles when they're selected but the control point isn't. */
7845 if (ELEM(pts[a].h2, HD_FREE, HD_ALIGN) && pts[a].flag == PROF_H1_SELECT) {
7846 moved_point |= BKE_curveprofile_move_handle(&pts[a], true, snap, delta);
7847 last_x = pts[a].h1_loc[0];
7848 last_y = pts[a].h1_loc[1];
7849 }
7850 if (ELEM(pts[a].h2, HD_FREE, HD_ALIGN) && pts[a].flag == PROF_H2_SELECT) {
7851 moved_point |= BKE_curveprofile_move_handle(&pts[a], false, snap, delta);
7852 last_x = pts[a].h2_loc[0];
7853 last_y = pts[a].h2_loc[1];
7854 }
7855 }
7856 }
7857
7859
7860 if (moved_point) {
7861 data->draglastx = evtx;
7862 data->draglasty = evty;
7863 changed = true;
7864#ifdef USE_CONT_MOUSE_CORRECT
7865 /* NOTE: using 'cmp_last' is weak since there may be multiple points selected,
7866 * but in practice this isn't really an issue */
7867 if (ui_but_is_cursor_warp(but)) {
7868 /* OK but can go outside bounds */
7869 data->ungrab_mval[0] = but->rect.xmin + ((last_x - profile->view_rect.xmin) * zoomx);
7870 data->ungrab_mval[1] = but->rect.ymin + ((last_y - profile->view_rect.ymin) * zoomy);
7871 BLI_rctf_clamp_pt_v(&but->rect, data->ungrab_mval);
7872 }
7873#endif
7874 }
7875 data->dragchange = true; /* mark for selection */
7876 }
7877 else {
7878 /* Clamp the view rect when clipping is on. */
7879 if (profile->flag & PROF_USE_CLIP) {
7880 if (profile->view_rect.xmin - fx < profile->clip_rect.xmin) {
7881 fx = profile->view_rect.xmin - profile->clip_rect.xmin;
7882 }
7883 else if (profile->view_rect.xmax - fx > profile->clip_rect.xmax) {
7884 fx = profile->view_rect.xmax - profile->clip_rect.xmax;
7885 }
7886 if (profile->view_rect.ymin - fy < profile->clip_rect.ymin) {
7887 fy = profile->view_rect.ymin - profile->clip_rect.ymin;
7888 }
7889 else if (profile->view_rect.ymax - fy > profile->clip_rect.ymax) {
7890 fy = profile->view_rect.ymax - profile->clip_rect.ymax;
7891 }
7892 }
7893
7894 profile->view_rect.xmin -= fx;
7895 profile->view_rect.ymin -= fy;
7896 profile->view_rect.xmax -= fx;
7897 profile->view_rect.ymax -= fy;
7898
7899 data->draglastx = evtx;
7900 data->draglasty = evty;
7901
7902 changed = true;
7903 }
7904
7905 return changed;
7906}
7907
7912{
7913 return (point->flag & PROF_SELECT &&
7914 (ELEM(point->h1, HD_FREE, HD_ALIGN) || ELEM(point->h2, HD_FREE, HD_ALIGN))) ||
7916}
7917
7923 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
7924{
7925 CurveProfile *profile = (CurveProfile *)but->poin;
7926 int mx = event->xy[0];
7927 int my = event->xy[1];
7928
7929 ui_window_to_block(data->region, block, &mx, &my);
7930
7931 /* Move selected control points. */
7932 if (event->type == EVT_GKEY && event->val == KM_RELEASE) {
7933 data->dragstartx = mx;
7934 data->dragstarty = my;
7935 data->draglastx = mx;
7936 data->draglasty = my;
7938 return WM_UI_HANDLER_BREAK;
7939 }
7940
7941 /* Delete selected control points. */
7942 if (event->type == EVT_XKEY && event->val == KM_RELEASE) {
7946 return WM_UI_HANDLER_BREAK;
7947 }
7948
7949 /* Selecting, adding, and starting point movements. */
7950 if (data->state == BUTTON_STATE_HIGHLIGHT) {
7951 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
7952 const float m_xy[2] = {float(mx), float(my)};
7953
7954 if (event->modifier & KM_CTRL) {
7955 float f_xy[2];
7956 BLI_rctf_transform_pt_v(&profile->view_rect, &but->rect, f_xy, m_xy);
7957
7958 BKE_curveprofile_insert(profile, f_xy[0], f_xy[1]);
7960 }
7961
7962 /* Check for selecting of a point by finding closest point in radius. */
7963 CurveProfilePoint *pts = profile->path;
7964 /* 14 pixels radius for selecting points. */
7965 float dist_min_sq = square_f(UI_SCALE_FAC * 14.0f);
7966 int i_selected = -1;
7967 short selection_type = 0; /* For handle selection. */
7968 for (int i = 0; i < profile->path_len; i++) {
7969 float f_xy[2];
7970 BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &pts[i].x);
7971 float dist_sq = len_squared_v2v2(m_xy, f_xy);
7972 if (dist_sq < dist_min_sq) {
7973 i_selected = i;
7974 selection_type = PROF_SELECT;
7975 dist_min_sq = dist_sq;
7976 }
7977
7978 /* Also select handles if the point is selected and it has the right handle type. */
7979 if (point_draw_handles(&pts[i])) {
7980 if (ELEM(profile->path[i].h1, HD_FREE, HD_ALIGN)) {
7981 BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, pts[i].h1_loc);
7982 dist_sq = len_squared_v2v2(m_xy, f_xy);
7983 if (dist_sq < dist_min_sq) {
7984 i_selected = i;
7985 selection_type = PROF_H1_SELECT;
7986 dist_min_sq = dist_sq;
7987 }
7988 }
7989 if (ELEM(profile->path[i].h2, HD_FREE, HD_ALIGN)) {
7990 BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, pts[i].h2_loc);
7991 dist_sq = len_squared_v2v2(m_xy, f_xy);
7992 if (dist_sq < dist_min_sq) {
7993 i_selected = i;
7994 selection_type = PROF_H2_SELECT;
7995 dist_min_sq = dist_sq;
7996 }
7997 }
7998 }
7999 }
8000
8001 /* Add a point if the click was close to the path but not a control point or handle. */
8002 if (i_selected == -1) {
8003 float f_xy[2], f_xy_prev[2];
8004 CurveProfilePoint *table = profile->table;
8005 BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &table[0].x);
8006
8007 dist_min_sq = square_f(UI_SCALE_FAC * 8.0f); /* 8 pixel radius from each table point. */
8008
8009 /* Loop through the path's high resolution table and find what's near the click. */
8010 for (int i = 1; i <= BKE_curveprofile_table_size(profile); i++) {
8011 copy_v2_v2(f_xy_prev, f_xy);
8012 BLI_rctf_transform_pt_v(&but->rect, &profile->view_rect, f_xy, &table[i].x);
8013
8014 if (dist_squared_to_line_segment_v2(m_xy, f_xy_prev, f_xy) < dist_min_sq) {
8015 BLI_rctf_transform_pt_v(&profile->view_rect, &but->rect, f_xy, m_xy);
8016
8017 CurveProfilePoint *new_pt = BKE_curveprofile_insert(profile, f_xy[0], f_xy[1]);
8019
8020 /* Get the index of the newly added point. */
8021 i_selected = int(new_pt - profile->path);
8022 BLI_assert(i_selected >= 0 && i_selected <= profile->path_len);
8023 selection_type = PROF_SELECT;
8024 break;
8025 }
8026 }
8027 }
8028
8029 /* Change the flag for the point(s) if one was selected or added. */
8030 if (i_selected != -1) {
8031 /* Deselect all if this one is deselected, except if we hold shift. */
8032 if (event->modifier & KM_SHIFT) {
8033 pts[i_selected].flag ^= selection_type;
8034 }
8035 else {
8036 for (int i = 0; i < profile->path_len; i++) {
8037 // pts[i].flag &= ~(PROF_SELECT | PROF_H1_SELECT | PROF_H2_SELECT);
8038 profile->path[i].flag &= ~(PROF_SELECT | PROF_H1_SELECT | PROF_H2_SELECT);
8039 }
8040 profile->path[i_selected].flag |= selection_type;
8041 }
8042 }
8043 else {
8044 /* Move the view. */
8045 data->cancel = true;
8046 }
8047
8048 data->dragsel = i_selected;
8049
8050 data->dragstartx = mx;
8051 data->dragstarty = my;
8052 data->draglastx = mx;
8053 data->draglasty = my;
8054
8056 return WM_UI_HANDLER_BREAK;
8057 }
8058 }
8059 else if (data->state == BUTTON_STATE_NUM_EDITING) { /* Do control point movement. */
8060 if (event->type == MOUSEMOVE) {
8061 if (mx != data->draglastx || my != data->draglasty) {
8063 block, but, data, mx, my, event->modifier & KM_CTRL, event->modifier & KM_SHIFT))
8064 {
8065 ui_numedit_apply(C, block, but, data);
8066 }
8067 }
8068 }
8069 else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
8070 /* Finish move. */
8071 if (data->dragsel != -1) {
8072
8073 if (data->dragchange == false) {
8074 /* Deselect all, select one. */
8075 }
8076 else {
8077 /* Remove doubles, clip after move. */
8079 }
8080 }
8082 }
8083 return WM_UI_HANDLER_BREAK;
8084 }
8085
8087}
8088
8089static bool ui_numedit_but_HISTOGRAM(uiBut *but, uiHandleButtonData *data, int mx, int my)
8090{
8091 Histogram *hist = (Histogram *)but->poin;
8092 const bool changed = true;
8093 const float dy = my - data->draglasty;
8094
8095 /* scale histogram values (dy / 10 for better control) */
8096 const float yfac = min_ff(pow2f(hist->ymax), 1.0f) * 0.5f;
8097 hist->ymax += (dy * 0.1f) * yfac;
8098
8099 /* 0.1 allows us to see HDR colors up to 10 */
8100 CLAMP(hist->ymax, 0.1f, 100.0f);
8101
8102 data->draglastx = mx;
8103 data->draglasty = my;
8104
8105 return changed;
8106}
8107
8109 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
8110{
8111 int mx = event->xy[0];
8112 int my = event->xy[1];
8113 ui_window_to_block(data->region, block, &mx, &my);
8114
8115 if (data->state == BUTTON_STATE_HIGHLIGHT) {
8116 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
8117 data->dragstartx = mx;
8118 data->dragstarty = my;
8119 data->draglastx = mx;
8120 data->draglasty = my;
8122
8123 /* also do drag the first time */
8124 if (ui_numedit_but_HISTOGRAM(but, data, mx, my)) {
8125 ui_numedit_apply(C, block, but, data);
8126 }
8127
8128 return WM_UI_HANDLER_BREAK;
8129 }
8130 /* XXX hardcoded keymap check.... */
8131 if (event->type == EVT_BACKSPACEKEY && event->val == KM_PRESS) {
8132 Histogram *hist = (Histogram *)but->poin;
8133 hist->ymax = 1.0f;
8134
8136 return WM_UI_HANDLER_BREAK;
8137 }
8138 }
8139 else if (data->state == BUTTON_STATE_NUM_EDITING) {
8140 if (event->type == EVT_ESCKEY) {
8141 if (event->val == KM_PRESS) {
8142 data->cancel = true;
8143 data->escapecancel = true;
8145 }
8146 }
8147 else if (event->type == MOUSEMOVE) {
8148 if (mx != data->draglastx || my != data->draglasty) {
8149 if (ui_numedit_but_HISTOGRAM(but, data, mx, my)) {
8150 ui_numedit_apply(C, block, but, data);
8151 }
8152 }
8153 }
8154 else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
8156 }
8157 return WM_UI_HANDLER_BREAK;
8158 }
8159
8161}
8162
8163static bool ui_numedit_but_WAVEFORM(uiBut *but, uiHandleButtonData *data, int mx, int my)
8164{
8165 Scopes *scopes = (Scopes *)but->poin;
8166 const bool changed = true;
8167
8168 const float dy = my - data->draglasty;
8169
8170 /* scale waveform values */
8171 scopes->wavefrm_yfac += dy / 200.0f;
8172
8173 CLAMP(scopes->wavefrm_yfac, 0.5f, 2.0f);
8174
8175 data->draglastx = mx;
8176 data->draglasty = my;
8177
8178 return changed;
8179}
8180
8182 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
8183{
8184 int mx = event->xy[0];
8185 int my = event->xy[1];
8186 ui_window_to_block(data->region, block, &mx, &my);
8187
8188 if (data->state == BUTTON_STATE_HIGHLIGHT) {
8189 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
8190 data->dragstartx = mx;
8191 data->dragstarty = my;
8192 data->draglastx = mx;
8193 data->draglasty = my;
8195
8196 /* also do drag the first time */
8197 if (ui_numedit_but_WAVEFORM(but, data, mx, my)) {
8198 ui_numedit_apply(C, block, but, data);
8199 }
8200
8201 return WM_UI_HANDLER_BREAK;
8202 }
8203 /* XXX hardcoded keymap check.... */
8204 if (event->type == EVT_BACKSPACEKEY && event->val == KM_PRESS) {
8205 Scopes *scopes = (Scopes *)but->poin;
8206 scopes->wavefrm_yfac = 1.0f;
8207
8209 return WM_UI_HANDLER_BREAK;
8210 }
8211 }
8212 else if (data->state == BUTTON_STATE_NUM_EDITING) {
8213 if (event->type == EVT_ESCKEY) {
8214 if (event->val == KM_PRESS) {
8215 data->cancel = true;
8216 data->escapecancel = true;
8218 }
8219 }
8220 else if (event->type == MOUSEMOVE) {
8221 if (mx != data->draglastx || my != data->draglasty) {
8222 if (ui_numedit_but_WAVEFORM(but, data, mx, my)) {
8223 ui_numedit_apply(C, block, but, data);
8224 }
8225 }
8226 }
8227 else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
8229 }
8230 return WM_UI_HANDLER_BREAK;
8231 }
8232
8234}
8235
8237 bContext *C, uiBut *but, uiHandleButtonData *data, int mx, int my, const bool shift)
8238{
8239 MovieClipScopes *scopes = (MovieClipScopes *)but->poin;
8240 const bool changed = true;
8241
8242 float dx = mx - data->draglastx;
8243 float dy = my - data->draglasty;
8244
8245 if (shift) {
8246 dx /= 5.0f;
8247 dy /= 5.0f;
8248 }
8249
8250 if (!scopes->track_locked) {
8251 const MovieClip *clip = CTX_data_edit_movieclip(C);
8252 const int clip_framenr = BKE_movieclip_remap_scene_to_clip_frame(clip, scopes->scene_framenr);
8253 if (scopes->marker->framenr != clip_framenr) {
8254 scopes->marker = BKE_tracking_marker_ensure(scopes->track, clip_framenr);
8255 }
8256
8257 scopes->marker->flag &= ~(MARKER_DISABLED | MARKER_TRACKED);
8258 scopes->marker->pos[0] += -dx * scopes->slide_scale[0] / BLI_rctf_size_x(&but->block->rect);
8259 scopes->marker->pos[1] += -dy * scopes->slide_scale[1] / BLI_rctf_size_y(&but->block->rect);
8260
8262 }
8263
8264 scopes->ok = 0;
8265
8266 data->draglastx = mx;
8267 data->draglasty = my;
8268
8269 return changed;
8270}
8271
8273 bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
8274{
8275 int mx = event->xy[0];
8276 int my = event->xy[1];
8277 ui_window_to_block(data->region, block, &mx, &my);
8278
8279 if (data->state == BUTTON_STATE_HIGHLIGHT) {
8280 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
8281 data->dragstartx = mx;
8282 data->dragstarty = my;
8283 data->draglastx = mx;
8284 data->draglasty = my;
8286
8287 /* also do drag the first time */
8288 if (ui_numedit_but_TRACKPREVIEW(C, but, data, mx, my, event->modifier & KM_SHIFT)) {
8289 ui_numedit_apply(C, block, but, data);
8290 }
8291
8292 return WM_UI_HANDLER_BREAK;
8293 }
8294 }
8295 else if (data->state == BUTTON_STATE_NUM_EDITING) {
8296 if (event->type == EVT_ESCKEY) {
8297 if (event->val == KM_PRESS) {
8298 data->cancel = true;
8299 data->escapecancel = true;
8301 }
8302 }
8303 else if (event->type == MOUSEMOVE) {
8304 if (mx != data->draglastx || my != data->draglasty) {
8305 if (ui_numedit_but_TRACKPREVIEW(C, but, data, mx, my, event->modifier & KM_SHIFT)) {
8306 ui_numedit_apply(C, block, but, data);
8307 }
8308 }
8309 }
8310 else if (event->type == LEFTMOUSE && event->val == KM_RELEASE) {
8312 }
8313 return WM_UI_HANDLER_BREAK;
8314 }
8315
8317}
8318
8319static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *event)
8320{
8322 int retval = WM_UI_HANDLER_CONTINUE;
8323
8324 const bool is_disabled = but->flag & UI_BUT_DISABLED || data->disable_force;
8325
8326 /* If `but->pointype` is set, `but->poin` should be too. */
8327 BLI_assert(!bool(but->pointype) || but->poin);
8328
8329 /* Only hard-coded stuff here, button interactions with configurable
8330 * keymaps are handled using operators (see #ED_keymap_ui). */
8331
8332 if (data->state == BUTTON_STATE_HIGHLIGHT) {
8333
8334 /* handle copy and paste */
8335 bool is_press_ctrl_but_no_shift = (event->val == KM_PRESS) &&
8336 (event->modifier & (KM_CTRL | KM_OSKEY)) &&
8337 (event->modifier & KM_SHIFT) == 0;
8338 const bool do_copy = event->type == EVT_CKEY && is_press_ctrl_but_no_shift;
8339 const bool do_paste = event->type == EVT_VKEY && is_press_ctrl_but_no_shift;
8340
8341 /* Specific handling for list-rows, we try to find their overlapping text button. */
8342 if ((do_copy || do_paste) && but->type == ButType::ListRow) {
8344 if (labelbut) {
8345 but = labelbut;
8346 data = but->active;
8347 }
8348 }
8349
8350 /* do copy first, because it is the only allowed operator when disabled */
8351 if (do_copy) {
8352 if (ui_but_copy(C, but, event->modifier & KM_ALT)) {
8353 return WM_UI_HANDLER_BREAK;
8354 }
8355 }
8356
8357 /* handle menu */
8358
8359 if ((event->type == RIGHTMOUSE) && (event->modifier == 0) && (event->val == KM_PRESS)) {
8360 /* For some button types that are typically representing entire sets of data,
8361 * right-clicking to spawn the context menu should also activate the item. This makes it
8362 * clear which item will be operated on. Apply the button immediately, so context menu
8363 * polls get the right active item. */
8364 uiButViewItem *clicked_view_item_but = static_cast<uiButViewItem *>(
8365 but->type == ButType::ViewItem ? but :
8366 ui_view_item_find_mouse_over(data->region, event->xy));
8367 if (clicked_view_item_but) {
8368 clicked_view_item_but->view_item->activate_for_context_menu(*C);
8370 }
8371
8372 /* RMB has two options now */
8373 if (ui_popup_context_menu_for_button(C, but, event)) {
8375 return WM_UI_HANDLER_BREAK;
8376 }
8377 }
8378
8379 if (is_disabled) {
8381 }
8382
8383#ifdef WITH_INPUT_NDOF
8384 /* 2D view navigation conflicts with using NDOF to adjust colors,
8385 * especially in the node-editor, see: #105224. */
8386 if (event->type == NDOF_MOTION) {
8387 if (data->region->runtime->type->keymapflag & ED_KEYMAP_VIEW2D) {
8389 }
8390 }
8391#endif /* WITH_INPUT_NDOF */
8392
8393 if (do_paste) {
8394 ui_but_paste(C, but, data, event->modifier & KM_ALT);
8395 return WM_UI_HANDLER_BREAK;
8396 }
8397
8398 if ((data->state == BUTTON_STATE_HIGHLIGHT) &&
8400 (event->val == KM_RELEASE) &&
8401 /* Only returns true if the event was handled. */
8402 ui_do_but_extra_operator_icon(C, but, data, event))
8403 {
8404 return WM_UI_HANDLER_BREAK;
8405 }
8406 }
8407
8408 if (but->flag & UI_BUT_DISABLED) {
8409 /* It's important to continue here instead of breaking since breaking causes the event to be
8410 * considered "handled", preventing further click/drag events from being generated.
8411 *
8412 * An example of where this is needed is dragging node-sockets, where dragging a node-socket
8413 * could exit the button before the drag threshold was reached, disable the button then break
8414 * handling of the #MOUSEMOVE event preventing the socket being dragged entirely, see: #96255.
8415 *
8416 * Region level event handling is responsible for preventing events being passed
8417 * through to parts of the UI that are logically behind this button, see: #92364. */
8419 }
8420
8421 switch (but->type) {
8422 case ButType::But:
8423 case ButType::Decorator:
8424 retval = ui_do_but_BUT(C, but, data, event);
8425 break;
8426 case ButType::KeyEvent:
8427 retval = ui_do_but_KEYEVT(C, but, data, event);
8428 break;
8430 retval = ui_do_but_HOTKEYEVT(C, but, data, event);
8431 break;
8432 case ButType::Tab:
8433 retval = ui_do_but_TAB(C, block, but, data, event);
8434 break;
8435 case ButType::ButToggle:
8436 case ButType::Toggle:
8439 case ButType::ToggleN:
8440 case ButType::Checkbox:
8441 case ButType::CheckboxN:
8442 case ButType::Row:
8443 retval = ui_do_but_TOG(C, but, data, event);
8444 break;
8445 case ButType::ViewItem:
8446 retval = ui_do_but_VIEW_ITEM(C, but, data, event);
8447 break;
8448 case ButType::Scroll:
8449 retval = ui_do_but_SCROLL(C, block, but, data, event);
8450 break;
8451 case ButType::Grip:
8452 retval = ui_do_but_GRIP(C, block, but, data, event);
8453 break;
8454 case ButType::Num:
8455 retval = ui_do_but_NUM(C, block, but, data, event);
8456 break;
8457 case ButType::NumSlider:
8458 retval = ui_do_but_SLI(C, block, but, data, event);
8459 break;
8460 case ButType::ListBox:
8461 /* Nothing to do! */
8462 break;
8463 case ButType::ListRow:
8464 retval = ui_do_but_LISTROW(C, but, data, event);
8465 break;
8466 case ButType::Roundbox:
8467 case ButType::Label:
8468 case ButType::Image:
8469 case ButType::Progress:
8472 retval = ui_do_but_EXIT(C, but, data, event);
8473 break;
8474 case ButType::Histogram:
8475 retval = ui_do_but_HISTOGRAM(C, block, but, data, event);
8476 break;
8477 case ButType::Waveform:
8478 retval = ui_do_but_WAVEFORM(C, block, but, data, event);
8479 break;
8481 /* Nothing to do! */
8482 break;
8483 case ButType::Text:
8485 if ((but->type == ButType::SearchMenu) && (but->flag & UI_BUT_VALUE_CLEAR)) {
8486 retval = ui_do_but_SEARCH_UNLINK(C, block, but, data, event);
8487 if (retval & WM_UI_HANDLER_BREAK) {
8488 break;
8489 }
8490 }
8491 retval = ui_do_but_TEX(C, block, but, data, event);
8492 break;
8493 case ButType::Menu:
8494 case ButType::Popover:
8495 case ButType::Block:
8496 case ButType::Pulldown:
8497 retval = ui_do_but_BLOCK(C, but, data, event);
8498 break;
8499 case ButType::ButMenu:
8500 retval = ui_do_but_BUT(C, but, data, event);
8501 break;
8502 case ButType::Color:
8503 retval = ui_do_but_COLOR(C, but, data, event);
8504 break;
8505 case ButType::Unitvec:
8506 retval = ui_do_but_UNITVEC(C, block, but, data, event);
8507 break;
8508 case ButType::ColorBand:
8509 retval = ui_do_but_COLORBAND(C, block, but, data, event);
8510 break;
8511 case ButType::Curve:
8512 retval = ui_do_but_CURVE(C, block, but, data, event);
8513 break;
8515 retval = ui_do_but_CURVEPROFILE(C, block, but, data, event);
8516 break;
8517 case ButType::HsvCube:
8518 retval = ui_do_but_HSVCUBE(C, block, but, data, event);
8519 break;
8520 case ButType::HsvCircle:
8521 retval = ui_do_but_HSVCIRCLE(C, block, but, data, event);
8522 break;
8524 retval = ui_do_but_TRACKPREVIEW(C, block, but, data, event);
8525 break;
8526
8527 /* quiet warnings for unhandled types */
8528 case ButType::Sepr:
8529 case ButType::SeprLine:
8531 case ButType::Extra:
8532 break;
8533 }
8534
8535#ifdef USE_DRAG_MULTINUM
8536 data = but->active;
8537 if (data) {
8538 if (ISMOUSE_MOTION(event->type) ||
8539 /* if we started dragging, progress on any event */
8540 (data->multi_data.init == uiHandleButtonMulti::INIT_SETUP))
8541 {
8544 {
8545 /* initialize! */
8546 if (data->multi_data.init == uiHandleButtonMulti::INIT_UNSET) {
8547 /* --> (uiHandleButtonMulti::INIT_SETUP | uiHandleButtonMulti::INIT_DISABLE) */
8548
8549 const float margin_y = DRAG_MULTINUM_THRESHOLD_DRAG_Y / sqrtf(block->aspect);
8550
8551 /* check if we have a vertical gesture */
8552 if (len_squared_v2(data->multi_data.drag_dir) > (margin_y * margin_y)) {
8553 const float dir_nor_y[2] = {0.0, 1.0f};
8554 float dir_nor_drag[2];
8555
8556 normalize_v2_v2(dir_nor_drag, data->multi_data.drag_dir);
8557
8558 if (fabsf(dot_v2v2(dir_nor_drag, dir_nor_y)) > DRAG_MULTINUM_THRESHOLD_VERTICAL) {
8559 data->multi_data.init = uiHandleButtonMulti::INIT_SETUP;
8560 data->multi_data.drag_lock_x = event->xy[0];
8561 }
8562 else {
8563 data->multi_data.init = uiHandleButtonMulti::INIT_DISABLE;
8564 }
8565 }
8566 }
8567 else if (data->multi_data.init == uiHandleButtonMulti::INIT_SETUP) {
8568 /* --> (uiHandleButtonMulti::INIT_ENABLE) */
8569 const float margin_x = DRAG_MULTINUM_THRESHOLD_DRAG_X / sqrtf(block->aspect);
8570 /* Check if we're don't setting buttons. */
8571 if ((data->text_edit.edit_string &&
8573 ((abs(data->multi_data.drag_lock_x - event->xy[0]) > margin_x) &&
8574 /* Just to be sure, check we're dragging more horizontally then vertically. */
8575 abs(event->prev_xy[0] - event->xy[0]) > abs(event->prev_xy[1] - event->xy[1])))
8576 {
8577 if (data->multi_data.has_mbuts) {
8579 data->multi_data.init = uiHandleButtonMulti::INIT_ENABLE;
8580 }
8581 else {
8582 data->multi_data.init = uiHandleButtonMulti::INIT_DISABLE;
8583 }
8584 }
8585 }
8586
8587 if (data->multi_data.init == uiHandleButtonMulti::INIT_SETUP) {
8588 if (ui_multibut_states_tag(but, data, event)) {
8589 ED_region_tag_redraw(data->region);
8590 }
8591 }
8592 }
8593 }
8594 }
8595#endif /* USE_DRAG_MULTINUM */
8596
8597 return retval;
8598}
8599
8601
8602/* -------------------------------------------------------------------- */
8605
8606static void ui_blocks_set_tooltips(ARegion *region, const bool enable)
8607{
8608 if (!region) {
8609 return;
8610 }
8611
8612 /* We disabled buttons when they were already shown, and re-enable them on mouse move. */
8613 LISTBASE_FOREACH (uiBlock *, block, &region->runtime->uiblocks) {
8614 block->tooltipdisabled = !enable;
8615 }
8616}
8617
8619{
8621 if (data) {
8622 bScreen *screen = WM_window_get_active_screen(data->window);
8623 if (screen->tool_tip && screen->tool_tip->region) {
8624 WM_tooltip_refresh(C, data->window);
8625 }
8626 }
8627}
8628
8630{
8632 if (data) {
8633 if (data->autoopentimer) {
8634 WM_event_timer_remove(data->wm, data->window, data->autoopentimer);
8635 data->autoopentimer = nullptr;
8636 }
8637
8638 if (data->window) {
8639 WM_tooltip_clear(C, data->window);
8640 }
8641 }
8642}
8643
8645 bContext *C, ARegion *region, int *pass, double *r_pass_delay, bool *r_exit_on_event)
8646{
8647 bool is_quick_tip = false;
8648 if (*pass == 1) {
8649 is_quick_tip = true;
8650 (*pass)--;
8651 (*r_pass_delay) = UI_TOOLTIP_DELAY - UI_TOOLTIP_DELAY_QUICK;
8652 }
8653
8654 uiBut *but = UI_region_active_but_get(region);
8655 *r_exit_on_event = false;
8656 if (but) {
8657 const wmWindow *win = CTX_wm_window(C);
8659 but, but->active ? but->active->region : region, win->eventstate);
8660
8661 return UI_tooltip_create_from_button_or_extra_icon(C, region, but, extra_icon, is_quick_tip);
8662 }
8663 return nullptr;
8664}
8665
8667{
8670
8671 WM_tooltip_timer_clear(C, data->window);
8672
8673 if ((U.flag & USER_TOOLTIPS) || (data->tooltip_force)) {
8674 if (!but->block->tooltipdisabled) {
8675 if (!wm->runtime->drags.first) {
8676 const bool is_quick_tip = UI_but_has_quick_tooltip(but);
8677 const double delay = is_quick_tip ? UI_TOOLTIP_DELAY_QUICK : UI_TOOLTIP_DELAY;
8679 C, data->window, data->area, data->region, ui_but_tooltip_init, delay);
8680 if (is_quick_tip) {
8681 bScreen *screen = WM_window_get_active_screen(data->window);
8682 if (screen->tool_tip) {
8683 screen->tool_tip->pass = screen->tool_tip->region ? 0 : 1;
8684 }
8685 }
8686 }
8687 }
8688 }
8689}
8690
8692
8693/* -------------------------------------------------------------------- */
8696
8707
8709{
8711 if (data->state == state) {
8712 return;
8713 }
8714
8715 /* Highlight has timers for tool-tips and auto open. */
8717 but->flag &= ~UI_SELECT;
8718
8720
8721 /* Automatic open pull-down block timer. */
8723 /* Menu button types may draw as popovers, check for this case
8724 * ignoring other kinds of menus (mainly enums). (see #66538). */
8725 ((but->type == ButType::Menu) &&
8727 {
8728 if (data->used_mouse && !data->autoopentimer) {
8729 int time;
8730
8731 if (but->block->auto_open == true) { /* test for toolbox */
8732 time = 1;
8733 }
8734 else if ((but->block->flag & UI_BLOCK_LOOP && but->type != ButType::Block) ||
8735 (but->block->auto_open == true))
8736 {
8737 time = 5 * U.menuthreshold2;
8738 }
8739 else if (U.uiflag & USER_MENUOPENAUTO) {
8740 time = 5 * U.menuthreshold1;
8741 }
8742 else {
8743 time = -1; /* do nothing */
8744 }
8745
8746 if (time >= 0) {
8747 data->autoopentimer = WM_event_timer_add(
8748 data->wm, data->window, TIMER, 0.02 * double(time));
8749 }
8750 }
8751 }
8752 }
8753 else {
8754 but->flag |= UI_SELECT;
8756 }
8757
8758 /* text editing */
8760 ui_textedit_begin(C, but, data);
8761 }
8763 ui_textedit_end(C, but, data);
8764 }
8766 ui_textedit_end(C, but, data);
8767 }
8768
8769 /* number editing */
8771 if (ui_but_is_cursor_warp(but)) {
8773 rctf rectf;
8774 ui_block_to_window_rctf(data->region, but->block, &rectf, &but->rect);
8775 rcti bounds;
8776 BLI_rcti_rctf_copy(&bounds, &rectf);
8778 }
8779 else {
8781 }
8783 status.item(IFACE_("Cancel"), ICON_EVENT_ESC);
8784#if defined(__APPLE__)
8785 status.item(IFACE_("Snap"), ICON_KEY_COMMAND);
8786#else
8787 status.item(IFACE_("Snap"), ICON_EVENT_CTRL);
8788#endif
8789 if (ui_but_is_float(but)) {
8790 status.item(IFACE_("Precision"), ICON_EVENT_SHIFT);
8791 }
8792 }
8793 ui_numedit_begin(but, data);
8794 }
8795 else if (data->state == BUTTON_STATE_NUM_EDITING) {
8796 ui_numedit_end(but, data);
8797
8799 ED_workspace_status_text(C, nullptr);
8800 }
8801
8802 if (but->flag & UI_BUT_DRIVEN) {
8803 /* Only warn when editing stepping/dragging the value.
8804 * No warnings should show for editing driver expressions though!
8805 */
8808 "Can't edit driven number value, see driver editor for the driver setup");
8809 }
8810 }
8811
8812 if (ui_but_is_cursor_warp(but)) {
8813
8814#ifdef USE_CONT_MOUSE_CORRECT
8815 /* stereo3d has issues with changing cursor location so rather avoid */
8816 if (data->ungrab_mval[0] != FLT_MAX && !WM_stereo3d_enabled(data->window, false)) {
8817 int mouse_ungrab_xy[2];
8819 data->region, but->block, &data->ungrab_mval[0], &data->ungrab_mval[1]);
8820 mouse_ungrab_xy[0] = data->ungrab_mval[0];
8821 mouse_ungrab_xy[1] = data->ungrab_mval[1];
8822
8823 WM_cursor_grab_disable(data->window, mouse_ungrab_xy);
8824 }
8825 else {
8826 WM_cursor_grab_disable(data->window, nullptr);
8827 }
8828#else
8829 WM_cursor_grab_disable(data->window, nullptr);
8830#endif
8831 }
8832 }
8833 /* menu open */
8835 ui_block_open_begin(C, but, data);
8836 }
8837 else if (data->state == BUTTON_STATE_MENU_OPEN) {
8838 ui_block_open_end(C, but, data);
8839 }
8840
8841 /* add a short delay before exiting, to ensure there is some feedback */
8843 data->flashtimer = WM_event_timer_add(data->wm, data->window, TIMER, BUTTON_FLASH_DELAY);
8844 }
8845 else if (data->flashtimer) {
8846 WM_event_timer_remove(data->wm, data->window, data->flashtimer);
8847 data->flashtimer = nullptr;
8848 }
8849
8850 /* add hold timer if it's used */
8851 if (state == BUTTON_STATE_WAIT_RELEASE && (but->hold_func != nullptr)) {
8852 data->hold_action_timer = WM_event_timer_add(
8853 data->wm, data->window, TIMER, BUTTON_AUTO_OPEN_THRESH);
8854 }
8855 else if (data->hold_action_timer) {
8856 WM_event_timer_remove(data->wm, data->window, data->hold_action_timer);
8857 data->hold_action_timer = nullptr;
8858 }
8859
8860 /* Add a blocking ui handler at the window handler for blocking, modal states
8861 * but not for popups, because we already have a window level handler. */
8862 if (!(but->block->handle && but->block->handle->popup)) {
8864 if (!button_modal_state(data->state)) {
8866 &data->window->modalhandlers,
8868 nullptr,
8869 data,
8871 }
8872 }
8873 else {
8874 if (button_modal_state(data->state)) {
8875 /* true = postpone free */
8877 &data->window->modalhandlers, ui_handler_region_menu, nullptr, data, true);
8878 }
8879 }
8880 }
8881
8882 /* Wait for mouse-move to enable drag. */
8884 but->flag &= ~UI_SELECT;
8885 }
8886
8889 }
8890 else if (state == BUTTON_STATE_EXIT) {
8891 if (data->state == BUTTON_STATE_NUM_EDITING) {
8892 /* This happens on pasting values for example. */
8894 }
8895 }
8896
8897 data->state = state;
8898
8899 if (state != BUTTON_STATE_EXIT) {
8900 /* When objects for eg. are removed, running ui_but_update() can access
8901 * the removed data - so disable update on exit. Also in case of
8902 * highlight when not in a popup menu, we remove because data used in
8903 * button below popup might have been removed by action of popup. Needs
8904 * a more reliable solution... */
8905 if (state != BUTTON_STATE_HIGHLIGHT || (but->block->flag & UI_BLOCK_LOOP)) {
8906 ui_but_update(but);
8907 }
8908 }
8909
8910 /* redraw */
8912}
8913
8915 ARegion *region,
8916 uiBut *but,
8918{
8919 /* Don't activate semi-modal buttons the normal way, they have special activation handling. */
8920 if (but->semi_modal_state) {
8921 return;
8922 }
8923 /* Only ever one active button! */
8924 BLI_assert(ui_region_find_active_but(region) == nullptr);
8925
8926 /* setup struct */
8927 uiHandleButtonData *data = MEM_new<uiHandleButtonData>(__func__);
8928 data->wm = CTX_wm_manager(C);
8929 data->window = CTX_wm_window(C);
8930 data->area = CTX_wm_area(C);
8931 BLI_assert(region != nullptr);
8932 data->region = region;
8933
8934#ifdef USE_CONT_MOUSE_CORRECT
8935 copy_v2_fl(data->ungrab_mval, FLT_MAX);
8936#endif
8937
8939 /* XXX curve is temp */
8940 }
8941 else {
8942 if ((but->flag & UI_BUT_UPDATE_DELAY) == 0) {
8943 data->interactive = true;
8944 }
8945 }
8946
8947 data->state = BUTTON_STATE_INIT;
8948
8949 /* Activate button. Sets the hover flag to enable button highlights, usually the button is
8950 * initially activated because it's hovered. */
8951 but->flag |= UI_HOVER;
8952 but->active = data;
8953
8954 /* we disable auto_open in the block after a threshold, because we still
8955 * want to allow auto opening adjacent menus even if no button is activated
8956 * in between going over to the other button, but only for a short while */
8957 if (type == BUTTON_ACTIVATE_OVER && but->block->auto_open == true) {
8959 but->block->auto_open = false;
8960 }
8961 }
8962
8963 if (type == BUTTON_ACTIVATE_OVER) {
8964 data->used_mouse = true;
8965 }
8967
8968 if (type == BUTTON_ACTIVATE_OPEN) {
8970
8971 /* activate first button in submenu */
8972 if (data->menu && data->menu->region) {
8973 ARegion *subar = data->menu->region;
8974 uiBlock *subblock = static_cast<uiBlock *>(subar->runtime->uiblocks.first);
8975 uiBut *subbut;
8976
8977 if (subblock) {
8978 subbut = ui_but_first(subblock);
8979
8980 if (subbut) {
8982 }
8983 }
8984 }
8985 }
8986 else if (type == BUTTON_ACTIVATE_TEXT_EDITING) {
8988 }
8989 else if (type == BUTTON_ACTIVATE_APPLY) {
8991 }
8992
8993 if (but->type == ButType::Grip) {
8994 const bool horizontal = (BLI_rctf_size_x(&but->rect) < BLI_rctf_size_y(&but->rect));
8996 }
8997 else if (but->type == ButType::Num) {
8999 }
9000
9001 if (UI_but_has_quick_tooltip(but)) {
9002 /* Show a label for this button. */
9003 bScreen *screen = WM_window_get_active_screen(data->window);
9004 if ((BLI_time_now_seconds() - WM_tooltip_time_closed()) < 0.1) {
9006 if (screen->tool_tip) {
9007 screen->tool_tip->pass = 1;
9008 }
9009 }
9010 }
9011}
9012
9014 bContext *C, uiBut *but, uiHandleButtonData *data, const bool mousemove, const bool onfree)
9015{
9016 wmWindow *win = data->window;
9017 uiBlock *block = but->block;
9018
9019 if (but->type == ButType::Grip) {
9021 }
9022
9023 /* ensure we are in the exit state */
9024 if (data->state != BUTTON_STATE_EXIT) {
9026 }
9027
9028 /* apply the button action or value */
9029 if (!onfree) {
9030 ui_apply_but(C, block, but, data, false);
9031 }
9032
9033#ifdef USE_DRAG_MULTINUM
9034 if (data->multi_data.has_mbuts) {
9035 for (const std::unique_ptr<uiBut> &bt : block->buttons) {
9036 if (bt->flag & UI_BUT_DRAG_MULTI) {
9037 bt->flag &= ~UI_BUT_DRAG_MULTI;
9038
9039 if (!data->cancel) {
9040 ui_apply_but_autokey(C, bt.get());
9041 }
9042 }
9043 }
9044
9045 ui_multibut_free(data, block);
9046 }
9047#endif
9048
9049 /* if this button is in a menu, this will set the button return
9050 * value to the button value and the menu return value to ok, the
9051 * menu return value will be picked up and the menu will close */
9052 if (block->handle && !(block->flag & UI_BLOCK_KEEP_OPEN)) {
9053 if (!data->cancel || data->escapecancel) {
9054 uiPopupBlockHandle *menu;
9055
9056 menu = block->handle;
9057 menu->butretval = data->retval;
9058 menu->menuretval = (data->cancel) ? UI_RETURN_CANCEL : UI_RETURN_OK;
9059 }
9060 }
9061
9062 if (!onfree && !data->cancel) {
9063 /* autokey & undo push */
9064 ui_apply_but_undo(but);
9065 ui_apply_but_autokey(C, but);
9066
9067#ifdef USE_ALLSELECT
9068 {
9069 /* only RNA from this button is used */
9070 uiBut but_temp = *but;
9071 uiSelectContextStore *selctx_data = &data->select_others;
9072 for (uiSelectContextElem &other : selctx_data->elems) {
9073 but_temp.rnapoin = other.ptr;
9074 ui_apply_but_autokey(C, &but_temp);
9075 }
9076 }
9077#endif
9078
9079 /* popup menu memory */
9080 if (block->flag & UI_BLOCK_POPUP_MEMORY) {
9081 ui_popup_menu_memory_set(block, but);
9082 }
9083
9084 if (U.runtime.is_dirty == false) {
9086 }
9087 }
9088
9089 /* Disable tool-tips until mouse-move + last active flag. */
9090 LISTBASE_FOREACH (uiBlock *, block_iter, &data->region->runtime->uiblocks) {
9091 for (const std::unique_ptr<uiBut> &bt : block_iter->buttons) {
9092 bt->flag &= ~UI_BUT_LAST_ACTIVE;
9093 }
9094
9095 block_iter->tooltipdisabled = true;
9096 }
9097
9098 ui_blocks_set_tooltips(data->region, false);
9099
9100 /* clean up */
9101 if (data->text_edit.edit_string) {
9102 MEM_freeN(data->text_edit.edit_string);
9103 }
9104 if (data->text_edit.original_string) {
9105 MEM_freeN(data->text_edit.original_string);
9106 }
9107
9108#ifdef USE_ALLSELECT
9109 ui_selectcontext_end(but, &data->select_others);
9110#endif
9111
9112 if (data->changed_cursor) {
9114 }
9115
9116 /* redraw and refresh (for popups) */
9119
9120 if ((but->flag & UI_BUT_DRAG_MULTI) == 0) {
9121 if (data->custom_interaction_handle != nullptr) {
9122 /* Should only set when the button is modal. */
9123 BLI_assert(but->active != nullptr);
9124 data->custom_interaction_handle->user_count--;
9125
9126 BLI_assert(data->custom_interaction_handle->user_count >= 0);
9127 if (data->custom_interaction_handle->user_count == 0) {
9129 C, &but->block->custom_interaction_callbacks, data->custom_interaction_handle);
9130 }
9131 data->custom_interaction_handle = nullptr;
9132 }
9133 }
9134
9135 BLI_assert(!but->semi_modal_state || but->semi_modal_state == but->active);
9136 but->semi_modal_state = nullptr;
9137 /* clean up button */
9139
9140 but->flag &= ~(UI_HOVER | UI_SELECT);
9141 but->flag |= UI_BUT_LAST_ACTIVE;
9142 if (!onfree) {
9143 ui_but_update(but);
9144 }
9145
9146 /* Adds empty mouse-move in queue for re-initialize handler, in case mouse is
9147 * still over a button. We cannot just check for this ourselves because
9148 * at this point the mouse may be over a button in another region. */
9149 if (mousemove) {
9151 }
9152}
9153
9155{
9156 /* this gets called when the button somehow disappears while it is still
9157 * active, this is bad for user interaction, but we need to handle this
9158 * case cleanly anyway in case it happens */
9159 if (but->active) {
9161 data->cancel = true;
9162 button_activate_exit((bContext *)C, but, data, false, true);
9163 }
9164}
9165
9167{
9168 if (!but->semi_modal_state) {
9169 return;
9170 }
9171 /* Activate the button (using the semi modal state) and use the normal active button freeing. */
9174 but,
9175 [&]() { ui_but_active_free(C, but); });
9176}
9177
9178/* returns the active button with an optional checking function */
9179static uiBut *ui_context_button_active(const ARegion *region, bool (*but_check_cb)(const uiBut *))
9180{
9181 uiBut *but_found = nullptr;
9182
9183 while (region) {
9184 /* Follow this exact priority (from highest to lowest priority):
9185 * 1) Active-override button (#UI_BUT_ACTIVE_OVERRIDE).
9186 * 2) The real active button.
9187 * 3) The previously active button (#UI_BUT_LAST_ACTIVE).
9188 */
9189 uiBut *active_but_override = nullptr;
9190 uiBut *active_but_real = nullptr;
9191 uiBut *active_but_last = nullptr;
9192
9193 /* find active button */
9194 LISTBASE_FOREACH (uiBlock *, block, &region->runtime->uiblocks) {
9195 for (const std::unique_ptr<uiBut> &but : block->buttons) {
9196 if (but->flag & UI_BUT_ACTIVE_OVERRIDE) {
9197 active_but_override = but.get();
9198 }
9199 if (but->active) {
9200 active_but_real = but.get();
9201 }
9202 if (but->flag & UI_BUT_LAST_ACTIVE) {
9203 active_but_last = but.get();
9204 }
9205 }
9206 }
9207
9208 uiBut *activebut = active_but_override;
9209 if (!activebut) {
9210 activebut = active_but_real;
9211 }
9212 if (!activebut) {
9213 activebut = active_but_last;
9214 }
9215
9216 if (activebut && (but_check_cb == nullptr || but_check_cb(activebut))) {
9217 uiHandleButtonData *data = activebut->active;
9218
9219 but_found = activebut;
9220
9221 /* Recurse into opened menu, like color-picker case. */
9222 if (data && data->menu && (region != data->menu->region)) {
9223 region = data->menu->region;
9224 }
9225 else {
9226 return but_found;
9227 }
9228 }
9229 else {
9230 /* no active button */
9231 return but_found;
9232 }
9233 }
9234
9235 return but_found;
9236}
9237
9242
9244{
9245 ARegion *region_popup = CTX_wm_region_popup(C);
9246 return ui_context_button_active(region_popup ? region_popup : CTX_wm_region(C), nullptr);
9247}
9248
9250{
9251 return ui_context_button_active(region, nullptr);
9252}
9253
9254uiBut *UI_region_but_find_rect_over(const ARegion *region, const rcti *rect_px)
9255{
9256 return ui_but_find_rect_over(region, rect_px);
9257}
9258
9259uiBlock *UI_region_block_find_mouse_over(const ARegion *region, const int xy[2], bool only_clip)
9260{
9261 return ui_block_find_mouse_over_ex(region, xy, only_clip);
9262}
9263
9265 PointerRNA *r_ptr,
9266 PropertyRNA **r_prop,
9267 int *r_index)
9268{
9269 uiBut *activebut = UI_region_active_but_get(region);
9270
9271 if (activebut && activebut->rnapoin.data) {
9272 *r_ptr = activebut->rnapoin;
9273 *r_prop = activebut->rnaprop;
9274 *r_index = activebut->rnaindex;
9275 }
9276 else {
9277 *r_ptr = {};
9278 *r_prop = nullptr;
9279 *r_index = 0;
9280 }
9281
9282 return activebut;
9283}
9284
9286 PointerRNA *r_ptr,
9287 PropertyRNA **r_prop,
9288 int *r_index)
9289{
9290 ARegion *region_popup = CTX_wm_region_popup(C);
9292 region_popup ? region_popup : CTX_wm_region(C), r_ptr, r_prop, r_index);
9293}
9294
9295void UI_context_active_but_prop_handle(bContext *C, const bool handle_undo)
9296{
9298 if (activebut) {
9299 /* TODO(@ideasman42): look into a better way to handle the button change
9300 * currently this is mainly so reset defaults works for the
9301 * operator redo panel. */
9302 uiBlock *block = activebut->block;
9303
9304 /* There are various functions buttons may run on completion.
9305 * See #ui_apply_but_funcs_after for reference, we could even call
9306 * this function however it may have unforeseen consequences,
9307 * see replies to: !134233. */
9308
9309 /* This may be needed to validate the value, see: #134101. */
9310 if (activebut->func) {
9311 activebut->func(C, activebut->func_arg1, activebut->func_arg2);
9312 }
9313 if (block->handle_func) {
9314 block->handle_func(C, block->handle_func_arg, activebut->retval);
9315 }
9316 if (handle_undo) {
9317 /* Update the button so the undo text uses the correct value. */
9318 ui_but_update(activebut);
9319 ui_apply_but_undo(activebut);
9320 }
9321 }
9322}
9323
9325{
9326 wm_event_handler_ui_cancel_ex(C, win, region, false);
9327}
9328
9330{
9331 ARegion *region_ctx = CTX_wm_region(C);
9332
9333 /* background mode */
9334 if (region_ctx == nullptr) {
9335 return nullptr;
9336 }
9337
9338 /* scan active regions ui */
9339 LISTBASE_FOREACH (uiBlock *, block, &region_ctx->runtime->uiblocks) {
9340 if (block->ui_operator) {
9341 return block->ui_operator;
9342 }
9343 }
9344
9345 /* scan popups */
9346 {
9347 bScreen *screen = CTX_wm_screen(C);
9348
9349 LISTBASE_FOREACH (ARegion *, region, &screen->regionbase) {
9350 if (region == region_ctx) {
9351 continue;
9352 }
9353 LISTBASE_FOREACH (uiBlock *, block, &region->runtime->uiblocks) {
9354 if (block->ui_operator) {
9355 return block->ui_operator;
9356 }
9357 }
9358 }
9359 }
9360
9361 return nullptr;
9362}
9363
9365{
9366 uiBut *but = UI_region_active_but_get(button_region);
9367 return (but != nullptr) ? but->active->searchbox : nullptr;
9368}
9369
9371{
9372 Scene *scene = CTX_data_scene(C);
9373 ARegion *region = CTX_wm_region(C);
9376 depsgraph, (scene) ? BKE_scene_frame_get(scene) : 0.0f);
9377
9378 while (region) {
9379 /* find active button */
9380 uiBut *activebut = nullptr;
9381
9382 LISTBASE_FOREACH (uiBlock *, block, &region->runtime->uiblocks) {
9383 for (const std::unique_ptr<uiBut> &but : block->buttons) {
9384 ui_but_anim_flag(but.get(), &anim_eval_context);
9386 if (UI_but_is_decorator(but)) {
9388 }
9389
9390 ED_region_tag_redraw(region);
9391
9392 if (but->active) {
9393 activebut = but.get();
9394 }
9395 else if (!activebut && (but->flag & UI_BUT_LAST_ACTIVE)) {
9396 activebut = but.get();
9397 }
9398 }
9399 }
9400
9401 if (activebut) {
9402 /* Always recurse into opened menu, so all buttons update (like color-picker). */
9403 uiHandleButtonData *data = activebut->active;
9404 if (data && data->menu) {
9405 region = data->menu->region;
9406 }
9407 else {
9408 return;
9409 }
9410 }
9411 else {
9412 /* no active button */
9413 return;
9414 }
9415 }
9416}
9417
9419{
9420 uiBut *active_but = ui_block_active_but_get(block);
9421 if (!active_but || !active_but->active || !active_but->changed || active_but->block != block) {
9422 return;
9423 }
9424 /* If there is a search popup attached to the button, don't change the view. The popups don't
9425 * support updating the position to the button position nicely. */
9426 uiHandleButtonData *data = active_but->active;
9427 if (data->searchbox) {
9428 return;
9429 }
9430
9431 UI_but_ensure_in_view(C, active_but->active->region, active_but);
9432}
9433
9435
9436/* -------------------------------------------------------------------- */
9439
9440static uiBut *ui_but_find_open_event(ARegion *region, const wmEvent *event)
9441{
9442 LISTBASE_FOREACH (uiBlock *, block, &region->runtime->uiblocks) {
9443 for (const std::unique_ptr<uiBut> &but : block->buttons) {
9444 if (but.get() == event->customdata) {
9445 return but.get();
9446 }
9447 }
9448 }
9449 return nullptr;
9450}
9451
9452static int ui_handle_button_over(bContext *C, const wmEvent *event, ARegion *region)
9453{
9454 if (event->type == MOUSEMOVE) {
9455 const bool labeledit = event->modifier & KM_CTRL;
9456 /* Allow buttons to be activated to show the tool-tip,
9457 * then force-disable them if they're not considered interactive
9458 * so they don't swallow events but can still display tips. */
9459 const bool for_tooltip = true;
9461 region, event->xy, labeledit, for_tooltip, nullptr, nullptr);
9462 if (but) {
9464
9465 if ((event->modifier & KM_ALT) && but->active) {
9466 /* Display tool-tips if holding Alt on mouse-over when tool-tips are disabled in the
9467 * preferences. */
9468 but->active->tooltip_force = true;
9469 }
9470
9471 if (but->active && !ui_but_is_interactive(but, labeledit)) {
9472 but->active->disable_force = true;
9473 }
9474 }
9475 }
9476 else if (event->type == EVT_BUT_OPEN) {
9477 uiBut *but = ui_but_find_open_event(region, event);
9478 if (but) {
9480 ui_do_button(C, but->block, but, event);
9481 }
9482 }
9483
9485}
9486
9488{
9489 wmWindow *win = CTX_wm_window(C);
9490
9492
9493 wmEvent event;
9494 wm_event_init_from_window(win, &event);
9495 event.type = EVT_BUT_OPEN;
9496 event.val = KM_PRESS;
9497 event.flag = eWM_EventFlag(0);
9498 event.customdata = but;
9499 event.customdata_free = false;
9500
9501 ui_do_button(C, but->block, but, &event);
9502}
9503
9505{
9507}
9508
9509void ui_but_execute_begin(bContext * /*C*/, ARegion *region, uiBut *but, void **active_back)
9510{
9511 BLI_assert(region != nullptr);
9512 BLI_assert(BLI_findindex(&region->runtime->uiblocks, but->block) != -1);
9513 /* NOTE: ideally we would not have to change 'but->active' however
9514 * some functions we call don't use data (as they should be doing) */
9516 *active_back = but->active;
9517 data = MEM_new<uiHandleButtonData>(__func__);
9518 but->active = data;
9519 BLI_assert(region != nullptr);
9520 data->region = region;
9521}
9522
9523void ui_but_execute_end(bContext *C, ARegion * /*region*/, uiBut *but, void *active_back)
9524{
9525 ui_apply_but(C, but->block, but, but->active, true);
9526
9527 if ((but->flag & UI_BUT_DRAG_MULTI) == 0) {
9528 ui_apply_but_autokey(C, but);
9529 }
9530 /* use onfree event so undo is handled by caller and apply is already done above */
9531 button_activate_exit(C, but, but->active, false, true);
9532 but->active = static_cast<uiHandleButtonData *>(active_back);
9533}
9534
9536 ARegion *region,
9537 uiBut *but,
9539{
9540 uiBut *oldbut = ui_region_find_active_but(region);
9541 if (oldbut) {
9542 uiHandleButtonData *data = oldbut->active;
9543 data->cancel = true;
9544 button_activate_exit(C, oldbut, data, false, false);
9545 }
9546
9547 button_activate_init(C, region, but, type);
9548}
9549
9554{
9555 if (ELEM(but->type, ButType::ButMenu, ButType::Row)) {
9556 /* mainly for operator buttons */
9558 }
9559 else if (ELEM(but->type, ButType::Block, ButType::Pulldown)) {
9560 /* open sub-menus (like right arrow key) */
9562 }
9563 else if (but->type == ButType::Menu) {
9564 /* activate menu items */
9566 }
9567 else {
9568#ifndef NDEBUG
9569 CLOG_WARN(&LOG, "%s: error, unhandled type: %d", __func__, int(but->type));
9570#endif
9571 return false;
9572 }
9573 return true;
9574}
9575
9587 ARegion *region,
9588 uiBut *but,
9589 blender::FunctionRef<void()> fn)
9590{
9591 BLI_assert(but->active == nullptr);
9592
9593 uiBut *prev_active_but = ui_region_find_active_but(region);
9594 uiHandleButtonData *prev_active_data = prev_active_but ? prev_active_but->active : nullptr;
9595 if (prev_active_but) {
9596 prev_active_but->active = nullptr;
9597 }
9598
9599 /* Enforce the button to actually be active, using #uiBut.semi_modal_state to store its handling
9600 * state. */
9601 if (!but->semi_modal_state) {
9602 ui_but_activate_event(C, region, but);
9603 but->semi_modal_state = but->active;
9604 but->semi_modal_state->is_semi_modal = true;
9605 }
9606
9607 /* Activate the button using the previously created/stored semi-modal state. */
9608 but->active = but->semi_modal_state;
9609 fn();
9610 but->active = nullptr;
9611
9612 if (prev_active_but) {
9613 prev_active_but->active = prev_active_data;
9614 }
9615}
9616
9623 ARegion *region,
9624 blender::FunctionRef<void(uiBut *semi_modal_but)> fn)
9625{
9626 /* Might want to have some way to define which order these should be handled in - if there's
9627 * every actually a use-case for multiple semi-active buttons at the same time. */
9628
9629 LISTBASE_FOREACH (uiBlock *, block, &region->runtime->uiblocks) {
9630 for (const std::unique_ptr<uiBut> &but : block->buttons) {
9631 if ((but->flag2 & UI_BUT2_FORCE_SEMI_MODAL_ACTIVE) || but->semi_modal_state) {
9632 with_but_active_as_semi_modal(C, region, but.get(), [&]() { fn(but.get()); });
9633 }
9634 }
9635 }
9636}
9637
9639
9640/* -------------------------------------------------------------------- */
9643
9644static bool ui_button_value_default(uiBut *but, double *r_value)
9645{
9646 if (but->rnaprop != nullptr && ui_but_is_rna_valid(but)) {
9647 const int type = RNA_property_type(but->rnaprop);
9648 if (ELEM(type, PROP_FLOAT, PROP_INT)) {
9649 double default_value;
9650 switch (type) {
9651 case PROP_INT:
9653 default_value = double(
9655 }
9656 else {
9657 default_value = double(RNA_property_int_get_default(&but->rnapoin, but->rnaprop));
9658 }
9659 break;
9660 case PROP_FLOAT:
9662 default_value = double(
9664 }
9665 else {
9666 default_value = double(RNA_property_float_get_default(&but->rnapoin, but->rnaprop));
9667 }
9668 break;
9669 }
9670 *r_value = default_value;
9671 return true;
9672 }
9673 }
9674 return false;
9675}
9676
9677static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but)
9678{
9680 const uiHandleButtonState state_orig = data->state;
9681
9682 uiBlock *block = but->block;
9683 ARegion *region = data->region;
9684
9685 int retval = WM_UI_HANDLER_CONTINUE;
9686
9687 if (data->state == BUTTON_STATE_HIGHLIGHT) {
9688 switch (event->type) {
9689 case WINDEACTIVATE:
9690 case EVT_BUT_CANCEL:
9691 data->cancel = true;
9693 break;
9694#ifdef USE_UI_POPOVER_ONCE
9695 case LEFTMOUSE: {
9696 if (event->val == KM_RELEASE) {
9697 if (block->flag & UI_BLOCK_POPOVER_ONCE) {
9698 if (!(but->flag & UI_BUT_DISABLED)) {
9700 data->cancel = false;
9702 retval = WM_UI_HANDLER_BREAK;
9703 /* Cancel because this `but` handles all events and we don't want
9704 * the parent button's update function to do anything.
9705 *
9706 * Causes issues with buttons defined by #uiLayout::prop_with_popover. */
9708 }
9709 else if (ui_but_is_editable_as_text(but)) {
9711 retval = WM_UI_HANDLER_BREAK;
9712 }
9713 }
9714 }
9715 }
9716 break;
9717 }
9718#endif
9719 case MOUSEMOVE: {
9720 uiBut *but_other = UI_but_find_mouse_over(region, event);
9721 bool exit = false;
9722
9723 /* always deactivate button for pie menus,
9724 * else moving to blank space will leave activated */
9725 if ((!ui_block_is_menu(block) || ui_block_is_pie_menu(block)) &&
9726 !ui_but_contains_point_px(but, region, event->xy))
9727 {
9728 exit = true;
9729 }
9730 else if (but_other && ui_but_is_editable(but_other) && (but_other != but)) {
9731 exit = true;
9732 }
9733
9734 if (exit) {
9735 data->cancel = true;
9737 }
9738 else {
9739 /* Re-enable tool-tip on mouse move. */
9740 bool reenable_tooltip = true;
9741 bScreen *screen = CTX_wm_screen(C);
9742 if (screen && screen->tool_tip) {
9743 /* Allow some movement once the tooltip timer has started. */
9744 const int threshold = WM_event_drag_threshold(event);
9745 const int movement = len_manhattan_v2v2_int(event->xy, screen->tool_tip->event_xy);
9746 reenable_tooltip = (movement > threshold);
9747 }
9748 if (reenable_tooltip) {
9749 ui_blocks_set_tooltips(region, true);
9751 }
9752 }
9753
9754 /* Update extra icons states. */
9756
9757 break;
9758 }
9759 case TIMER: {
9760 /* Handle menu auto open timer. */
9761 if (event->customdata == data->autoopentimer) {
9762 WM_event_timer_remove(data->wm, data->window, data->autoopentimer);
9763 data->autoopentimer = nullptr;
9764
9765 if (ui_but_contains_point_px(but, region, event->xy) || but->active) {
9767 }
9768 }
9769
9770 break;
9771 }
9772 /* XXX hardcoded keymap check... but anyway,
9773 * while view changes, tool-tips should be removed */
9774 case WHEELUPMOUSE:
9775 case WHEELDOWNMOUSE:
9776 case MIDDLEMOUSE:
9777 case MOUSEPAN:
9780 default:
9781 break;
9782 }
9783
9784 /* handle button type specific events */
9785 retval = ui_do_button(C, block, but, event);
9786 }
9787 else if (data->state == BUTTON_STATE_WAIT_RELEASE) {
9788 switch (event->type) {
9789 case WINDEACTIVATE:
9790 data->cancel = true;
9792 break;
9793
9794 case TIMER: {
9795 if (event->customdata == data->hold_action_timer) {
9796 if (true) {
9797 data->cancel = true;
9799 }
9800 else {
9801 /* Do this so we can still mouse-up, closing the menu and running the button.
9802 * This is nice to support but there are times when the button gets left pressed.
9803 * Keep disabled for now. */
9804 WM_event_timer_remove(data->wm, data->window, data->hold_action_timer);
9805 data->hold_action_timer = nullptr;
9806 }
9807 retval = WM_UI_HANDLER_CONTINUE;
9808 but->hold_func(C, data->region, but);
9809 }
9810 break;
9811 }
9812 case MOUSEMOVE: {
9813 /* deselect the button when moving the mouse away */
9814 /* also de-activate for buttons that only show highlights */
9815 if (ui_but_contains_point_px(but, region, event->xy)) {
9816
9817 /* Drag on a hold button (used in the toolbar) now opens it immediately. */
9818 if (data->hold_action_timer) {
9819 if (but->flag & UI_SELECT) {
9820 const int threshold = WM_event_drag_threshold(event);
9821 const int movement = len_manhattan_v2v2_int(event->xy, event->prev_press_xy);
9822 if (movement <= threshold) {
9823 /* pass */
9824 }
9825 else {
9826 WM_event_timer_remove(data->wm, data->window, data->hold_action_timer);
9827 data->hold_action_timer = WM_event_timer_add(data->wm, data->window, TIMER, 0.0f);
9828 }
9829 }
9830 }
9831
9832 if (!(but->flag & UI_SELECT)) {
9833 but->flag |= (UI_SELECT | UI_HOVER);
9834 data->cancel = false;
9836 }
9837 }
9838 else {
9839 if (but->flag & UI_SELECT) {
9840 but->flag &= ~(UI_SELECT | UI_HOVER);
9841 data->cancel = true;
9843 }
9844 }
9845 break;
9846 }
9847 default:
9848 /* otherwise catch mouse release event */
9849 ui_do_button(C, block, but, event);
9850 break;
9851 }
9852
9853 retval = WM_UI_HANDLER_BREAK;
9854 }
9855 else if (data->state == BUTTON_STATE_WAIT_FLASH) {
9856 switch (event->type) {
9857 case TIMER: {
9858 if (event->customdata == data->flashtimer) {
9860 }
9861 break;
9862 }
9863 default: {
9864 break;
9865 }
9866 }
9867
9868 retval = WM_UI_HANDLER_CONTINUE;
9869 }
9870 else if (data->state == BUTTON_STATE_MENU_OPEN) {
9871 /* check for exit because of mouse-over another button */
9872 switch (event->type) {
9873 case MOUSEMOVE: {
9874 uiBut *bt;
9875
9876 if (data->menu && data->menu->region) {
9877 if (ui_region_contains_point_px(data->menu->region, event->xy)) {
9878 break;
9879 }
9880 }
9881
9882 bt = UI_but_find_mouse_over(region, event);
9883
9884 if (bt && bt->active != data) {
9885 if (but->type != ButType::Color) { /* exception */
9886 data->cancel = true;
9887 }
9889 }
9890 break;
9891 }
9892 case RIGHTMOUSE: {
9893 if (event->val == KM_PRESS) {
9894 uiBut *bt = UI_but_find_mouse_over(region, event);
9895 if (bt && bt->active == data) {
9897 }
9898 }
9899 break;
9900 }
9901 default: {
9902 break;
9903 }
9904 }
9905
9906 ui_do_button(C, block, but, event);
9907 retval = WM_UI_HANDLER_CONTINUE;
9908 }
9909 else {
9910 retval = ui_do_button(C, block, but, event);
9911 // retval = WM_UI_HANDLER_BREAK; XXX why ?
9912 }
9913
9914 /* may have been re-allocated above (eyedropper for eg) */
9915 data = but->active;
9916 if (data && data->state == BUTTON_STATE_EXIT) {
9917 uiBut *post_but = data->postbut;
9918 const uiButtonActivateType post_type = data->posttype;
9919
9920 /* Reset the button value when empty text is typed. */
9921 if ((data->cancel == false) && (data->text_edit.edit_string != nullptr) &&
9922 (data->text_edit.edit_string[0] == '\0') &&
9924 {
9925 MEM_SAFE_FREE(data->text_edit.edit_string);
9926 ui_button_value_default(but, &data->value);
9927
9928#ifdef USE_DRAG_MULTINUM
9929 if (data->multi_data.mbuts) {
9930 for (LinkNode *l = data->multi_data.mbuts; l; l = l->next) {
9931 uiButMultiState *state = static_cast<uiButMultiState *>(l->link);
9932 uiBut *but_iter = state->but;
9933 double default_value;
9934
9935 if (ui_button_value_default(but_iter, &default_value)) {
9936 ui_but_value_set(but_iter, default_value);
9937 }
9938 }
9939 }
9940 data->multi_data.skip = true;
9941#endif
9942 }
9943
9944 button_activate_exit(C, but, data, (post_but == nullptr), false);
9945
9946 /* for jumping to the next button with tab while text editing */
9947 if (post_but) {
9948 /* The post_but still has previous ranges (without the changes in active button considered),
9949 * needs refreshing the ranges. */
9950 ui_but_range_set_soft(post_but);
9951 ui_but_range_set_hard(post_but);
9952
9953 button_activate_init(C, region, post_but, post_type);
9954 }
9955 else if (!((event->type == EVT_BUT_CANCEL) && (event->val == 1))) {
9956 /* XXX issue is because WM_event_add_mousemove(wm) is a bad hack and not reliable,
9957 * if that gets coded better this bypass can go away too.
9958 *
9959 * This is needed to make sure if a button was active,
9960 * it stays active while the mouse is over it.
9961 * This avoids adding mouse-moves, see: #33466. */
9963 if (UI_but_find_mouse_over(region, event) == but) {
9965 }
9966 }
9967 }
9968 }
9969
9970 return retval;
9971}
9972
9973static int ui_list_get_increment(const uiList *ui_list, const int type, const int columns)
9974{
9975 int increment = 0;
9976
9977 /* Handle column offsets for grid layouts. */
9978 if (ELEM(type, EVT_UPARROWKEY, EVT_DOWNARROWKEY) &&
9980 {
9981 increment = (type == EVT_UPARROWKEY) ? -columns : columns;
9982 }
9983 else {
9984 /* Left or right in grid layouts or any direction in single column layouts increments by 1. */
9985 increment = ELEM(type, EVT_UPARROWKEY, EVT_LEFTARROWKEY, WHEELUPMOUSE) ? -1 : 1;
9986 }
9987
9988 if ((ui_list->filter_sort_flag & UILST_FLT_SORT_REVERSE) != 0) {
9989 increment *= -1;
9990 }
9991
9992 return increment;
9993}
9994
9995static int ui_handle_list_event(bContext *C, const wmEvent *event, ARegion *region, uiBut *listbox)
9996{
9997 int retval = WM_UI_HANDLER_CONTINUE;
9998 int type = event->type, val = event->val;
9999 int scroll_dir = 1;
10000 bool redraw = false;
10001
10002 uiList *ui_list = static_cast<uiList *>(listbox->custom_data);
10003 if (!ui_list || !ui_list->dyn_data) {
10004 return retval;
10005 }
10006 uiListDyn *dyn_data = ui_list->dyn_data;
10007
10008 int mx = event->xy[0];
10009 int my = event->xy[1];
10010 ui_window_to_block(region, listbox->block, &mx, &my);
10011
10012 /* Convert pan to scroll-wheel. */
10013 if (type == MOUSEPAN) {
10014 ui_pan_to_scroll(event, &type, &val);
10015
10016 /* 'ui_pan_to_scroll' gives the absolute direction. */
10017 if (event->flag & WM_EVENT_SCROLL_INVERT) {
10018 scroll_dir = -1;
10019 }
10020
10021 /* If type still is mouse-pan, we call it handled, since delta-y accumulate. */
10022 /* also see `wm_event_system.cc` do_wheel_ui hack. */
10023 if (type == MOUSEPAN) {
10024 retval = WM_UI_HANDLER_BREAK;
10025 }
10026 }
10027
10028 if (val == KM_PRESS) {
10030 (event->modifier == 0)) ||
10031 (ELEM(type, WHEELUPMOUSE, WHEELDOWNMOUSE) && (event->modifier == KM_CTRL)))
10032 {
10033 const int value_orig = RNA_property_int_get(&listbox->rnapoin, listbox->rnaprop);
10034 int value, min, max;
10035
10036 value = value_orig;
10037 const int inc = ui_list_get_increment(ui_list, type, dyn_data->columns);
10038
10039 if (dyn_data->items_filter_neworder || dyn_data->items_filter_flags) {
10040 /* If we have a display order different from
10041 * collection order, we have some work! */
10042 int *org_order = MEM_malloc_arrayN<int>(dyn_data->items_shown, __func__);
10043 const int *new_order = dyn_data->items_filter_neworder;
10044 int org_idx = -1, len = dyn_data->items_len;
10045 int current_idx = -1;
10046
10047 for (int i = 0; i < len; i++) {
10049 org_order[new_order ? new_order[++org_idx] : ++org_idx] = i;
10050 if (i == value) {
10051 current_idx = new_order ? new_order[org_idx] : org_idx;
10052 }
10053 }
10054 else if (i == value && org_idx >= 0) {
10055 current_idx = -(new_order ? new_order[org_idx] : org_idx) - 1;
10056 }
10057 }
10058 /* Now, org_order maps displayed indices to real indices,
10059 * and current_idx either contains the displayed index of active value (positive),
10060 * or its more-nearest one (negated).
10061 */
10062 if (current_idx < 0) {
10063 current_idx = (current_idx * -1) + (inc < 0 ? inc : inc - 1);
10064 }
10065 else {
10066 current_idx += inc;
10067 }
10068 CLAMP(current_idx, 0, dyn_data->items_shown - 1);
10069 value = org_order[current_idx];
10070 MEM_freeN(org_order);
10071 }
10072 else {
10073 value += inc;
10074 }
10075
10076 CLAMP(value, 0, dyn_data->items_len - 1);
10077
10078 RNA_property_int_range(&listbox->rnapoin, listbox->rnaprop, &min, &max);
10079 CLAMP(value, min, max);
10080
10081 if (value != value_orig) {
10082 RNA_property_int_set(&listbox->rnapoin, listbox->rnaprop, value);
10083 RNA_property_update(C, &listbox->rnapoin, listbox->rnaprop);
10084
10085 ui_apply_but_undo(listbox);
10086
10088 redraw = true;
10089 }
10090 retval = WM_UI_HANDLER_BREAK;
10091 }
10092 else if (ELEM(type, WHEELUPMOUSE, WHEELDOWNMOUSE) && (event->modifier & KM_SHIFT)) {
10093 /* We now have proper grip, but keep this anyway! */
10094 if (ui_list->list_grip < (dyn_data->visual_height_min - UI_LIST_AUTO_SIZE_THRESHOLD)) {
10095 ui_list->list_grip = dyn_data->visual_height;
10096 }
10097 ui_list->list_grip += (type == WHEELUPMOUSE) ? -1 : 1;
10098
10100
10101 redraw = true;
10102 retval = WM_UI_HANDLER_BREAK;
10103 }
10104 else if (ELEM(type, WHEELUPMOUSE, WHEELDOWNMOUSE)) {
10105 if (dyn_data->height > dyn_data->visual_height) {
10106 /* list template will clamp */
10107 ui_list->list_scroll += scroll_dir * ((type == WHEELUPMOUSE) ? -1 : 1);
10108
10109 redraw = true;
10110 retval = WM_UI_HANDLER_BREAK;
10111 }
10112 }
10113 }
10114
10115 if (redraw) {
10116 ED_region_tag_redraw(region);
10118 }
10119
10120 return retval;
10121}
10122
10123/* Handle mouse hover for Views and UiList rows. */
10124static int ui_handle_viewlist_items_hover(const wmEvent *event, ARegion *region)
10125{
10126 const bool has_list = !BLI_listbase_is_empty(&region->ui_lists);
10127 const bool has_view = [&]() {
10128 LISTBASE_FOREACH (uiBlock *, block, &region->runtime->uiblocks) {
10129 if (!BLI_listbase_is_empty(&block->views)) {
10130 return true;
10131 }
10132 }
10133 return false;
10134 }();
10135
10136 if (!has_view && !has_list) {
10137 /* Avoid unnecessary lookup. */
10139 }
10140
10141 /* Always highlight the hovered view item, even if the mouse hovers another button inside. */
10142 uiBut *highlight_row_but = [&]() -> uiBut * {
10143 if (uiBut *but = ui_view_item_find_search_highlight(region)) {
10144 return but;
10145 }
10146 if (uiBut *but = ui_view_item_find_mouse_over(region, event->xy)) {
10147 return but;
10148 }
10149 if (uiBut *but = ui_list_row_find_mouse_over(region, event->xy)) {
10150 return but;
10151 }
10152 return nullptr;
10153 }();
10154
10155 bool changed = false;
10156
10157 if (highlight_row_but && !(highlight_row_but->flag & UI_HOVER)) {
10158 highlight_row_but->flag |= UI_HOVER;
10159 changed = true;
10160 }
10161
10162 LISTBASE_FOREACH (uiBlock *, block, &region->runtime->uiblocks) {
10163 for (const std::unique_ptr<uiBut> &but : block->buttons) {
10164 if (but.get() == highlight_row_but) {
10165 continue;
10166 }
10167 if (!ELEM(but->type, ButType::ViewItem, ButType::ListRow)) {
10168 continue;
10169 }
10170
10171 if (but->flag & UI_HOVER) {
10172 but->flag &= ~UI_HOVER;
10173 changed = true;
10174 }
10175 }
10176 }
10177
10178 if (changed) {
10180 }
10181
10183}
10184
10186 const wmEvent *event,
10187 uiBut *active_but,
10188 ARegion *region)
10189{
10190 switch (event->type) {
10191 case MOUSEMOVE:
10192 if (event->xy[0] != event->prev_xy[0] || event->xy[1] != event->prev_xy[1]) {
10194 }
10195 break;
10196 case LEFTMOUSE:
10197 if (event->modifier == 0) {
10198 /* Only bother finding the active view item button if the active button isn't already a
10199 * view item. */
10200 uiButViewItem *view_but = static_cast<uiButViewItem *>(
10201 (active_but && active_but->type == ButType::ViewItem) ?
10202 active_but :
10203 ui_view_item_find_mouse_over(region, event->xy));
10204
10205 if (view_but) {
10206 if (UI_view_item_supports_drag(*view_but->view_item)) {
10207 if (event->val != KM_CLICK) {
10208 break;
10209 }
10210 }
10211 else if (event->val != KM_PRESS) {
10212 break;
10213 }
10214
10215 /* Will free active button if there already is one. */
10216 /* Close the popup when clicking on the view item directly, not any overlapped button. */
10217 const bool close_popup = view_but == active_but;
10218 force_activate_view_item_but(C, region, view_but, close_popup);
10219 }
10220 }
10221 break;
10222 case EVT_RETKEY:
10223 case EVT_PADENTER:
10224 if (event->val == KM_PRESS) {
10225 if (uiButViewItem *search_highlight_but = static_cast<uiButViewItem *>(
10227 {
10228 force_activate_view_item_but(C, region, search_highlight_but);
10229 return WM_UI_HANDLER_BREAK;
10230 }
10231 }
10232 break;
10233 default:
10234 break;
10235 }
10236
10238}
10239
10240static void ui_handle_button_return_submenu(bContext *C, const wmEvent *event, uiBut *but)
10241{
10243 uiPopupBlockHandle *menu = data->menu;
10244
10245 /* copy over return values from the closing menu */
10246 if ((menu->menuretval & UI_RETURN_OK) || (menu->menuretval & UI_RETURN_UPDATE)) {
10247 if (but->type == ButType::Color) {
10248 copy_v3_v3(data->vec, menu->retvec);
10249 }
10250 else if (but->type == ButType::Menu) {
10251 data->value = menu->retvalue;
10252 }
10253 }
10254
10255 if (menu->menuretval & UI_RETURN_UPDATE) {
10256 if (data->interactive) {
10257 ui_apply_but(C, but->block, but, data, true);
10258 }
10259 else {
10260 ui_but_update(but);
10261 }
10262
10263 menu->menuretval = 0;
10264 }
10265
10266 /* now change button state or exit, which will close the submenu */
10267 if ((menu->menuretval & UI_RETURN_OK) || (menu->menuretval & UI_RETURN_CANCEL)) {
10268 if (menu->menuretval != UI_RETURN_OK) {
10269 data->cancel = true;
10270 }
10271
10272 button_activate_exit(C, but, data, true, false);
10273 }
10274 else if (menu->menuretval & UI_RETURN_OUT) {
10275 if (event->type == MOUSEMOVE && ui_but_contains_point_px(but, data->region, event->xy)) {
10277 }
10278 else {
10279 if (ISKEYBOARD(event->type)) {
10280 /* keyboard menu hierarchy navigation, going back to previous level */
10281 but->active->used_mouse = false;
10283 }
10284 else {
10285 data->cancel = true;
10286 button_activate_exit(C, but, data, true, false);
10287 }
10288 }
10289 }
10290}
10291
10293
10294/* -------------------------------------------------------------------- */
10297
10307
10309 const int xy[2],
10310 const bool force)
10311{
10312 BLI_assert(((uiBlock *)menu->region->runtime->uiblocks.first)->flag &
10314
10315 if (!menu->dotowards || force) {
10316 menu->dotowards = true;
10317 menu->towards_xy[0] = xy[0];
10318 menu->towards_xy[1] = xy[1];
10319
10320 if (force) {
10321 menu->towardstime = DBL_MAX; /* unlimited time */
10322 }
10323 else {
10325 }
10326 }
10327}
10328
10330{
10331 ui_mouse_motion_towards_init_ex(menu, xy, false);
10332}
10333
10335{
10337}
10338
10340 uiPopupBlockHandle *menu,
10341 const int xy[2],
10342 const bool use_wiggle_room)
10343{
10345
10346 /* annoying fix for #36269, this is a bit odd but in fact works quite well
10347 * don't mouse-out of a menu if another menu has been created after it.
10348 * if this causes problems we could remove it and check on a different fix - campbell */
10349 if (menu->region->next) {
10350 /* Test if this is the last menu. */
10351 ARegion *region = menu->region->next;
10352 do {
10353 uiBlock *block_iter = static_cast<uiBlock *>(region->runtime->uiblocks.first);
10354 if (block_iter && ui_block_is_menu(block_iter)) {
10355 return true;
10356 }
10357 } while ((region = region->next));
10358 }
10359 /* annoying fix end! */
10360
10361 if (!menu->dotowards) {
10362 return false;
10363 }
10364
10365 float oldp[2] = {menu->towards_xy[0], menu->towards_xy[1]};
10366 const float newp[2] = {float(xy[0]), float(xy[1])};
10367 if (len_squared_v2v2(oldp, newp) < (4.0f * 4.0f)) {
10368 return menu->dotowards;
10369 }
10370
10371 /* verify that we are moving towards one of the edges of the
10372 * menu block, in other words, in the triangle formed by the
10373 * initial mouse location and two edge points. */
10374 rctf rect_px;
10375 ui_block_to_window_rctf(menu->region, block, &rect_px, &block->rect);
10376
10377 const float margin = MENU_TOWARDS_MARGIN;
10378
10379 const float p1[2] = {rect_px.xmin - margin, rect_px.ymin - margin};
10380 const float p2[2] = {rect_px.xmax + margin, rect_px.ymin - margin};
10381 const float p3[2] = {rect_px.xmax + margin, rect_px.ymax + margin};
10382 const float p4[2] = {rect_px.xmin - margin, rect_px.ymax + margin};
10383
10384 /* allow for some wiggle room, if the user moves a few pixels away,
10385 * don't immediately quit (only for top level menus) */
10386 if (use_wiggle_room) {
10387 const float cent[2] = {BLI_rctf_cent_x(&rect_px), BLI_rctf_cent_y(&rect_px)};
10388 float delta[2];
10389
10390 sub_v2_v2v2(delta, oldp, cent);
10392 add_v2_v2(oldp, delta);
10393 }
10394
10395 bool closer = (isect_point_tri_v2(newp, oldp, p1, p2) ||
10396 isect_point_tri_v2(newp, oldp, p2, p3) ||
10397 isect_point_tri_v2(newp, oldp, p3, p4) || isect_point_tri_v2(newp, oldp, p4, p1));
10398
10399 if (!closer) {
10400 menu->dotowards = false;
10401 }
10402
10403 /* 1 second timer */
10405 menu->dotowards = false;
10406 }
10407
10408 return menu->dotowards;
10409}
10410
10411#ifdef USE_KEYNAV_LIMIT
10412static void ui_mouse_motion_keynav_init(uiKeyNavLock *keynav, const wmEvent *event)
10413{
10414 keynav->is_keynav = true;
10415 copy_v2_v2_int(keynav->event_xy, event->xy);
10416}
10417
10421static bool ui_mouse_motion_keynav_test(uiKeyNavLock *keynav, const wmEvent *event)
10422{
10423 if (keynav->is_keynav &&
10425 {
10426 keynav->is_keynav = false;
10427 }
10428
10429 return keynav->is_keynav;
10430}
10431#endif /* USE_KEYNAV_LIMIT */
10432
10434
10435/* -------------------------------------------------------------------- */
10438
10439static char ui_menu_scroll_test(uiBlock *block, int my)
10440{
10441 if (block->flag & (UI_BLOCK_CLIPTOP | UI_BLOCK_CLIPBOTTOM)) {
10442 if (block->flag & UI_BLOCK_CLIPTOP) {
10443 if (my > block->rect.ymax - UI_MENU_SCROLL_MOUSE) {
10444 return 't';
10445 }
10446 }
10447 if (block->flag & UI_BLOCK_CLIPBOTTOM) {
10448 if (my < block->rect.ymin + UI_MENU_SCROLL_MOUSE) {
10449 return 'b';
10450 }
10451 }
10452 }
10453 return 0;
10454}
10455
10456static void ui_menu_scroll_apply_offset_y(ARegion *region, uiBlock *block, float dy)
10457{
10458 BLI_assert(dy != 0.0f);
10459
10460 const int scroll_pad = ui_block_is_menu(block) ? UI_MENU_SCROLL_PAD : UI_UNIT_Y * 0.5f;
10461
10462 if (dy < 0.0f) {
10463 /* Stop at top item, extra 0.5 UI_UNIT_Y makes it snap nicer. */
10464 float ymax = -FLT_MAX;
10465 for (const std::unique_ptr<uiBut> &bt : block->buttons) {
10466 ymax = max_ff(ymax, bt->rect.ymax);
10467 }
10468 if (ymax + dy - UI_UNIT_Y * 0.5f < block->rect.ymax - scroll_pad) {
10469 dy = block->rect.ymax - ymax - scroll_pad;
10470 }
10471 }
10472 else {
10473 /* Stop at bottom item, extra 0.5 UI_UNIT_Y makes it snap nicer. */
10474 float ymin = FLT_MAX;
10475 for (const std::unique_ptr<uiBut> &bt : block->buttons) {
10476 ymin = min_ff(ymin, bt->rect.ymin);
10477 }
10478 if (ymin + dy + UI_UNIT_Y * 0.5f > block->rect.ymin + scroll_pad) {
10479 dy = block->rect.ymin - ymin + scroll_pad;
10480 }
10481 }
10482
10483 /* remember scroll offset for refreshes */
10484 block->handle->scrolloffset += dy;
10485 /* Apply popup scroll delta to layout panels too. */
10487
10488 /* apply scroll offset */
10489 for (const std::unique_ptr<uiBut> &bt : block->buttons) {
10490 bt->rect.ymin += dy;
10491 bt->rect.ymax += dy;
10492 }
10493
10494 /* set flags again */
10496
10497 ED_region_tag_redraw(region);
10498}
10499
10501static bool ui_menu_scroll_to_but(ARegion *region, uiBlock *block, uiBut *but_target)
10502{
10503 float dy = 0.0;
10504 if (block->flag & UI_BLOCK_CLIPTOP) {
10505 if (but_target->rect.ymax > block->rect.ymax - UI_MENU_SCROLL_ARROW) {
10506 dy = block->rect.ymax - but_target->rect.ymax - UI_MENU_SCROLL_ARROW;
10507 }
10508 }
10509 if (block->flag & UI_BLOCK_CLIPBOTTOM) {
10510 if (but_target->rect.ymin < block->rect.ymin + UI_MENU_SCROLL_ARROW) {
10511 dy = block->rect.ymin - but_target->rect.ymin + UI_MENU_SCROLL_ARROW;
10512 }
10513 }
10514 if (dy != 0.0f) {
10515 ui_menu_scroll_apply_offset_y(region, block, dy);
10516 return true;
10517 }
10518 return false;
10519}
10520
10522static bool ui_menu_scroll_to_y(ARegion *region, uiBlock *block, int y)
10523{
10524 const char test = ui_menu_scroll_test(block, y);
10525 float dy = 0.0f;
10526 if (test == 't') {
10527 dy = -UI_UNIT_Y; /* scroll to the top */
10528 }
10529 else if (test == 'b') {
10530 dy = UI_UNIT_Y; /* scroll to the bottom */
10531 }
10532 if (dy != 0.0f) {
10533 ui_menu_scroll_apply_offset_y(region, block, dy);
10534 return true;
10535 }
10536 return false;
10537}
10538
10539static bool ui_menu_scroll_step(ARegion *region, uiBlock *block, const int scroll_dir)
10540{
10541 int my;
10542 if (scroll_dir == 1) {
10543 if ((block->flag & UI_BLOCK_CLIPTOP) == 0) {
10544 return false;
10545 }
10546 my = block->rect.ymax + UI_UNIT_Y;
10547 }
10548 else if (scroll_dir == -1) {
10549 if ((block->flag & UI_BLOCK_CLIPBOTTOM) == 0) {
10550 return false;
10551 }
10552 my = block->rect.ymin - UI_UNIT_Y;
10553 }
10554 else {
10555 BLI_assert(0);
10556 return false;
10557 }
10558
10559 return ui_menu_scroll_to_y(region, block, my);
10560}
10561
10563
10564/* -------------------------------------------------------------------- */
10567
10569{
10570 LISTBASE_FOREACH (uiBlock *, block, &region->runtime->uiblocks) {
10571 block->auto_open = false;
10572 }
10573}
10574
10585 const uiBut *but,
10586 const int level,
10587 const bool is_parent_menu,
10588 const int retval)
10589{
10590 /* NOTE(@ideasman42): For `menu->popup` (not a nested tree of menus), don't pass events parents.
10591 * This is needed because enum popups (for example) aren't created with an active button.
10592 * Otherwise opening a popup & pressing the accelerator key would fail, see: #107838. */
10593 if ((level != 0) && (but == nullptr) && (is_parent_menu || menu->popup == false)) {
10595 (void)retval; /* so release builds with strict flags are happy as well */
10597 return true;
10598 }
10599 return false;
10600}
10601
10603{
10604 ARegion *region = menu->region;
10605 uiBut *but = ui_region_find_active_but(region);
10606
10607 if (but) {
10608 /* Its possible there is an active menu item NOT under the mouse,
10609 * in this case ignore mouse clicks outside the button (but Enter etc is accepted) */
10610 if (event->val == KM_RELEASE) {
10611 /* pass, needed so we can exit active menu-items when click-dragging out of them */
10612 }
10613 else if (but->type == ButType::SearchMenu) {
10614 /* Pass, needed so search popup can have RMB context menu.
10615 * This may be useful for other interactions which happen in the search popup
10616 * without being directly over the search button. */
10617 }
10618 else if (!ui_block_is_menu(but->block) || ui_block_is_pie_menu(but->block)) {
10619 /* pass, skip for dialogs */
10620 }
10621 else if (!ui_region_contains_point_px(but->active->region, event->xy)) {
10622 /* Pass, needed to click-exit outside of non-floating menus. */
10624 }
10625 else if (ISMOUSE_BUTTON(event->type)) {
10626 if (!ui_but_contains_point_px(but, but->active->region, event->xy)) {
10627 but = nullptr;
10628 }
10629 }
10630 }
10631
10632 int retval;
10633 if (but) {
10634 ScrArea *ctx_area = CTX_wm_area(C);
10635 ARegion *ctx_region = CTX_wm_region(C);
10636
10637 if (menu->ctx_area) {
10638 CTX_wm_area_set(C, menu->ctx_area);
10639 }
10640 if (menu->ctx_region) {
10642 }
10643
10644 retval = ui_handle_button_event(C, event, but);
10645
10646 if (menu->ctx_area) {
10647 CTX_wm_area_set(C, ctx_area);
10648 }
10649 if (menu->ctx_region) {
10650 CTX_wm_region_set(C, ctx_region);
10651 }
10652 }
10653 else {
10654 retval = ui_handle_button_over(C, event, region);
10655 }
10656
10657 return retval;
10658}
10659
10660float ui_block_calc_pie_segment(uiBlock *block, const float event_xy[2])
10661{
10662 float seg1[2];
10663
10665 copy_v2_v2(seg1, block->pie_data.pie_center_init);
10666 }
10667 else {
10669 }
10670
10671 float seg2[2];
10672 sub_v2_v2v2(seg2, event_xy, seg1);
10673
10674 const float len = normalize_v2_v2(block->pie_data.pie_dir, seg2);
10675
10676 if (len < U.pie_menu_threshold * UI_SCALE_FAC) {
10678 }
10679 else {
10681 }
10682
10683 return len;
10684}
10685
10687{
10688 /* Start menu search if the menu has a name. */
10689 if (menu->menu_idname[0]) {
10690 uiAfterFunc *after = ui_afterfunc_new();
10691 wmOperatorType *ot = WM_operatortype_find("WM_OT_search_single_menu", false);
10692 after->optype = ot;
10694 after->opptr = MEM_new<PointerRNA>(__func__);
10696 RNA_string_set(after->opptr, "menu_idname", menu->menu_idname);
10697 if (event->type != EVT_SPACEKEY) {
10698 /* Forward all keys except space-bar to the search. */
10699 const int num_bytes = BLI_str_utf8_size_or_error(event->utf8_buf);
10700 if (num_bytes != -1) {
10701 char buf[sizeof(event->utf8_buf) + 1];
10702 memcpy(buf, event->utf8_buf, num_bytes);
10703 buf[num_bytes] = '\0';
10704 RNA_string_set(after->opptr, "initial_query", buf);
10705 }
10706 }
10707 menu->menuretval = UI_RETURN_OK;
10708 return WM_UI_HANDLER_BREAK;
10709 }
10711}
10712
10714 const wmEvent *event,
10715 uiPopupBlockHandle *menu,
10716 int level,
10717 const bool is_parent_inside,
10718 const bool is_parent_menu,
10719 const bool is_floating)
10720{
10721 uiBut *but;
10722 ARegion *region = menu->region;
10723 uiBlock *block = static_cast<uiBlock *>(region->runtime->uiblocks.first);
10724
10725 int retval = WM_UI_HANDLER_CONTINUE;
10726
10727 int mx = event->xy[0];
10728 int my = event->xy[1];
10729 ui_window_to_block(region, block, &mx, &my);
10730
10731 /* check if mouse is inside block */
10732 const bool inside = BLI_rctf_isect_pt(&block->rect, mx, my);
10733 /* check for title dragging */
10734 const bool inside_title = inside && ((my + (UI_UNIT_Y * 1.4f)) > block->rect.ymax);
10735
10736 /* if there's an active modal button, don't check events or outside, except for search menu */
10737 but = ui_region_find_active_but(region);
10738
10739#ifdef USE_DRAG_POPUP
10740
10741# if defined(__APPLE__)
10742 constexpr int PopupTitleHoverCursor = WM_CURSOR_HAND;
10743 constexpr int PopupTitleDragCursor = WM_CURSOR_HAND_CLOSED;
10744# else
10745 constexpr int PopupTitleHoverCursor = WM_CURSOR_MOVE;
10746 constexpr int PopupTitleDragCursor = WM_CURSOR_MOVE;
10747# endif
10748
10749 wmWindow *win = CTX_wm_window(C);
10750
10751 if (!menu->is_grab && is_floating) {
10752 if (inside_title && (!but || but->type == ButType::Image)) {
10753 if (event->type == LEFTMOUSE && event->val == KM_PRESS) {
10754 /* Initial press before starting to drag. */
10755 WM_cursor_set(win, PopupTitleDragCursor);
10756 }
10757 else if (event->type == MOUSEMOVE && !win->modalcursor) {
10758 /* Hover over draggable area. */
10759 WM_cursor_set(win, PopupTitleHoverCursor);
10760 }
10761 }
10762 else if (win->cursor == PopupTitleHoverCursor) {
10764 }
10765 }
10766
10767 if (menu->is_grab) {
10768 if (event->type == LEFTMOUSE) {
10769 menu->is_grab = false;
10771 retval = WM_UI_HANDLER_BREAK;
10772 }
10773 else {
10774 if (event->type == MOUSEMOVE) {
10775 WM_cursor_set(win, PopupTitleDragCursor);
10776 blender::int2 mdiff = blender::int2(event->xy) - blender::int2(menu->grab_xy_prev);
10777
10778 copy_v2_v2_int(menu->grab_xy_prev, event->xy);
10779
10780 menu->popup_create_vars.event_xy += mdiff;
10781
10782 ui_popup_translate(region, mdiff);
10783 }
10784
10785 return retval;
10786 }
10787 }
10788#endif
10789
10790 if (but && button_modal_state(but->active->state)) {
10792 /* if a button is activated modal, always reset the start mouse
10793 * position of the towards mechanism to avoid losing focus,
10794 * and don't handle events */
10795 ui_mouse_motion_towards_reinit(menu, event->xy);
10796 }
10797 }
10798 else if (event->type == TIMER) {
10799 if (event->customdata == menu->scrolltimer) {
10800 ui_menu_scroll_to_y(region, block, my);
10801 }
10802 }
10803 else {
10804 /* for ui_mouse_motion_towards_block */
10805 if (event->type == MOUSEMOVE) {
10807 ui_mouse_motion_towards_init(menu, event->xy);
10808 }
10809
10810 /* add menu scroll timer, if needed */
10811 if (ui_menu_scroll_test(block, my)) {
10812 if (menu->scrolltimer == nullptr) {
10815 }
10816 }
10817 }
10818
10819 /* first block own event func */
10820 if (block->block_event_func && block->block_event_func(C, block, event)) {
10821 /* pass */
10822 } /* events not for active search menu button */
10823 else {
10824 int act = 0;
10825
10826 switch (event->type) {
10827
10828 /* Closing sub-levels of pull-downs.
10829 *
10830 * The actual event is handled by the button under the cursor.
10831 * This is done so we can right click on menu items even when they have sub-menus open.
10832 */
10833 case RIGHTMOUSE:
10834 if (inside == false) {
10835 if (event->val == KM_PRESS && (block->flag & UI_BLOCK_LOOP)) {
10836 if (block->saferct.first) {
10837 /* Currently right clicking on a top level pull-down (typically in the header)
10838 * just closes the menu and doesn't support immediately handling the RMB event.
10839 *
10840 * To support we would need UI_RETURN_OUT_PARENT to be handled by
10841 * top-level buttons, not just menus. Note that this isn't very important
10842 * since it's easy to manually close these menus by clicking on them. */
10843 menu->menuretval = (level > 0 && is_parent_inside) ? UI_RETURN_OUT_PARENT :
10845 }
10846 }
10847 retval = WM_UI_HANDLER_BREAK;
10848 }
10849 break;
10850
10851 /* Closing sub-levels of pull-downs. */
10852 case EVT_LEFTARROWKEY:
10853 if (event->val == KM_PRESS && (block->flag & UI_BLOCK_LOOP)) {
10854 if (block->saferct.first) {
10855 menu->menuretval = UI_RETURN_OUT;
10856 }
10857 }
10858
10859 retval = WM_UI_HANDLER_BREAK;
10860 break;
10861
10862 /* Opening sub-levels of pull-downs. */
10863 case EVT_RIGHTARROWKEY:
10864 if (event->val == KM_PRESS && (block->flag & UI_BLOCK_LOOP)) {
10865
10867 menu, but, level, is_parent_menu, retval))
10868 {
10869 break;
10870 }
10871
10872 but = ui_region_find_active_but(region);
10873
10874 if (!but) {
10875 /* no item active, we make first active */
10876 if (block->direction & UI_DIR_UP) {
10877 but = ui_but_last(block);
10878 }
10879 else {
10880 but = ui_but_first(block);
10881 }
10882 }
10883
10884 if (but && ELEM(but->type, ButType::Block, ButType::Pulldown)) {
10886 }
10887 }
10888
10889 retval = WM_UI_HANDLER_BREAK;
10890 break;
10891
10892 /* Smooth scrolling for popovers. */
10893 case MOUSEPAN: {
10894 if (event->modifier) {
10895 /* pass */
10896 }
10897 else if (!ui_block_is_menu(block)) {
10898 if (block->flag & (UI_BLOCK_CLIPTOP | UI_BLOCK_CLIPBOTTOM)) {
10899 const float dy = event->xy[1] - event->prev_xy[1];
10900 if (dy != 0.0f) {
10901 ui_menu_scroll_apply_offset_y(region, block, dy);
10902
10903 if (but) {
10904 but->active->cancel = true;
10905 button_activate_exit(C, but, but->active, false, false);
10906 }
10908 }
10909 }
10910 break;
10911 }
10913 }
10914 case WHEELUPMOUSE:
10915 case WHEELDOWNMOUSE: {
10916 if (event->modifier) {
10917 /* pass */
10918 }
10919 else if (!ui_block_is_menu(block)) {
10920 const int scroll_dir = (event->type == WHEELUPMOUSE) ? 1 : -1;
10921 if (ui_menu_scroll_step(region, block, scroll_dir)) {
10922 if (but) {
10923 but->active->cancel = true;
10924 button_activate_exit(C, but, but->active, false, false);
10925 }
10927 }
10928 break;
10929 }
10931 }
10932 case EVT_UPARROWKEY:
10933 case EVT_DOWNARROWKEY:
10934 case EVT_PAGEUPKEY:
10935 case EVT_PAGEDOWNKEY:
10936 case EVT_HOMEKEY:
10937 case EVT_ENDKEY:
10938 /* Arrow-keys: only handle for block_loop blocks. */
10939 if (event->modifier) {
10940 /* pass */
10941 }
10942 else if (inside || (block->flag & UI_BLOCK_LOOP)) {
10943 int type = event->type;
10944 int val = event->val;
10945
10946 /* Convert pan to scroll-wheel. */
10947 if (type == MOUSEPAN) {
10948 ui_pan_to_scroll(event, &type, &val);
10949 }
10950
10951 if (val == KM_PRESS) {
10952 /* Determine scroll operation. */
10953 uiMenuScrollType scrolltype;
10954
10955 if (ELEM(type, EVT_PAGEUPKEY, EVT_HOMEKEY)) {
10956 scrolltype = MENU_SCROLL_TOP;
10957 }
10958 else if (ELEM(type, EVT_PAGEDOWNKEY, EVT_ENDKEY)) {
10959 scrolltype = MENU_SCROLL_BOTTOM;
10960 }
10961 else if (ELEM(type, EVT_UPARROWKEY, WHEELUPMOUSE)) {
10962 scrolltype = MENU_SCROLL_UP;
10963 }
10964 else {
10965 scrolltype = MENU_SCROLL_DOWN;
10966 }
10967
10969 menu, but, level, is_parent_menu, retval))
10970 {
10971 break;
10972 }
10973
10974#ifdef USE_KEYNAV_LIMIT
10976#endif
10977
10978 but = ui_region_find_active_but(region);
10979 if (but) {
10980 /* Apply scroll operation. */
10981 if (scrolltype == MENU_SCROLL_DOWN) {
10982 but = ui_but_next(but);
10983 }
10984 else if (scrolltype == MENU_SCROLL_UP) {
10985 but = ui_but_prev(but);
10986 }
10987 else if (scrolltype == MENU_SCROLL_TOP) {
10988 but = ui_but_first(block);
10989 }
10990 else if (scrolltype == MENU_SCROLL_BOTTOM) {
10991 but = ui_but_last(block);
10992 }
10993 }
10994
10995 if (!but) {
10996 /* Wrap button or no active button. */
10997 uiBut *but_wrap = nullptr;
10998 if (ELEM(scrolltype, MENU_SCROLL_UP, MENU_SCROLL_BOTTOM)) {
10999 but_wrap = ui_but_last(block);
11000 }
11001 else if (ELEM(scrolltype, MENU_SCROLL_DOWN, MENU_SCROLL_TOP)) {
11002 but_wrap = ui_but_first(block);
11003 }
11004 if (but_wrap) {
11005 but = but_wrap;
11006 }
11007 }
11008
11009 if (but) {
11011 ui_menu_scroll_to_but(region, block, but);
11012 }
11013 }
11014
11015 retval = WM_UI_HANDLER_BREAK;
11016 }
11017
11018 break;
11019
11020 case EVT_ONEKEY:
11021 case EVT_PAD1:
11022 act = 1;
11024 case EVT_TWOKEY:
11025 case EVT_PAD2:
11026 if (act == 0) {
11027 act = 2;
11028 }
11030 case EVT_THREEKEY:
11031 case EVT_PAD3:
11032 if (act == 0) {
11033 act = 3;
11034 }
11036 case EVT_FOURKEY:
11037 case EVT_PAD4:
11038 if (act == 0) {
11039 act = 4;
11040 }
11042 case EVT_FIVEKEY:
11043 case EVT_PAD5:
11044 if (act == 0) {
11045 act = 5;
11046 }
11048 case EVT_SIXKEY:
11049 case EVT_PAD6:
11050 if (act == 0) {
11051 act = 6;
11052 }
11054 case EVT_SEVENKEY:
11055 case EVT_PAD7:
11056 if (act == 0) {
11057 act = 7;
11058 }
11060 case EVT_EIGHTKEY:
11061 case EVT_PAD8:
11062 if (act == 0) {
11063 act = 8;
11064 }
11066 case EVT_NINEKEY:
11067 case EVT_PAD9:
11068 if (act == 0) {
11069 act = 9;
11070 }
11072 case EVT_ZEROKEY:
11073 case EVT_PAD0:
11074 if (act == 0) {
11075 act = 10;
11076 }
11077
11078 if ((block->flag & UI_BLOCK_NUMSELECT) && event->val == KM_PRESS) {
11079 int count;
11080
11082 menu, but, level, is_parent_menu, retval))
11083 {
11084 break;
11085 }
11086
11087 /* Only respond to explicit press to avoid the event that opened the menu
11088 * activating an item when the key is held. */
11089 if (event->flag & WM_EVENT_IS_REPEAT) {
11090 break;
11091 }
11092
11093 if (event->modifier & KM_ALT) {
11094 act += 10;
11095 }
11096
11097 count = 0;
11098 for (const std::unique_ptr<uiBut> &but : block->buttons) {
11099 bool doit = false;
11100
11101 if (!ELEM(
11103 {
11104 count++;
11105 }
11106
11107 /* exception for rna layer buts */
11108 if (but->rnapoin.data && but->rnaprop &&
11110 {
11111 if (but->rnaindex == act - 1) {
11112 doit = true;
11113 }
11114 }
11115 else if (ELEM(but->type,
11121 count == act)
11122 {
11123 doit = true;
11124 }
11125
11126 if (!(but->flag & UI_BUT_DISABLED) && doit) {
11127 /* activate buttons but open menu's */
11129 if (but->type == ButType::Pulldown) {
11131 }
11132 else {
11134 }
11135
11136 ui_handle_button_activate(C, region, but.get(), activate);
11137 break;
11138 }
11139 }
11140
11141 retval = WM_UI_HANDLER_BREAK;
11142 }
11143 break;
11144
11145 /* Handle keystrokes on menu items */
11146 case EVT_AKEY:
11147 case EVT_BKEY:
11148 case EVT_CKEY:
11149 case EVT_DKEY:
11150 case EVT_EKEY:
11151 case EVT_FKEY:
11152 case EVT_GKEY:
11153 case EVT_HKEY:
11154 case EVT_IKEY:
11155 case EVT_JKEY:
11156 case EVT_KKEY:
11157 case EVT_LKEY:
11158 case EVT_MKEY:
11159 case EVT_NKEY:
11160 case EVT_OKEY:
11161 case EVT_PKEY:
11162 case EVT_QKEY:
11163 case EVT_RKEY:
11164 case EVT_SKEY:
11165 case EVT_TKEY:
11166 case EVT_UKEY:
11167 case EVT_VKEY:
11168 case EVT_WKEY:
11169 case EVT_XKEY:
11170 case EVT_YKEY:
11171 case EVT_ZKEY:
11172 case EVT_SPACEKEY: {
11173 if (ELEM(event->val, KM_PRESS, KM_DBL_CLICK) && ((event->modifier & ~KM_ALT) == 0) &&
11174 /* Only respond to explicit press to avoid the event that opened the menu
11175 * activating an item when the key is held. */
11176 (event->flag & WM_EVENT_IS_REPEAT) == 0)
11177 {
11178
11179 /* Menu search if space-bar or #MenuTypeFlag::SearchOnKeyPress. */
11180 MenuType *mt = WM_menutype_find(menu->menu_idname, true);
11181 if ((mt && bool(mt->flag & MenuTypeFlag::SearchOnKeyPress)) ||
11182 event->type == EVT_SPACEKEY)
11183 {
11184 if ((level != 0) && (but == nullptr || !menu->menu_idname[0])) {
11185 /* Search parent if the child is open but not activated or not searchable. */
11187 }
11188 else {
11189 retval = ui_handle_menu_letter_press_search(menu, event);
11190 }
11191 break;
11192 }
11193
11195 menu, but, level, is_parent_menu, retval))
11196 {
11197 break;
11198 }
11199
11200 /* Accelerator keys that allow "pressing" a menu entry by pressing a single key. */
11201 for (const std::unique_ptr<uiBut> &but_iter : block->buttons) {
11202 if (!(but_iter->flag & UI_BUT_DISABLED) && but_iter->menu_key == event->type) {
11204 {
11205 UI_but_execute(C, region, but_iter.get());
11206 }
11207 else {
11208 ui_handle_button_activate_by_type(C, region, but_iter.get());
11209 }
11210 return WM_UI_HANDLER_BREAK;
11211 }
11212 }
11213 }
11214 default: {
11215 break;
11216 }
11217 }
11218 }
11219 }
11220
11221 /* here we check return conditions for menus */
11222 if (block->flag & UI_BLOCK_LOOP) {
11223 /* If we click outside the block, verify if we clicked on the
11224 * button that opened us, otherwise we need to close,
11225 *
11226 * note that there is an exception for root level menus and
11227 * popups which you can click again to close.
11228 *
11229 * Events handled above may have already set the return value,
11230 * don't overwrite them, see: #61015.
11231 */
11232 if ((inside == false) && (menu->menuretval == 0)) {
11233 uiSafetyRct *saferct = static_cast<uiSafetyRct *>(block->saferct.first);
11234
11235 if (event->type == MOUSEMOVE) {
11236 WM_tooltip_clear(C, win);
11237 }
11238
11239 if (ELEM(event->type, LEFTMOUSE, MIDDLEMOUSE, RIGHTMOUSE)) {
11240 if (ELEM(event->val, KM_PRESS, KM_DBL_CLICK)) {
11241 if ((is_parent_menu == false) && (U.uiflag & USER_MENUOPENAUTO) == 0) {
11242 /* for root menus, allow clicking to close */
11243 if (block->flag & UI_BLOCK_OUT_1) {
11244 menu->menuretval = UI_RETURN_OK;
11245 }
11246 else {
11247 menu->menuretval = UI_RETURN_OUT;
11248 }
11249 }
11250 else if (saferct && !BLI_rctf_isect_pt(
11251 &saferct->parent, float(event->xy[0]), float(event->xy[1])))
11252 {
11253 if (block->flag & UI_BLOCK_OUT_1) {
11254 menu->menuretval = UI_RETURN_OK;
11255 }
11256 else {
11257 menu->menuretval = (U.flag & USER_MENU_CLOSE_LEAVE) ? UI_RETURN_OUT :
11259 }
11260 }
11261 }
11262 else if (ELEM(event->val, KM_RELEASE, KM_CLICK)) {
11263 /* For buttons that use a hold function,
11264 * exit when mouse-up outside the menu. */
11265 if (block->flag & UI_BLOCK_POPUP_HOLD) {
11266 /* NOTE: we could check the cursor is over the parent button. */
11268 retval = WM_UI_HANDLER_CONTINUE;
11269 }
11270 }
11271 }
11272 }
11273
11274 if (menu->menuretval) {
11275 /* pass */
11276 }
11277#ifdef USE_KEYNAV_LIMIT
11278 else if ((event->type == MOUSEMOVE) &&
11280 {
11281 /* Don't handle the mouse-move if we're using key-navigation. */
11282 retval = WM_UI_HANDLER_BREAK;
11283 }
11284#endif
11285 else if (event->type == EVT_ESCKEY && event->val == KM_PRESS) {
11286 /* Escape cancels this and all preceding menus. */
11288 }
11289 else if (ELEM(event->type, EVT_RETKEY, EVT_PADENTER) && event->val == KM_PRESS) {
11292 if ((but_default != nullptr) && (but_default->active == nullptr)) {
11293 if (but_default->type == ButType::But) {
11294 UI_but_execute(C, region, but_default);
11295 retval = WM_UI_HANDLER_BREAK;
11296 }
11297 else {
11298 ui_handle_button_activate_by_type(C, region, but_default);
11299 }
11300 }
11301 else {
11302 uiBut *but_active = ui_region_find_active_but(region);
11303
11304 /* enter will always close this block, we let the event
11305 * get handled by the button if it is activated, otherwise we cancel */
11306 if (but_active == nullptr) {
11308 }
11309 }
11310 }
11311#ifdef USE_DRAG_POPUP
11312 else if ((event->type == LEFTMOUSE) && (event->val == KM_PRESS) &&
11313 (inside && is_floating && inside_title))
11314 {
11315 if (!but || but->type == ButType::Image ||
11316 !ui_but_contains_point_px(but, region, event->xy))
11317 {
11318 if (but) {
11320 }
11321
11322 menu->is_grab = true;
11323 copy_v2_v2_int(menu->grab_xy_prev, event->xy);
11324 retval = WM_UI_HANDLER_BREAK;
11325 }
11326 }
11327#endif
11328 else {
11329
11330 /* check mouse moving outside of the menu */
11331 if (inside == false && (block->flag & (UI_BLOCK_MOVEMOUSE_QUIT | UI_BLOCK_POPOVER))) {
11332 uiSafetyRct *saferct;
11333
11334 ui_mouse_motion_towards_check(block, menu, event->xy, is_parent_inside == false);
11335
11336 /* Check for all parent rects, enables arrow-keys to be used. */
11337 for (saferct = static_cast<uiSafetyRct *>(block->saferct.first); saferct;
11338 saferct = saferct->next)
11339 {
11340 /* for mouse move we only check our own rect, for other
11341 * events we check all preceding block rects too to make
11342 * arrow keys navigation work */
11343 if (event->type != MOUSEMOVE ||
11344 saferct == static_cast<uiSafetyRct *>(block->saferct.first))
11345 {
11346 if (BLI_rctf_isect_pt(&saferct->parent, float(event->xy[0]), float(event->xy[1]))) {
11347 break;
11348 }
11349 if (BLI_rctf_isect_pt(&saferct->safety, float(event->xy[0]), float(event->xy[1]))) {
11350 break;
11351 }
11352 }
11353 }
11354
11355 /* strict check, and include the parent rect */
11356 if (!menu->dotowards && !saferct && ((U.flag & USER_MENU_CLOSE_LEAVE) || level > 0)) {
11357 if (block->flag & UI_BLOCK_OUT_1) {
11358 menu->menuretval = UI_RETURN_OK;
11359 }
11360 else {
11361 menu->menuretval = UI_RETURN_OUT;
11362 }
11363 }
11364 else if (menu->dotowards && event->type == MOUSEMOVE) {
11365 retval = WM_UI_HANDLER_BREAK;
11366 }
11367 }
11368 }
11369
11370 /* end switch */
11371 }
11372 }
11373
11374 /* if we are didn't handle the event yet, lets pass it on to
11375 * buttons inside this region. disabled inside check .. not sure
11376 * anymore why it was there? but it meant enter didn't work
11377 * for example when mouse was not over submenu */
11378 if ((event->type == TIMER) ||
11379 (/* inside && */ (!menu->menuretval || (menu->menuretval & UI_RETURN_UPDATE)) &&
11380 retval == WM_UI_HANDLER_CONTINUE))
11381 {
11382 retval = ui_handle_menu_button(C, event, menu);
11383 }
11384
11385#ifdef USE_UI_POPOVER_ONCE
11386 if (block->flag & UI_BLOCK_POPOVER_ONCE) {
11387 if ((event->type == LEFTMOUSE) && (event->val == KM_RELEASE)) {
11389 block->flag &= ~UI_BLOCK_POPOVER_ONCE;
11390 }
11391 }
11392#endif
11393
11394 /* Don't handle double click events, re-handle as regular press/release. */
11395 if (retval == WM_UI_HANDLER_CONTINUE && event->val == KM_DBL_CLICK) {
11396 return retval;
11397 }
11398
11399 /* if we set a menu return value, ensure we continue passing this on to
11400 * lower menus and buttons, so always set continue then, and if we are
11401 * inside the region otherwise, ensure we swallow the event */
11402 if (menu->menuretval) {
11404 }
11405 if (inside) {
11406 return WM_UI_HANDLER_BREAK;
11407 }
11408 return retval;
11409}
11410
11412 const wmEvent *event,
11413 uiPopupBlockHandle *menu)
11414{
11415 ARegion *region = menu->region;
11416 uiBlock *block = static_cast<uiBlock *>(region->runtime->uiblocks.first);
11417
11418 uiBut *but = ui_region_find_active_but(region);
11419
11420 BLI_assert(but);
11421
11423 uiPopupBlockHandle *submenu = data->menu;
11424
11425 if (submenu->menuretval) {
11426 bool update;
11427
11428 /* first decide if we want to close our own menu cascading, if
11429 * so pass on the sub menu return value to our own menu handle */
11430 if ((submenu->menuretval & UI_RETURN_OK) || (submenu->menuretval & UI_RETURN_CANCEL)) {
11431 if (!(block->flag & UI_BLOCK_KEEP_OPEN)) {
11432 menu->menuretval = submenu->menuretval;
11433 menu->butretval = data->retval;
11434 }
11435 }
11436
11437 update = (submenu->menuretval & UI_RETURN_UPDATE) != 0;
11438
11439 /* now let activated button in this menu exit, which
11440 * will actually close the submenu too */
11442
11443 if (update) {
11444 submenu->menuretval = 0;
11445 }
11446 }
11447
11449 /* for cases where close does not cascade, allow the user to
11450 * move the mouse back towards the menu without closing */
11451 ui_mouse_motion_towards_reinit(menu, event->xy);
11452 }
11453
11454 if (menu->menuretval) {
11456 }
11457 return WM_UI_HANDLER_BREAK;
11458}
11459
11461{
11462 return !ELEM(but->type, ButType::NumSlider, ButType::Num);
11463}
11464
11466 uiPopupBlockHandle *menu,
11467 uiBut *but,
11468 bool force_close)
11469{
11470 const int retval = WM_UI_HANDLER_BREAK;
11471
11472 if (but && ui_but_pie_menu_supported_apply(but)) {
11473 if (but->type == ButType::Menu) {
11474 /* forcing the pie menu to close will not handle menus */
11475 if (!force_close) {
11476 uiBut *active_but = ui_region_find_active_but(menu->region);
11477
11478 if (active_but) {
11479 button_activate_exit(C, active_but, active_but->active, false, false);
11480 }
11481
11483 return retval;
11484 }
11486 }
11487 else {
11488 button_activate_exit(C, but, but->active, false, false);
11489
11490 menu->menuretval = UI_RETURN_OK;
11491 }
11492 }
11493 else {
11495
11497 }
11498
11499 return retval;
11500}
11501
11503{
11504 if ((block->flag & UI_BLOCK_NUMSELECT) && event->val == KM_PRESS) {
11505 for (const std::unique_ptr<uiBut> &but : block->buttons) {
11506 if (but->pie_dir == dir && !ELEM(but->type, ButType::Sepr, ButType::SeprLine)) {
11507 return but.get();
11508 }
11509 }
11510 }
11511
11512 return nullptr;
11513}
11514
11516{
11517 if (but == nullptr) {
11518 return WM_UI_HANDLER_BREAK;
11519 }
11520
11521 uiBut *active_but = ui_region_find_active_but(menu->region);
11522
11523 if (active_but) {
11524 /* Use onfree to not execute the hovered active_but. */
11525 button_activate_exit(C, active_but, active_but->active, false, true);
11526 }
11527
11529 return ui_but_pie_menu_apply(C, menu, but, false);
11530}
11531
11532static int ui_pie_handler(bContext *C, const wmEvent *event, uiPopupBlockHandle *menu)
11533{
11534 /* we block all events, this is modal interaction,
11535 * except for drop events which is described below */
11536 int retval = WM_UI_HANDLER_BREAK;
11537
11538 if (event->type == EVT_DROP) {
11539 /* may want to leave this here for later if we support pie ovens */
11540
11541 retval = WM_UI_HANDLER_CONTINUE;
11542 }
11543
11544 ARegion *region = menu->region;
11545 uiBlock *block = static_cast<uiBlock *>(region->runtime->uiblocks.first);
11546
11547 const bool is_click_style = (block->pie_data.flags & UI_PIE_CLICK_STYLE);
11548
11549 /* if there's an active modal button, don't check events or outside, except for search menu */
11550 uiBut *but_active = ui_region_find_active_but(region);
11551
11552 if (menu->scrolltimer == nullptr) {
11555 menu->scrolltimer->time_duration = 0.0;
11556 }
11557
11558 const double duration = menu->scrolltimer->time_duration;
11559
11560 float event_xy[2] = {float(event->xy[0]), float(event->xy[1])};
11561
11562 ui_window_to_block_fl(region, block, &event_xy[0], &event_xy[1]);
11563
11564 /* Distance from initial point. */
11565 const float dist = ui_block_calc_pie_segment(block, event_xy);
11566
11567 if (but_active && button_modal_state(but_active->active->state)) {
11568 retval = ui_handle_menu_button(C, event, menu);
11569 }
11570 else {
11571 if (event->type == TIMER) {
11572 if (event->customdata == menu->scrolltimer) {
11573 /* deactivate initial direction after a while */
11574 if (duration > 0.01 * U.pie_initial_timeout) {
11576 }
11577
11578 /* handle animation */
11579 if (!(block->pie_data.flags & UI_PIE_ANIMATION_FINISHED)) {
11580 const double final_time = (U.uiflag & USER_REDUCE_MOTION) ?
11581 0.0f :
11582 0.01 * U.pie_animation_timeout;
11583 float fac = duration / final_time;
11584 const float pie_radius = U.pie_menu_radius * UI_SCALE_FAC;
11585
11586 if (fac > 1.0f) {
11587 fac = 1.0f;
11589 }
11590
11591 for (const std::unique_ptr<uiBut> &but : block->buttons) {
11592 if (but->pie_dir != UI_RADIAL_NONE) {
11593 float vec[2];
11594 float center[2];
11595
11596 ui_but_pie_dir(but->pie_dir, vec);
11597
11598 center[0] = (vec[0] > 0.01f) ? 0.5f : ((vec[0] < -0.01f) ? -0.5f : 0.0f);
11599 center[1] = (vec[1] > 0.99f) ? 0.5f : ((vec[1] < -0.99f) ? -0.5f : 0.0f);
11600
11601 center[0] *= BLI_rctf_size_x(&but->rect);
11602 center[1] *= BLI_rctf_size_y(&but->rect);
11603
11604 mul_v2_fl(vec, pie_radius);
11605 add_v2_v2(vec, center);
11606 mul_v2_fl(vec, fac);
11608
11609 BLI_rctf_recenter(&but->rect, vec[0], vec[1]);
11610 }
11611 }
11612 block->pie_data.alphafac = fac;
11613
11614 ED_region_tag_redraw(region);
11615 }
11616 }
11617
11618 /* Check pie velocity here if gesture has ended. */
11619 if (block->pie_data.flags & UI_PIE_GESTURE_END_WAIT) {
11620 float len_sq = 10;
11621
11622 /* use a time threshold to ensure we leave time to the mouse to move */
11623 if (duration - block->pie_data.duration_gesture > 0.02) {
11624 len_sq = len_squared_v2v2(event_xy, block->pie_data.last_pos);
11625 copy_v2_v2(block->pie_data.last_pos, event_xy);
11626 block->pie_data.duration_gesture = duration;
11627 }
11628
11629 if (len_sq < 1.0f) {
11631
11632 if (but) {
11633 return ui_but_pie_menu_apply(C, menu, but, true);
11634 }
11635 }
11636 }
11637 }
11638
11639 if (event->type == block->pie_data.event_type && !is_click_style) {
11640 if (event->val != KM_RELEASE) {
11641 ui_handle_menu_button(C, event, menu);
11642
11645 }
11646 /* why redraw here? It's simple, we are getting many double click events here.
11647 * Those operate like mouse move events almost */
11648 ED_region_tag_redraw(region);
11649 }
11650 else {
11651 if ((duration < 0.01 * U.pie_tap_timeout) && !(block->pie_data.flags & UI_PIE_DRAG_STYLE))
11652 {
11654 }
11655 else {
11657
11658 if (but && (U.pie_menu_confirm > 0) &&
11659 (dist >= UI_SCALE_FAC * (U.pie_menu_threshold + U.pie_menu_confirm)))
11660 {
11661 return ui_but_pie_menu_apply(C, menu, but, true);
11662 }
11663
11664 retval = ui_but_pie_menu_apply(C, menu, but, true);
11665 }
11666 }
11667 }
11668 else {
11669 /* direction from numpad */
11671
11672 switch (event->type) {
11673 case MOUSEMOVE:
11674 if (!is_click_style) {
11675 const float len_sq = len_squared_v2v2(event_xy, block->pie_data.pie_center_init);
11676
11677 /* here we use the initial position explicitly */
11678 if (len_sq > PIE_CLICK_THRESHOLD_SQ) {
11680 }
11681
11682 /* here instead, we use the offset location to account for the initial
11683 * direction timeout */
11684 if ((U.pie_menu_confirm > 0) &&
11685 (dist >= UI_SCALE_FAC * (U.pie_menu_threshold + U.pie_menu_confirm)))
11686 {
11688 copy_v2_v2(block->pie_data.last_pos, event_xy);
11689 block->pie_data.duration_gesture = duration;
11690 }
11691 }
11692
11693 ui_handle_menu_button(C, event, menu);
11694
11695 /* mouse move should always refresh the area for pie menus */
11696 ED_region_tag_redraw(region);
11697 break;
11698
11699 case LEFTMOUSE:
11700 if (is_click_style) {
11701 if (block->pie_data.flags & UI_PIE_INVALID_DIR) {
11703 }
11704 else {
11705 retval = ui_handle_menu_button(C, event, menu);
11706 }
11707 }
11708 break;
11709
11710 case WINDEACTIVATE: {
11711 /* Prevent waiting for the pie key release if it was released outside of focus. */
11712 wmWindow *win = CTX_wm_window(C);
11713 if (win) {
11715 }
11717 break;
11718 }
11719
11720 case EVT_ESCKEY:
11721 case RIGHTMOUSE:
11723 break;
11724
11725 case EVT_AKEY:
11726 case EVT_BKEY:
11727 case EVT_CKEY:
11728 case EVT_DKEY:
11729 case EVT_EKEY:
11730 case EVT_FKEY:
11731 case EVT_GKEY:
11732 case EVT_HKEY:
11733 case EVT_IKEY:
11734 case EVT_JKEY:
11735 case EVT_KKEY:
11736 case EVT_LKEY:
11737 case EVT_MKEY:
11738 case EVT_NKEY:
11739 case EVT_OKEY:
11740 case EVT_PKEY:
11741 case EVT_QKEY:
11742 case EVT_RKEY:
11743 case EVT_SKEY:
11744 case EVT_TKEY:
11745 case EVT_UKEY:
11746 case EVT_VKEY:
11747 case EVT_WKEY:
11748 case EVT_XKEY:
11749 case EVT_YKEY:
11750 case EVT_ZKEY: {
11751 if (ELEM(event->val, KM_PRESS, KM_DBL_CLICK) && ((event->modifier & ~KM_ALT) == 0)) {
11752 for (const std::unique_ptr<uiBut> &but : block->buttons) {
11753 if (but->menu_key == event->type) {
11754 ui_but_pie_button_activate(C, but.get(), menu);
11755 }
11756 }
11757 }
11758 break;
11759 }
11760
11761#define CASE_NUM_TO_DIR(n, d) \
11762 case (EVT_ZEROKEY + n): \
11763 case (EVT_PAD0 + n): { \
11764 if (num_dir == UI_RADIAL_NONE) { \
11765 num_dir = d; \
11766 } \
11767 } \
11768 (void)0
11769
11785 {
11786 uiBut *but = ui_block_pie_dir_activate(block, event, num_dir);
11787 retval = ui_but_pie_button_activate(C, but, menu);
11788 break;
11789 }
11790#undef CASE_NUM_TO_DIR
11791 default:
11792 retval = ui_handle_menu_button(C, event, menu);
11793 break;
11794 }
11795 }
11796 }
11797
11798 return retval;
11799}
11800
11802 const wmEvent *event,
11803 uiPopupBlockHandle *menu,
11804 int level,
11805 const bool is_parent_inside,
11806 const bool is_parent_menu,
11807 const bool is_floating)
11808{
11809 int retval = WM_UI_HANDLER_CONTINUE;
11810 bool do_towards_reinit = false;
11811
11812 /* check if we have a submenu, and handle events for it first */
11814 uiHandleButtonData *data = (but) ? but->active : nullptr;
11815 uiPopupBlockHandle *submenu = (data) ? data->menu : nullptr;
11816
11817 if (submenu) {
11818 uiBlock *block = static_cast<uiBlock *>(menu->region->runtime->uiblocks.first);
11819 const bool is_menu = ui_block_is_menu(block);
11820 bool inside = false;
11821 /* root pie menus accept the key that spawned
11822 * them as double click to improve responsiveness */
11823 const bool do_recursion = (!(block->flag & UI_BLOCK_PIE_MENU) ||
11824 event->type != block->pie_data.event_type);
11825
11826 if (do_recursion) {
11827 if (is_parent_inside == false) {
11828 int mx = event->xy[0];
11829 int my = event->xy[1];
11830 ui_window_to_block(menu->region, block, &mx, &my);
11831 inside = BLI_rctf_isect_pt(&block->rect, mx, my);
11832 }
11833
11835 C, event, submenu, level + 1, is_parent_inside || inside, is_menu, false);
11836 }
11837 }
11838 else if (!but && event->val == KM_PRESS && event->type == LEFTMOUSE) {
11839 LISTBASE_FOREACH (uiBlock *, block, &menu->region->runtime->uiblocks) {
11840 if (block->panel) {
11841 int mx = event->xy[0];
11842 int my = event->xy[1];
11843 ui_window_to_block(menu->region, block, &mx, &my);
11844 if (!IN_RANGE(float(mx), block->rect.xmin, block->rect.xmax)) {
11845 break;
11846 }
11847 LayoutPanelHeader *header = ui_layout_panel_header_under_mouse(*block->panel, my);
11848 if (header) {
11851 ARegion *prev_region_popup = CTX_wm_region_popup(C);
11852 /* Set the current context popup region so the handler context can access to it. */
11855 /* Restore previous popup region. */
11856 CTX_wm_region_popup_set(C, prev_region_popup);
11857 retval = WM_UI_HANDLER_BREAK;
11858 }
11859 }
11860 }
11861 }
11862
11863 /* now handle events for our own menu */
11864
11865 if (retval == WM_UI_HANDLER_CONTINUE) {
11866 retval = ui_handle_region_semi_modal_buttons(C, event, menu->region);
11867 }
11868
11869 if (retval == WM_UI_HANDLER_CONTINUE || event->type == TIMER) {
11870 const bool do_but_search = (but && (but->type == ButType::SearchMenu));
11871 if (submenu && submenu->menuretval) {
11872 const bool do_ret_out_parent = (submenu->menuretval & UI_RETURN_OUT_PARENT) != 0;
11873 retval = ui_handle_menu_return_submenu(C, event, menu);
11874 submenu = nullptr; /* hint not to use this, it may be freed by call above */
11875 (void)submenu;
11876 /* we may want to quit the submenu and handle the even in this menu,
11877 * if its important to use it, check 'data->menu' first */
11878 if (((retval == WM_UI_HANDLER_BREAK) && do_ret_out_parent) == false) {
11879 /* skip applying the event */
11880 return retval;
11881 }
11882 }
11883
11884 if (do_but_search) {
11885 uiBlock *block = static_cast<uiBlock *>(menu->region->runtime->uiblocks.first);
11886
11887 retval = ui_handle_menu_button(C, event, menu);
11888
11890 /* when there is a active search button and we close it,
11891 * we need to reinit the mouse coords #35346. */
11892 if (ui_region_find_active_but(menu->region) != but) {
11893 do_towards_reinit = true;
11894 }
11895 }
11896 }
11897 else {
11898 uiBlock *block = static_cast<uiBlock *>(menu->region->runtime->uiblocks.first);
11899
11900 if (block->flag & UI_BLOCK_PIE_MENU) {
11901 retval = ui_pie_handler(C, event, menu);
11902 }
11903 else if (event->type == LEFTMOUSE || event->val != KM_DBL_CLICK) {
11904 bool handled = false;
11905
11906 if (uiBut *listbox = ui_list_find_mouse_over(menu->region, event)) {
11907 const int retval_test = ui_handle_list_event(C, event, menu->region, listbox);
11908 if (retval_test != WM_UI_HANDLER_CONTINUE) {
11909 retval = retval_test;
11910 handled = true;
11911 }
11912 }
11913
11914 if (handled == false) {
11915 retval = ui_handle_menu_event(
11916 C, event, menu, level, is_parent_inside, is_parent_menu, is_floating);
11917 }
11918 }
11919 }
11920 }
11921
11922 if (!menu->retvalue) {
11924 }
11925 /* Handle mouse clicks on overlapping view item button. */
11926 ui_handle_view_item_event(C, event, but, menu->region);
11927
11928 if (do_towards_reinit) {
11929 ui_mouse_motion_towards_reinit(menu, event->xy);
11930 }
11931
11932 return retval;
11933}
11934
11935void UI_popup_menu_retval_set(const uiBlock *block, const int retval, const bool enable)
11936{
11937 uiPopupBlockHandle *menu = block->handle;
11938 if (menu) {
11939 menu->menuretval = enable ? (menu->menuretval | retval) : (menu->menuretval & retval);
11940 }
11941}
11942
11944
11945/* -------------------------------------------------------------------- */
11948
11949static int ui_region_handler(bContext *C, const wmEvent *event, void * /*userdata*/)
11950{
11951 /* here we handle buttons at the region level, non-modal */
11952 ARegion *region = CTX_wm_region(C);
11953 int retval = WM_UI_HANDLER_CONTINUE;
11954
11955 if (region == nullptr || BLI_listbase_is_empty(&region->runtime->uiblocks)) {
11956 return retval;
11957 }
11958
11959 /* either handle events for already activated button or try to activate */
11960 uiBut *but = ui_region_find_active_but(region);
11961 uiBut *listbox = ui_list_find_mouse_over(region, event);
11962
11963 retval = ui_handler_panel_region(C, event, region, listbox ? listbox : but);
11964
11965 if (retval == WM_UI_HANDLER_CONTINUE && listbox) {
11966 retval = ui_handle_list_event(C, event, region, listbox);
11967
11968 /* interactions with the listbox should disable tips */
11969 if (retval == WM_UI_HANDLER_BREAK) {
11970 if (but) {
11972 }
11973 }
11974 }
11975
11976 if (retval == WM_UI_HANDLER_CONTINUE) {
11977 retval = ui_handle_region_semi_modal_buttons(C, event, region);
11978 }
11979
11980 if (retval == WM_UI_HANDLER_CONTINUE) {
11981 if (but) {
11982 retval = ui_handle_button_event(C, event, but);
11983 }
11984 else {
11985 retval = ui_handle_button_over(C, event, region);
11986 }
11987 }
11988
11989 /* Re-enable tool-tips. */
11990 if (event->type == MOUSEMOVE &&
11991 (event->xy[0] != event->prev_xy[0] || event->xy[1] != event->prev_xy[1]))
11992 {
11993 ui_blocks_set_tooltips(region, true);
11994 }
11995
11996 /* Always do this, to reliably update view and UI-list item highlighting, even if
11997 * the mouse hovers a button nested in the item (it's an overlapping layout). */
11998 ui_handle_viewlist_items_hover(event, region);
11999 if (retval == WM_UI_HANDLER_CONTINUE) {
12000 retval = ui_handle_view_item_event(C, event, but, region);
12001 }
12002
12003 /* delayed apply callbacks */
12005
12006 return retval;
12007}
12008
12009static void ui_region_handler_remove(bContext *C, void * /*userdata*/)
12010{
12011 ARegion *region = CTX_wm_region(C);
12012 if (region == nullptr) {
12013 return;
12014 }
12015
12016 UI_blocklist_free(C, region);
12017 bScreen *screen = CTX_wm_screen(C);
12018 if (screen == nullptr) {
12019 return;
12020 }
12021
12022 /* delayed apply callbacks, but not for screen level regions, those
12023 * we rather do at the very end after closing them all, which will
12024 * be done in ui_region_handler/window */
12025 if (BLI_findindex(&screen->regionbase, region) == -1) {
12027 }
12028}
12029
12031{
12032 /* If there's a fully modal button, it has priority. */
12033 if (const uiBut *active_but = ui_region_find_active_but(region)) {
12034 BLI_assert(active_but->semi_modal_state == nullptr);
12035 if (button_modal_state(active_but->active->state)) {
12037 }
12038 }
12039
12040 int retval = WM_UI_HANDLER_CONTINUE;
12041
12042 foreach_semi_modal_but_as_active(C, region, [&](uiBut *semi_modal_but) {
12043 if (retval == WM_UI_HANDLER_CONTINUE) {
12044 retval = ui_handle_button_event(C, event, semi_modal_but);
12045 }
12046 });
12047
12048 return retval;
12049}
12050
12051/* Return true if we should open another menu while one is already open. */
12052static bool ui_can_activate_other_menu(uiBut *but, uiBut *but_other, const wmEvent *event)
12053{
12054 if (but == but_other || but_other->flag & UI_BUT_DISABLED || but_other->menu_no_hover_open) {
12055 return false;
12056 }
12057
12059 return false;
12060 }
12061
12063 return false;
12064 }
12065
12067 if (!(data->menu->direction & (UI_DIR_DOWN | UI_DIR_UP))) {
12068 return true;
12069 }
12070
12071 if (data->menu && data->menu->region &&
12072 (BLI_rcti_size_x(&data->menu->region->winrct) > (600.0f * UI_SCALE_FAC)))
12073 {
12074 /* If the open menu is super wide then don't switch to any neighbors. */
12075 return false;
12076 }
12077
12078 float safety = 4.0f * UI_SCALE_FAC;
12079 if (!but_other->str.empty()) {
12080 safety += 4.0f * UI_SCALE_FAC;
12081 }
12082
12083 float left, right;
12084 if (but_other->rect.xmin < but->rect.xmin) {
12085 /* Right to Left. */
12086 if (but->rect.xmin - but_other->rect.xmax > (24.0f * UI_SCALE_FAC)) {
12087 /* If they are far enough part just switch. */
12088 return true;
12089 }
12090 right = but->rect.xmax;
12091 left = but_other->rect.xmax;
12092 if (ELEM(but_other->type, ButType::Popover, ButType::Menu)) {
12093 /* Skip the drop-down arrow on the right of it. */
12094 safety += 8.0f * UI_SCALE_FAC;
12095 }
12096 left -= safety;
12097 }
12098 else {
12099 /* Left to Right. */
12100 if (but_other->rect.xmin - but->rect.xmax > (24.0f * UI_SCALE_FAC)) {
12101 /* If they are far enough part just switch. */
12102 return true;
12103 }
12104 left = but->rect.xmin;
12105 right = but_other->rect.xmin;
12106 if (but_other->icon && !but_other->str.empty()) {
12107 /* Skip the icon on the left of it. */
12108 safety += 16.0f * UI_SCALE_FAC;
12109 }
12110 right += safety;
12111 }
12112
12113 return (event->mval[0] < int(left) || event->mval[0] > int(right));
12114}
12115
12116/* handle buttons at the window level, modal, for example while
12117 * number sliding, text editing, or when a menu block is open */
12118static int ui_handler_region_menu(bContext *C, const wmEvent *event, void * /*userdata*/)
12119{
12120 ARegion *region_popup = CTX_wm_region_popup(C);
12121 ARegion *region = region_popup ? region_popup : CTX_wm_region(C);
12122 int retval = WM_UI_HANDLER_CONTINUE;
12123
12124 uiBut *but = ui_region_find_active_but(region);
12125
12126 if (but) {
12127 bScreen *screen = CTX_wm_screen(C);
12128 uiBut *but_other;
12129
12130 /* handle activated button events */
12132
12133 if ((data->state == BUTTON_STATE_MENU_OPEN) &&
12134 /* Make sure this popup isn't dragging a button.
12135 * can happen with popovers (see #67882). */
12136 (ui_region_find_active_but(data->menu->region) == nullptr) &&
12137 /* make sure mouse isn't inside another menu (see #43247) */
12138 (ui_screen_region_find_mouse_over(screen, event) == nullptr) &&
12139 (but_other = UI_but_find_mouse_over(region, event)) &&
12140 ui_can_activate_other_menu(but, but_other, event) &&
12141 /* Hover-opening menu's doesn't work well for buttons over one another
12142 * along the same axis the menu is opening on (see #71719). */
12143 (((data->menu->direction & (UI_DIR_LEFT | UI_DIR_RIGHT)) &&
12144 BLI_rctf_isect_rect_x(&but->rect, &but_other->rect, nullptr)) ||
12145 ((data->menu->direction & (UI_DIR_DOWN | UI_DIR_UP)) &&
12146 BLI_rctf_isect_rect_y(&but->rect, &but_other->rect, nullptr))))
12147 {
12148 /* if mouse moves to a different root-level menu button,
12149 * open it to replace the current menu */
12152 retval = WM_UI_HANDLER_BREAK;
12153 }
12154 else if (data->state == BUTTON_STATE_MENU_OPEN) {
12155 /* handle events for menus and their buttons recursively,
12156 * this will handle events from the top to the bottom menu */
12157 if (data->menu) {
12158 retval = ui_handle_menus_recursive(C, event, data->menu, 0, false, false, false);
12159 }
12160
12161 /* handle events for the activated button */
12162 if ((data->menu && (retval == WM_UI_HANDLER_CONTINUE)) || (event->type == TIMER)) {
12163 if (data->menu && data->menu->menuretval) {
12165 retval = WM_UI_HANDLER_BREAK;
12166 }
12167 else {
12168 retval = ui_handle_button_event(C, event, but);
12169 }
12170 }
12171 }
12172 else {
12173 /* handle events for the activated button */
12174 retval = ui_handle_button_event(C, event, but);
12175 }
12176 }
12177
12178 /* Re-enable tool-tips. */
12179 if (event->type == MOUSEMOVE &&
12180 (event->xy[0] != event->prev_xy[0] || event->xy[1] != event->prev_xy[1]))
12181 {
12182 ui_blocks_set_tooltips(region, true);
12183 }
12184
12185 if (but && but->active && but->active->menu) {
12186 /* Set correct context popup-region. The handling button above breaks if we set the region
12187 * first, so only set it for executing the #uiAfterFunc. */
12189 }
12190
12191 /* delayed apply callbacks */
12193
12194 /* Reset to previous context region. */
12195 CTX_wm_region_popup_set(C, region_popup);
12196
12197 /* Don't handle double-click events,
12198 * these will be converted into regular clicks which we handle. */
12199 if (retval == WM_UI_HANDLER_CONTINUE) {
12200 if (event->val == KM_DBL_CLICK) {
12202 }
12203 }
12204
12205 /* we block all events, this is modal interaction */
12206 return WM_UI_HANDLER_BREAK;
12207}
12208
12209/* two types of popups, one with operator + enum, other with regular callbacks */
12210static int ui_popup_handler(bContext *C, const wmEvent *event, void *userdata)
12211{
12212 uiPopupBlockHandle *menu = static_cast<uiPopupBlockHandle *>(userdata);
12213 /* we block all events, this is modal interaction,
12214 * except for drop events which is described below */
12215 int retval = WM_UI_HANDLER_BREAK;
12216 bool reset_pie = false;
12217
12218 ARegion *region_popup = CTX_wm_region_popup(C);
12220
12221 if (event->type == EVT_DROP || event->val == KM_DBL_CLICK) {
12222 /* EVT_DROP:
12223 * If we're handling drop event we'll want it to be handled by popup callee as well,
12224 * so it'll be possible to perform such operations as opening .blend files by dropping
12225 * them into blender, even if there's opened popup like splash screen (sergey).
12226 * KM_DBL_CLICK:
12227 * Continue in case of double click so wm_handlers_do calls handler again with KM_PRESS
12228 * event. This is needed to ensure correct button handling for fast clicking (#47532).
12229 */
12230
12231 retval = WM_UI_HANDLER_CONTINUE;
12232 }
12233
12234 ui_handle_menus_recursive(C, event, menu, 0, false, false, true);
12235
12236 /* free if done, does not free handle itself */
12237 if (menu->menuretval) {
12238 wmWindow *win = CTX_wm_window(C);
12239 /* copy values, we have to free first (closes region) */
12240 const uiPopupBlockHandle temp = *menu;
12241 uiBlock *block = static_cast<uiBlock *>(menu->region->runtime->uiblocks.first);
12242
12243 /* set last pie event to allow chained pie spawning */
12244 if (block->flag & UI_BLOCK_PIE_MENU) {
12246 reset_pie = true;
12247 }
12248
12249 ui_popup_block_free(C, menu);
12251 CTX_wm_region_popup_set(C, nullptr);
12252
12253#ifdef USE_DRAG_TOGGLE
12254 {
12256 &win->modalhandlers,
12259 }
12260#endif
12261
12262 if ((temp.menuretval & UI_RETURN_OK) || (temp.menuretval & UI_RETURN_POPUP_OK)) {
12263 if (temp.popup_func) {
12264 temp.popup_func(C, temp.popup_arg, temp.retvalue);
12265 }
12266 }
12267 else if (temp.cancel_func) {
12268 temp.cancel_func(C, temp.popup_arg);
12269 }
12270
12272 }
12273 else {
12274 /* Re-enable tool-tips */
12275 if (event->type == MOUSEMOVE &&
12276 (event->xy[0] != event->prev_xy[0] || event->xy[1] != event->prev_xy[1]))
12277 {
12278 ui_blocks_set_tooltips(menu->region, true);
12279 }
12280 }
12281
12282 /* delayed apply callbacks */
12284
12285 if (reset_pie) {
12286 /* Reacquire window in case pie invalidates it somehow. */
12287 wmWindow *win = CTX_wm_window(C);
12288
12289 if (win) {
12291 }
12292 }
12293
12294 CTX_wm_region_set(C, region_popup);
12295
12296 return retval;
12297}
12298
12299static void ui_popup_handler_remove(bContext *C, void *userdata)
12300{
12301 uiPopupBlockHandle *menu = static_cast<uiPopupBlockHandle *>(userdata);
12302
12303 /* More correct would be to expect UI_RETURN_CANCEL here, but not wanting to
12304 * cancel when removing handlers because of file exit is a rare exception.
12305 * So instead of setting cancel flag for all menus before removing handlers,
12306 * just explicitly flag menu with UI_RETURN_OK to avoid canceling it. */
12307 if ((menu->menuretval & UI_RETURN_OK) == 0 && menu->cancel_func) {
12308 menu->cancel_func(C, menu->popup_arg);
12309 }
12310
12311 /* free menu block if window is closed for some reason */
12312 ui_popup_block_free(C, menu);
12313
12314 /* delayed apply callbacks */
12316}
12317
12319{
12321 handlers, ui_region_handler, ui_region_handler_remove, nullptr, false);
12323 handlers,
12326 nullptr,
12328}
12329
12331 ListBase *handlers,
12332 uiPopupBlockHandle *popup,
12333 const char flag)
12334{
12337}
12338
12340{
12341 LISTBASE_FOREACH (wmEventHandler *, handler_base, handlers) {
12342 if (handler_base->type == WM_HANDLER_TYPE_UI) {
12343 wmEventHandler_UI *handler = (wmEventHandler_UI *)handler_base;
12344
12345 if (handler->handle_fn == ui_popup_handler &&
12346 handler->remove_fn == ui_popup_handler_remove && handler->user_data == popup)
12347 {
12348 /* tag refresh parent popup */
12349 wmEventHandler_UI *handler_next = (wmEventHandler_UI *)handler->head.next;
12350 if (handler_next && handler_next->head.type == WM_HANDLER_TYPE_UI &&
12351 handler_next->handle_fn == ui_popup_handler &&
12352 handler_next->remove_fn == ui_popup_handler_remove)
12353 {
12354 uiPopupBlockHandle *parent_popup = static_cast<uiPopupBlockHandle *>(
12355 handler_next->user_data);
12356 ED_region_tag_refresh_ui(parent_popup->region);
12357 }
12358 break;
12359 }
12360 }
12361 }
12362
12364}
12365
12370
12372 ARegion *region,
12373 const void *rna_poin_data,
12374 const char *rna_prop_id)
12375{
12376 uiBlock *block_text = nullptr;
12377 uiBut *but_text = nullptr;
12378
12379 LISTBASE_FOREACH (uiBlock *, block, &region->runtime->uiblocks) {
12380 for (const std::unique_ptr<uiBut> &but : block->buttons) {
12381 if (but->type == ButType::Text) {
12382 if (but->rnaprop && but->rnapoin.data == rna_poin_data) {
12383 if (STREQ(RNA_property_identifier(but->rnaprop), rna_prop_id)) {
12384 block_text = block;
12385 but_text = but.get();
12386 break;
12387 }
12388 }
12389 }
12390 }
12391 if (but_text) {
12392 break;
12393 }
12394 }
12395
12396 if (but_text) {
12397 ARegion *region_ctx = CTX_wm_region(C);
12398
12399 /* Temporary context override for activating the button. */
12400 CTX_wm_region_set(const_cast<bContext *>(C), region);
12401 UI_but_active_only(C, region, block_text, but_text);
12402 CTX_wm_region_set(const_cast<bContext *>(C), region_ctx);
12403 return true;
12404 }
12405 return false;
12406}
12407
12409{
12410 ARegion *region = CTX_wm_region(C);
12411 uiBlock *block_text = nullptr;
12412 uiBut *but_text = nullptr;
12413
12414 LISTBASE_FOREACH (uiBlock *, block, &region->runtime->uiblocks) {
12415 for (const std::unique_ptr<uiBut> &but : block->buttons) {
12416 if (but.get() == actbut && but->type == ButType::Text) {
12417 block_text = block;
12418 but_text = but.get();
12419 break;
12420 }
12421 }
12422
12423 if (but_text) {
12424 break;
12425 }
12426 }
12427
12428 if (but_text) {
12429 UI_but_active_only(C, region, block_text, but_text);
12430 return true;
12431 }
12432 return false;
12433}
12434
12436
12437/* -------------------------------------------------------------------- */
12440
12442{
12443 LISTBASE_FOREACH (uiBlock *, block, &region->runtime->uiblocks) {
12444 for (const std::unique_ptr<uiBut> &but : block->buttons) {
12445 if (but->active == nullptr) {
12446 continue;
12447 }
12448 ui_but_active_free(C, but.get());
12449 }
12450 }
12451}
12452
12454{
12455 wmWindow *win = CTX_wm_window(C);
12456
12457 ED_screen_areas_iter (win, screen, area) {
12458 LISTBASE_FOREACH (ARegion *, region, &area->regionbase) {
12459 uiBut *but = ui_region_find_active_but(region);
12460 if (but) {
12462
12463 if (data->menu == nullptr && data->searchbox == nullptr) {
12464 if (data->state == BUTTON_STATE_HIGHLIGHT) {
12465 ui_but_active_free(C, but);
12466 }
12467 }
12468 }
12469 }
12470 }
12471}
12472
12474{
12475 ARegion *region = CTX_wm_region(C);
12476 uiBut *but = ui_region_find_active_but(region);
12477
12478 if (but) {
12480 return but;
12481 }
12482 }
12483
12484 return nullptr;
12485}
12486
12488{
12489 return UI_but_active_drop_name_button(C) != nullptr;
12490}
12491
12493{
12494 ARegion *region = CTX_wm_region(C);
12495
12496 if (region) {
12497 uiBut *but = ui_region_find_active_but(region);
12498
12499 if (but && but->type == ButType::Color) {
12500 return true;
12501 }
12502 }
12503
12504 return false;
12505}
12506
12508
12509/* -------------------------------------------------------------------- */
12512
12514{
12515 block->custom_interaction_callbacks = *callbacks;
12516}
12517
12519 uiBlock *block,
12520 const bool is_click)
12521{
12524
12525 int unique_retval_ids_len = 0;
12526 for (const std::unique_ptr<uiBut> &but : block->buttons) {
12527 if (but->active || (but->flag & UI_BUT_DRAG_MULTI)) {
12528 unique_retval_ids_len++;
12529 }
12530 }
12531
12532 int *unique_retval_ids = MEM_malloc_arrayN<int>(unique_retval_ids_len, __func__);
12533 unique_retval_ids_len = 0;
12534 for (const std::unique_ptr<uiBut> &but : block->buttons) {
12535 if (but->active || (but->flag & UI_BUT_DRAG_MULTI)) {
12536 unique_retval_ids[unique_retval_ids_len++] = but->retval;
12537 }
12538 }
12539
12540 if (unique_retval_ids_len > 1) {
12541 qsort(unique_retval_ids, unique_retval_ids_len, sizeof(int), BLI_sortutil_cmp_int);
12542 unique_retval_ids_len = BLI_array_deduplicate_ordered(unique_retval_ids,
12543 unique_retval_ids_len);
12544 unique_retval_ids = static_cast<int *>(
12545 MEM_reallocN(unique_retval_ids, sizeof(*unique_retval_ids) * unique_retval_ids_len));
12546 }
12547
12548 interaction->params.is_click = is_click;
12549 interaction->params.unique_retval_ids = unique_retval_ids;
12550 interaction->params.unique_retval_ids_len = unique_retval_ids_len;
12551
12552 interaction->user_data = block->custom_interaction_callbacks.begin_fn(
12553 C, &interaction->params, block->custom_interaction_callbacks.arg1);
12554 return interaction;
12555}
12556
12559 uiBlockInteraction_Handle *interaction)
12560{
12561 BLI_assert(callbacks->end_fn != nullptr);
12562 callbacks->end_fn(C, &interaction->params, callbacks->arg1, interaction->user_data);
12563 MEM_freeN(interaction->params.unique_retval_ids);
12564 MEM_freeN(interaction);
12565}
12566
12569 uiBlockInteraction_Handle *interaction)
12570{
12571 BLI_assert(callbacks->update_fn != nullptr);
12572 callbacks->update_fn(C, &interaction->params, callbacks->arg1, interaction->user_data);
12573}
12574
12587 uiBlock *block,
12589 const bool is_click)
12590{
12591 if (data->custom_interaction_handle) {
12592 return;
12593 }
12594 if (block->custom_interaction_callbacks.begin_fn == nullptr) {
12595 return;
12596 }
12597
12598 uiBlockInteraction_Handle *interaction = ui_block_interaction_begin(C, block, is_click);
12599 interaction->user_count = 1;
12600 data->custom_interaction_handle = interaction;
12601}
12602
AnimationEvalContext BKE_animsys_eval_context_construct(struct Depsgraph *depsgraph, float eval_time) ATTR_WARN_UNUSED_RESULT
Definition anim_sys.cc:738
#define BKE_UNDO_STR_MAX
void BKE_brush_color_set(Paint *paint, Brush *brush, const float color[3])
Definition brush.cc:1213
void BKE_brush_tag_unsaved_changes(Brush *brush)
Definition brush.cc:764
CBData * BKE_colorband_element_add(ColorBand *coba, float position)
Definition colorband.cc:606
void BKE_colorband_update_sort(ColorBand *coba)
Definition colorband.cc:584
void BKE_curvemapping_free_data(CurveMapping *cumap)
CurveMapPoint * BKE_curvemap_insert(CurveMap *cuma, float x, float y)
void BKE_curvemapping_changed(CurveMapping *cumap, bool rem_doubles)
void BKE_curvemapping_copy_data(CurveMapping *target, const CurveMapping *cumap)
ReportList * CTX_wm_reports(const bContext *C)
bScreen * CTX_wm_screen(const bContext *C)
MovieClip * CTX_data_edit_movieclip(const bContext *C)
ARegion * CTX_wm_region_popup(const bContext *C)
ScrArea * CTX_wm_area(const bContext *C)
wmWindow * CTX_wm_window(const bContext *C)
Depsgraph * CTX_data_depsgraph_pointer(const bContext *C)
Scene * CTX_data_scene(const bContext *C)
Main * CTX_data_main(const bContext *C)
void CTX_store_set(bContext *C, const bContextStore *store)
void CTX_wm_area_set(bContext *C, ScrArea *area)
void CTX_wm_region_set(bContext *C, ARegion *region)
ARegion * CTX_wm_region(const bContext *C)
wmWindowManager * CTX_wm_manager(const bContext *C)
void CTX_wm_region_popup_set(bContext *C, ARegion *region_popup)
ViewLayer * CTX_data_view_layer(const bContext *C)
bool BKE_curveprofile_move_point(struct CurveProfile *profile, struct CurveProfilePoint *point, bool snap, const float delta[2])
void BKE_curveprofile_update(struct CurveProfile *profile, int update_flags)
@ PROF_UPDATE_CLIP
@ PROF_UPDATE_REMOVE_DOUBLES
@ PROF_UPDATE_NONE
struct CurveProfilePoint * BKE_curveprofile_insert(struct CurveProfile *profile, float x, float y)
void BKE_curveprofile_copy_data(struct CurveProfile *target, const struct CurveProfile *profile)
bool BKE_curveprofile_move_handle(struct CurveProfilePoint *point, bool handle_1, bool snap, const float delta[2])
void BKE_curveprofile_remove_by_flag(struct CurveProfile *profile, short flag)
int BKE_curveprofile_table_size(const struct CurveProfile *profile)
void BKE_curveprofile_free_data(struct CurveProfile *profile)
float BKE_movieclip_remap_scene_to_clip_frame(const struct MovieClip *clip, float framenr)
void BKE_paint_invalidate_cursor_overlay(Scene *scene, ViewLayer *view_layer, CurveMapping *curve)
Definition paint.cc:279
void BKE_palette_color_remove(Palette *palette, PaletteColor *color)
Definition paint.cc:1394
Paint * BKE_paint_get_active_from_context(const bContext *C)
Definition paint.cc:476
Brush * BKE_paint_brush(Paint *paint)
Definition paint.cc:645
PaintMode BKE_paintmode_get_active_from_context(const bContext *C)
Definition paint.cc:505
@ RPT_INFO
Definition BKE_report.hh:35
@ RPT_ERROR
Definition BKE_report.hh:39
@ RPT_WARNING
Definition BKE_report.hh:38
@ RPT_PROPERTY
Definition BKE_report.hh:37
void BKE_report(ReportList *reports, eReportType type, const char *message)
Definition report.cc:153
float BKE_scene_frame_get(const Scene *scene)
Definition scene.cc:2384
struct MovieTrackingMarker * BKE_tracking_marker_ensure(struct MovieTrackingTrack *track, int framenr)
Definition tracking.cc:1402
@ B_UNIT_AREA
Definition BKE_unit.hh:138
@ B_UNIT_VOLUME
Definition BKE_unit.hh:139
@ B_UNIT_LENGTH
Definition BKE_unit.hh:137
bool BKE_unit_is_valid(int system, int type)
Definition unit.cc:2514
double BKE_unit_base_scalar(int system, int type)
Definition unit.cc:2504
float BLF_width(int fontid, const char *str, size_t str_len, ResultBLF *r_info=nullptr) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(2)
Definition blf.cc:802
size_t BLF_str_offset_from_cursor_position(int fontid, const char *str, size_t str_len, int location_x) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(2)
Definition blf.cc:680
Generic array manipulation API.
#define BLI_array_deduplicate_ordered(arr, arr_len)
#define BLI_assert(a)
Definition BLI_assert.h:46
#define BLI_assert_msg(a, msg)
Definition BLI_assert.h:53
#define ATTR_FALLTHROUGH
int BLI_findindex(const ListBase *listbase, const void *vlink) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition listbase.cc:586
#define LISTBASE_FOREACH(type, var, list)
BLI_INLINE void BLI_listbase_clear(ListBase *lb)
BLI_INLINE bool BLI_listbase_is_empty(const ListBase *lb)
#define LISTBASE_FOREACH_MUTABLE(type, var, list)
#define LISTBASE_FOREACH_BACKWARD(type, var, list)
void BLI_addtail(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:111
void BLI_remlink(ListBase *listbase, void *vlink) ATTR_NONNULL(1)
Definition listbase.cc:131
MINLINE int round_fl_to_int_clamp(float a)
MINLINE float max_fff(float a, float b, float c)
MINLINE int round_fl_to_int(float a)
MINLINE float max_ff(float a, float b)
MINLINE int min_ii(int a, int b)
MINLINE float pow2f(float x)
MINLINE float clamp_f(float value, float min, float max)
MINLINE float min_ff(float a, float b)
MINLINE int max_ii(int a, int b)
MINLINE float cube_f(float a)
MINLINE float square_f(float a)
MINLINE float sqrt3f(float f)
void hsv_to_rgb_v(const float hsv[3], float r_rgb[3])
Definition math_color.cc:57
void rgb_to_hsv_v(const float rgb[3], float r_hsv[3])
void rgb_to_hsv_compat_v(const float rgb[3], float r_hsv[3])
void linearrgb_to_srgb_v3_v3(float srgb[3], const float linear[3])
void hsl_to_rgb_v(const float hsl[3], float r_rgb[3])
Definition math_color.cc:62
void hsv_clamp_v(float hsv[3], float v_max)
void srgb_to_linearrgb_v3_v3(float linear[3], const float srgb[3])
void rgb_to_hsl_v(const float rgb[3], float r_hsl[3])
void rgb_to_hsl_compat_v(const float rgb[3], float r_hsl[3])
#define M_PI
int isect_point_tri_v2(const float pt[2], const float v1[2], const float v2[2], const float v3[2])
float dist_squared_to_line_segment_v2(const float p[2], const float l1[2], const float l2[2])
Definition math_geom.cc:291
MINLINE void copy_v4_v4(float r[4], const float a[4])
MINLINE float len_squared_v2(const float v[2]) ATTR_WARN_UNUSED_RESULT
MINLINE float len_v2(const float v[2]) ATTR_WARN_UNUSED_RESULT
MINLINE float len_squared_v2v2(const float a[2], const float b[2]) ATTR_WARN_UNUSED_RESULT
MINLINE float normalize_v2_length(float n[2], float unit_length)
MINLINE float len_v2v2(const float v1[2], const float v2[2]) ATTR_WARN_UNUSED_RESULT
MINLINE void mul_v2_fl(float r[2], float f)
MINLINE void copy_v2_v2(float r[2], const float a[2])
MINLINE void mul_v3_fl(float r[3], float f)
MINLINE void copy_v2_v2_int(int r[2], const int a[2])
void dist_ensure_v2_v2fl(float v1[2], const float v2[2], float dist)
MINLINE void copy_v3_v3(float r[3], const float a[3])
MINLINE void add_v2_v2(float r[2], const float a[2])
MINLINE int len_manhattan_v2v2_int(const int a[2], const int b[2]) ATTR_WARN_UNUSED_RESULT
MINLINE bool compare_v3v3(const float v1[3], const float v2[3], float limit) ATTR_WARN_UNUSED_RESULT
MINLINE float len_manhattan_v2v2(const float a[2], const float b[2]) ATTR_WARN_UNUSED_RESULT
MINLINE void sub_v2_v2v2(float r[2], const float a[2], const float b[2])
MINLINE bool is_zero_v3(const float v[3]) ATTR_WARN_UNUSED_RESULT
MINLINE float dot_v2v2(const float a[2], const float b[2]) ATTR_WARN_UNUSED_RESULT
MINLINE void zero_v3(float r[3])
MINLINE float normalize_v2_v2(float r[2], const float a[2])
MINLINE float normalize_v3_length(float n[3], float unit_length)
MINLINE float normalize_v3(float n[3])
MINLINE void copy_v2_fl(float r[2], float f)
ATTR_WARN_UNUSED_RESULT const size_t num
int BLI_path_sequence_decode(const char *path, char *head, size_t head_maxncpy, char *tail, size_t tail_maxncpy, unsigned short *r_digits_len)
Definition path_utils.cc:58
void BLI_path_sequence_encode(char *path, size_t path_maxncpy, const char *head, const char *tail, unsigned short numlen, int pic)
bool BLI_rctf_clamp_pt_v(const struct rctf *rect, float xy[2])
BLI_INLINE float BLI_rctf_cent_y(const struct rctf *rct)
Definition BLI_rect.h:189
void BLI_rctf_transform_pt_v(const rctf *dst, const rctf *src, float xy_dst[2], const float xy_src[2])
Definition rct.cc:526
BLI_INLINE float BLI_rctf_cent_x(const struct rctf *rct)
Definition BLI_rect.h:185
bool BLI_rctf_isect_rect_y(const struct rctf *src1, const struct rctf *src2, float range_y[2])
bool BLI_rctf_isect_segment(const struct rctf *rect, const float s1[2], const float s2[2])
bool BLI_rctf_isect_rect_x(const struct rctf *src1, const struct rctf *src2, float range_x[2])
void BLI_rctf_recenter(struct rctf *rect, float x, float y)
Definition rct.cc:602
BLI_INLINE int BLI_rcti_size_x(const struct rcti *rct)
Definition BLI_rect.h:194
bool BLI_rctf_isect_pt(const struct rctf *rect, float x, float y)
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
int BLI_sortutil_cmp_int(const void *a_, const void *b_)
Definition sort_utils.cc:53
char * BLI_strdup(const char *str) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1) ATTR_MALLOC
Definition string.cc:41
char * BLI_strdupn(const char *str, size_t len) ATTR_MALLOC ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
Definition string.cc:30
int BLI_str_rstrip_digits(char *str) ATTR_NONNULL()
Definition string.cc:1012
int BLI_str_rstrip_float_zero(char *str, char pad) ATTR_NONNULL(1)
Definition string.cc:992
char * BLI_strncpy(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
eStrCursorJumpDirection
@ STRCUR_DIR_NEXT
@ STRCUR_DIR_PREV
bool BLI_str_cursor_step_prev_utf8(const char *str, int str_maxlen, int *pos)
void BLI_str_cursor_step_utf8(const char *str, int str_maxlen, int *pos, eStrCursorJumpDirection direction, eStrCursorJumpType jump, bool use_init_step)
void BLI_str_cursor_step_bounds_utf8(const char *str, int str_maxlen, int pos, int *r_start, int *r_end)
@ STRCUR_JUMP_ALL
@ STRCUR_JUMP_NONE
@ STRCUR_JUMP_DELIM
const char const char * BLI_str_find_next_char_utf8(const char *p, const char *str_end) ATTR_WARN_UNUSED_RESULT ATTR_RETURNS_NONNULL ATTR_NONNULL(1
size_t size_t size_t BLI_snprintf_utf8(char *__restrict dst, size_t dst_maxncpy, const char *__restrict format,...) ATTR_NONNULL(1
char * BLI_strncpy_utf8(char *__restrict dst, const char *__restrict src, size_t dst_maxncpy) ATTR_NONNULL(1
size_t size_t BLI_strnlen_utf8(const char *strc, size_t strc_maxlen) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
size_t BLI_strlen_utf8(const char *strc) ATTR_NONNULL(1) ATTR_WARN_UNUSED_RESULT
size_t BLI_strnlen_utf8_ex(const char *strc, size_t strc_maxlen, size_t *r_len_bytes) ATTR_NONNULL(1
size_t BLI_snprintf_utf8_rlen(char *__restrict dst, size_t dst_maxncpy, const char *__restrict format,...) ATTR_NONNULL(1
int BLI_str_utf8_invalid_strip(char *str, size_t str_len) ATTR_NONNULL(1)
#define STRNCPY_UTF8(dst, src)
const char int BLI_str_utf8_size_or_error(const char *p) ATTR_WARN_UNUSED_RESULT ATTR_NONNULL(1)
unsigned short ushort
Platform independent time functions.
double BLI_time_now_seconds(void)
Definition time.cc:113
#define CLAMP(a, b, c)
#define CLAMP_MAX(a, c)
#define ARRAY_SIZE(arr)
#define UNUSED_VARS(...)
#define IN_RANGE(a, b, c)
#define UNUSED_VARS_NDEBUG(...)
#define UNLIKELY(x)
#define ELEM(...)
#define STREQ(a, b)
#define CLAMP_MIN(a, b)
#define IFACE_(msgid)
#define CLOG_WARN(clg_ref,...)
Definition CLG_log.h:189
#define CLOG_INFO_NOCHECK(clg_ref, format,...)
Definition CLG_log.h:204
@ ID_WM
@ BRUSH_USE_GRADIENT
@ CUMA_DO_CLIP
@ CUMA_SELECT
#define CM_TABLE
@ HD_FREE
@ HD_ALIGN
@ USER_UNIT_ROT_RADIANS
#define RGN_ALIGN_ENUM_FROM_MASK(align)
#define UI_LIST_AUTO_SIZE_THRESHOLD
@ UILST_LAYOUT_BIG_PREVIEW_GRID
@ UILST_SCROLL_TO_ACTIVE_ITEM
@ RGN_ALIGN_BOTTOM
@ RGN_ALIGN_LEFT
@ RGN_ALIGN_TOP
@ RGN_ALIGN_RIGHT
@ RGN_TYPE_TOOL_HEADER
@ RGN_TYPE_TEMPORARY
@ RGN_TYPE_ASSET_SHELF_HEADER
@ RGN_TYPE_HUD
@ RGN_TYPE_NAV_BAR
@ RGN_TYPE_FOOTER
@ RGN_TYPE_HEADER
@ UILST_FLT_SORT_REVERSE
#define UI_SCALE_FAC
#define UI_ICON_SIZE
@ MARKER_TRACKED
@ MARKER_DISABLED
@ USER_MENUOPENAUTO
@ USER_REDUCE_MOTION
@ USER_CP_CIRCLE_HSV
@ USER_FLAG_RECENT_SEARCHES_DISABLE
@ USER_TOOLTIPS
@ USER_MENU_CLOSE_LEAVE
void ED_region_tag_refresh_ui(ARegion *region)
Definition area.cc:647
#define ED_screen_areas_iter(win, screen, area_name)
Definition ED_screen.hh:296
void ED_region_tag_redraw_no_rebuild(ARegion *region)
Definition area.cc:638
void ED_workspace_status_text(bContext *C, const char *str)
Definition area.cc:1024
void ED_region_tag_redraw(ARegion *region)
Definition area.cc:618
@ ED_KEYMAP_VIEW2D
Definition ED_screen.hh:761
void ED_undo_push(bContext *C, const char *str)
Definition ed_undo.cc:98
bool ED_undo_is_legacy_compatible_for_property(bContext *C, ID *id, PointerRNA &ptr)
Definition ed_undo.cc:405
GHOST C-API function and type declarations.
void GHOST_SetAutoFocus(bool auto_focus)
static double angle(const Eigen::Vector3d &v1, const Eigen::Vector3d &v2)
Definition IK_Math.h:117
BLI_INLINE void IMB_colormanagement_srgb_to_scene_linear_v3(float scene_linear[3], const float srgb[3])
static bool is_disabled
Read Guarded memory(de)allocation.
#define MEM_SAFE_FREE(v)
#define MEM_reallocN(vmemh, len)
PropertyScaleType
Definition RNA_types.hh:203
@ PROP_SCALE_LOG
Definition RNA_types.hh:210
@ PROP_SCALE_LINEAR
Definition RNA_types.hh:205
@ PROP_SCALE_CUBIC
Definition RNA_types.hh:215
#define RNA_SUBTYPE_UNIT_VALUE(subtype)
Definition RNA_types.hh:220
@ PROP_FLOAT
Definition RNA_types.hh:164
@ PROP_BOOLEAN
Definition RNA_types.hh:162
@ PROP_ENUM
Definition RNA_types.hh:166
@ PROP_INT
Definition RNA_types.hh:163
@ PROP_STRING
Definition RNA_types.hh:165
@ PROP_POINTER
Definition RNA_types.hh:167
@ PROP_UNIT_ROTATION
Definition RNA_types.hh:178
@ PROP_UNIT_LENGTH
Definition RNA_types.hh:174
@ PROP_PROPORTIONAL
Definition RNA_types.hh:349
@ PROP_NO_DEG_UPDATE
Definition RNA_types.hh:439
PropertySubType
Definition RNA_types.hh:232
@ PROP_LAYER_MEMBER
Definition RNA_types.hh:278
@ PROP_PASSWORD
Definition RNA_types.hh:243
@ PROP_COLOR
Definition RNA_types.hh:260
@ PROP_COLOR_GAMMA
Definition RNA_types.hh:272
@ PROP_LAYER
Definition RNA_types.hh:277
#define C
Definition RandGen.cpp:29
bool UI_view_item_can_rename(const blender::ui::AbstractViewItem &item)
void UI_region_views_clear_search_highlight(const ARegion *region)
#define UI_UNIT_Y
#define AUTOCOMPLETE_FULL_MATCH
@ UI_BLOCK_CLIP_EVENTS
@ UI_BLOCK_CLIPBOTTOM
@ UI_BLOCK_POPOVER_ONCE
@ UI_BLOCK_NUMSELECT
@ UI_BLOCK_LOOP
@ UI_BLOCK_POPUP_MEMORY
@ UI_BLOCK_MOVEMOUSE_QUIT
@ UI_BLOCK_PIE_MENU
@ UI_BLOCK_KEEP_OPEN
@ UI_BLOCK_CLIPTOP
@ UI_BLOCK_POPOVER
@ UI_BLOCK_OUT_1
@ UI_BLOCK_POPUP_HOLD
bool UI_view_item_drag_start(bContext &C, blender::ui::AbstractViewItem &item)
bool UI_but_has_quick_tooltip(const uiBut *but)
void UI_blocklist_free(const bContext *C, ARegion *region)
@ UI_BUT_DRAG_LOCK
@ UI_BUT_REDALERT
@ UI_BUT_UNDO
@ UI_BUT_ACTIVE_DEFAULT
@ UI_BUT_DISABLED
@ UI_BUT_DRIVEN
@ UI_BUT_DRAG_MULTI
@ UI_BUT_UPDATE_DELAY
@ UI_BUT_VALUE_CLEAR
@ UI_BUT_TEXTEDIT_UPDATE
@ UI_BUT_LAST_ACTIVE
bool UI_view_item_popup_keep_open(const blender::ui::AbstractViewItem &item)
int UI_but_unit_type_get(const uiBut *but)
void(*)(bContext *C, void *arg, char *origstr) uiButHandleRenameFunc
bool UI_list_item_index_is_filtered_visible(const struct uiList *ui_list, int item_idx)
MenuType * UI_but_menutype_get(const uiBut *but)
bool UI_but_is_utf8(const uiBut *but)
#define AUTOCOMPLETE_NO_MATCH
void UI_popover_once_clear(uiPopover *pup)
@ UI_BUT2_FORCE_SEMI_MODAL_ACTIVE
@ UI_BUT2_ACTIVATE_ON_INIT_NO_SELECT
uiBut * UI_but_find_mouse_over(const ARegion *region, const wmEvent *event) ATTR_WARN_UNUSED_RESULT
#define UI_PRECISION_FLOAT_MAX
#define UI_PRECISION_FLOAT_SCALE
void(*)(void *argN) uiButArgNFree
void UI_but_execute(const bContext *C, ARegion *region, uiBut *but)
void UI_view_item_begin_rename(blender::ui::AbstractViewItem &item)
ARegion * UI_tooltip_create_from_button_or_extra_icon(bContext *C, ARegion *butregion, uiBut *but, uiButExtraOpIcon *extra_icon, bool is_quick_tip)
void(*)(bContext *C, void *arg, int event) uiBlockHandleFunc
const uiStyle * UI_style_get()
void UI_fontstyle_set(const uiFontStyle *fs)
uiBlock *(*)(bContext *C, ARegion *region, void *arg1) uiBlockCreateFunc
PointerRNA * UI_but_operator_ptr_ensure(uiBut *but)
@ UI_RETURN_UPDATE
@ UI_RETURN_OUT_PARENT
@ UI_RETURN_CANCEL
@ UI_RETURN_POPUP_OK
@ UI_RETURN_OK
@ UI_RETURN_OUT
PanelType * UI_but_paneltype_get(const uiBut *but)
void UI_butstore_register(uiButStore *bs_handle, uiBut **but_p)
@ UI_GRAD_L_ALT
@ UI_GRAD_SV
@ UI_GRAD_V_ALT
@ UI_GRAD_S
@ UI_GRAD_HV
@ UI_GRAD_HS
@ UI_GRAD_V
@ UI_GRAD_H
uiButStore * UI_butstore_create(uiBlock *block)
bool UI_context_copy_to_selected_list(bContext *C, PointerRNA *ptr, PropertyRNA *prop, blender::Vector< PointerRNA > *r_lb, bool *r_use_path_from_id, std::optional< std::string > *r_path)
bool UI_but_active_only(const bContext *C, ARegion *region, uiBlock *block, uiBut *but)
#define UI_TOOLTIP_DELAY
void(*)(bContext *C, void *argN, void *arg2) uiButHandleNFunc
void(*)(bContext *C, void *arg1, void *arg2) uiButHandleFunc
bool UI_view_item_supports_drag(const blender::ui::AbstractViewItem &item)
void UI_popup_menu_close_from_but(const uiBut *but, bool is_cancel=false)
void UI_butstore_free(uiBlock *block, uiButStore *bs_handle)
void UI_but_ensure_in_view(const bContext *C, ARegion *region, const uiBut *but)
@ UI_BUT_NO_TEXT_PADDING
@ UI_BUT_HOVER_RIGHT
@ UI_BUT_HOVER_LEFT
bool UI_context_copy_to_selected_check(PointerRNA *ptr, PointerRNA *ptr_link, PropertyRNA *prop, const char *path, bool use_path_from_id, PointerRNA *r_ptr, PropertyRNA **r_prop)
@ UI_DIR_DOWN
@ UI_DIR_RIGHT
@ UI_DIR_LEFT
@ UI_DIR_UP
#define UI_but_is_decorator(but)
#define UI_TOOLTIP_DELAY_QUICK
void(*)(void *arg) uiFreeArgFunc
void(*)(bContext *C, uiLayout *layout, void *arg1) uiMenuCreateFunc
eWM_EventHandlerFlag
Definition WM_api.hh:577
@ WM_HANDLER_BLOCKING
Definition WM_api.hh:579
#define NC_WINDOW
Definition WM_types.hh:375
#define WM_UI_HANDLER_CONTINUE
Definition WM_types.hh:348
wmEventModifierFlag
Definition WM_types.hh:277
@ KM_CTRL
Definition WM_types.hh:279
@ KM_ALT
Definition WM_types.hh:280
@ KM_OSKEY
Definition WM_types.hh:282
@ KM_SHIFT
Definition WM_types.hh:278
eWM_EventFlag
Definition WM_types.hh:671
@ WM_EVENT_SCROLL_INVERT
Definition WM_types.hh:677
@ WM_EVENT_IS_REPEAT
Definition WM_types.hh:684
@ WM_DRAG_FREE_DATA
Definition WM_types.hh:1230
@ KM_PRESS
Definition WM_types.hh:311
@ KM_DBL_CLICK
Definition WM_types.hh:314
@ KM_RELEASE
Definition WM_types.hh:312
@ KM_CLICK
Definition WM_types.hh:313
@ WM_DRAG_COLOR
Definition WM_types.hh:1218
#define NC_MOVIECLIP
Definition WM_types.hh:397
#define NA_EDITED
Definition WM_types.hh:584
#define ND_SPACE_INFO_REPORT
Definition WM_types.hh:520
#define WM_UI_HANDLER_BREAK
Definition WM_types.hh:349
@ WM_CURSOR_WRAP_XY
Definition WM_types.hh:229
#define NC_SPACE
Definition WM_types.hh:392
#define U
BMesh const char void * data
ATTR_WARN_UNUSED_RESULT const BMLoop * l
ATTR_WARN_UNUSED_RESULT const BMVert * v
BPy_StructRNA * depsgraph
void activate(bool forceActivation=false) const
long long int int64_t
static DBVT_INLINE btScalar size(const btDbvtVolume &a)
Definition btDbvt.cpp:52
static btDbvtVolume bounds(btDbvtNode **leaves, int count)
Definition btDbvt.cpp:299
void jump(const btVector3 &v=btVector3(0, 0, 0))
int64_t size() const
T pop_last()
bool is_empty() const
void resize(const int64_t new_size)
void clear()
const T * data() const
Definition BLI_array.hh:312
int64_t size() const
bool is_empty() const
void activate_for_context_menu(bContext &C)
nullptr float
#define logf(x)
#define expf(x)
#define powf(x, y)
#define asinf(x)
#define fmodf(x, y)
#define roundf(x)
#define str(s)
#define GS(x)
#define SIZE_MAX
uint pos
uint col
#define output
#define round
#define abs
#define select(A, B, C)
VecBase< float, D > step(VecOp< float, D >, VecOp< float, D >) RET
float length(VecOp< float, D >) RET
static void copy_array(const Node *node, const SocketType &socket, const Node *other, const SocketType &other_socket)
void ui_but_v4_get(uiBut *but, float vec[4])
bool ui_but_is_unit(const uiBut *but)
void ui_but_range_set_hard(uiBut *but)
void ui_but_extra_operator_icons_free(uiBut *but)
bool ui_but_is_compatible(const uiBut *but_a, const uiBut *but_b)
void ui_window_to_block(const ARegion *region, const uiBlock *block, int *x, int *y)
Definition interface.cc:228
void ui_but_string_get_ex(uiBut *but, char *str, const size_t str_maxncpy, const int float_precision, const bool use_exp_float, bool *r_use_exp_float)
void ui_but_range_set_soft(uiBut *but)
void ui_but_update(uiBut *but)
int ui_but_is_pushed(uiBut *but)
bool ui_but_is_float(const uiBut *but)
PropertyScaleType ui_but_scale_type(const uiBut *but)
void ui_but_override_flag(Main *bmain, uiBut *but)
double ui_but_value_get(uiBut *but)
float ui_block_to_window_scale(const ARegion *region, const uiBlock *block)
Definition interface.cc:180
void ui_but_string_get(uiBut *but, char *str, const size_t str_maxncpy)
void ui_region_to_window(const ARegion *region, int *x, int *y)
Definition interface.cc:261
void ui_window_to_block_fl(const ARegion *region, const uiBlock *block, float *x, float *y)
Definition interface.cc:191
bool ui_but_is_rna_valid(uiBut *but)
bool ui_but_string_set(bContext *C, uiBut *but, const char *str)
bool ui_but_menu_draw_as_popover(const uiBut *but)
void ui_but_value_set(uiBut *but, double value)
char * ui_but_string_get_dynamic(uiBut *but, int *r_str_size)
void ui_block_to_window_rctf(const ARegion *region, const uiBlock *block, rctf *rct_dst, const rctf *rct_src)
Definition interface.cc:170
void ui_but_update_edited(uiBut *but)
void ui_but_v3_set(uiBut *but, const float vec[3])
void ui_but_v3_get(uiBut *but, float vec[3])
int ui_but_string_get_maxncpy(uiBut *but)
bool ui_but_string_eval_number(bContext *C, const uiBut *but, const char *str, double *r_value)
void ui_fontscale(float *points, float aspect)
void ui_block_to_window_fl(const ARegion *region, const uiBlock *block, float *x, float *y)
Definition interface.cc:142
void ui_but_convert_to_unit_alt_name(uiBut *but, char *str, size_t str_maxncpy)
bool ui_but_supports_cycling(const uiBut *but)
bool ui_but_is_bool(const uiBut *but)
void ui_but_anim_decorate_update_from_flag(uiButDecorator *but)
void ui_but_anim_flag(uiBut *but, const AnimationEvalContext *anim_eval_context)
void ui_but_anim_autokey(bContext *C, uiBut *but, Scene *scene, float cfra)
bool ui_but_anim_expression_get(uiBut *but, char *str, size_t str_maxncpy)
bool ui_popup_context_menu_for_button(bContext *C, uiBut *but, const wmEvent *event)
bool ui_but_drag_is_draggable(const uiBut *but)
void ui_but_drag_start(bContext *C, uiBut *but)
void UI_context_update_anim_flag(const bContext *C)
static int ui_do_but_EXIT(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
static void ui_color_picker_to_rgb_HSVCUBE_v(const uiButHSVCube *hsv_but, const float hsv[3], float rgb[3])
static void ui_do_but_extra_operator_icons_mousemove(uiBut *but, uiHandleButtonData *data, const wmEvent *event)
static void ui_but_paste(bContext *C, uiBut *but, uiHandleButtonData *data, const bool paste_array)
static void ui_multibut_states_apply(bContext *C, uiHandleButtonData *data, uiBlock *block)
static void ui_multibut_free(uiHandleButtonData *data, uiBlock *block)
static CurveProfile but_copypaste_profile
static int ui_handle_menu_letter_press_search(uiPopupBlockHandle *menu, const wmEvent *event)
static float ui_numedit_apply_snapf(uiBut *but, float tempf, float softmin, float softmax, const enum eSnapType snap)
bool UI_textbutton_activate_rna(const bContext *C, ARegion *region, const void *rna_poin_data, const char *rna_prop_id)
void UI_context_active_but_prop_handle(bContext *C, const bool handle_undo)
#define BUTTON_FLASH_DELAY
static int ui_handle_menu_event(bContext *C, const wmEvent *event, uiPopupBlockHandle *menu, int level, const bool is_parent_inside, const bool is_parent_menu, const bool is_floating)
static void button_activate_exit(bContext *C, uiBut *but, uiHandleButtonData *data, const bool mousemove, const bool onfree)
static bool ui_numedit_but_CURVE(uiBlock *block, uiBut *but, uiHandleButtonData *data, int evtx, int evty, bool snap, const bool shift)
static void ui_afterfunc_update_preferences_dirty(uiAfterFunc *after)
static bool ui_multibut_states_tag(uiBut *but_active, uiHandleButtonData *data, const wmEvent *event)
static bool ui_but_is_drag_toggle(const uiBut *but)
static bool but_copypaste_curve_alive
static void ui_but_paste_numeric_array(bContext *C, uiBut *but, uiHandleButtonData *data, char *buf_paste)
static bool ui_textedit_copypaste(uiBut *but, uiTextEdit &text_edit, const int mode)
static int ui_do_but_SCROLL(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
static char ui_menu_scroll_test(uiBlock *block, int my)
static int ui_do_but_TEX(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
bool ui_but_is_editing(const uiBut *but)
int ui_but_menu_direction(uiBut *but)
static void ui_but_get_pasted_text_from_clipboard(const bool ensure_utf8, char **r_buf_paste, int *r_buf_len)
bool UI_but_active_drop_name(const bContext *C)
static bool ui_mouse_motion_keynav_test(uiKeyNavLock *keynav, const wmEvent *event)
static bool ui_menu_scroll_step(ARegion *region, uiBlock *block, const int scroll_dir)
#define DRAG_MULTINUM_THRESHOLD_DRAG_X
static int ui_region_handler(bContext *C, const wmEvent *event, void *)
static void ui_numedit_apply(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data)
static bool ui_numedit_but_HSVCIRCLE(uiBut *but, uiHandleButtonData *data, float mx, float my, const enum eSnapType snap, const bool shift)
static void force_activate_view_item_but(bContext *C, ARegion *region, uiButViewItem *but, const bool close_popup=true)
static void ui_but_copy_colorband(uiBut *but)
static bool ui_button_value_default(uiBut *but, double *r_value)
static void ui_but_paste_curvemapping(bContext *C, uiBut *but)
static int ui_handle_button_over(bContext *C, const wmEvent *event, ARegion *region)
static void ui_rgb_to_color_picker_HSVCUBE_compat_v(const uiButHSVCube *hsv_but, const float rgb[3], float hsv[3])
#define MENU_TOWARDS_WIGGLE_ROOM
#define BUTTON_MOUSE_TOWARDS_THRESH
void UI_block_interaction_set(uiBlock *block, uiBlockInteraction_CallbackData *callbacks)
#define UI_MAX_PASSWORD_STR
@ MENU_SCROLL_BOTTOM
@ MENU_SCROLL_TOP
@ MENU_SCROLL_DOWN
@ MENU_SCROLL_UP
static void ui_apply_but_ROW(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data)
static bool ui_numedit_but_HISTOGRAM(uiBut *but, uiHandleButtonData *data, int mx, int my)
static void ui_but_copy_numeric_array(uiBut *but, char *output, int output_maxncpy)
static int ui_handle_menu_return_submenu(bContext *C, const wmEvent *event, uiPopupBlockHandle *menu)
static int ui_do_but_BUT(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
static CurveMapping but_copypaste_curve
static bool ui_mouse_motion_towards_check(uiBlock *block, uiPopupBlockHandle *menu, const int xy[2], const bool use_wiggle_room)
static void ui_but_copy_numeric_value(uiBut *but, char *output, int output_maxncpy)
float ui_block_calc_pie_segment(uiBlock *block, const float event_xy[2])
static bool ui_rna_is_userdef(PointerRNA *ptr, PropertyRNA *prop)
static int ui_do_but_CURVEPROFILE(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
static bool ui_textedit_delete(uiBut *but, uiTextEdit &text_edit, eStrCursorJumpDirection direction, eStrCursorJumpType jump)
static int ui_handle_viewlist_items_hover(const wmEvent *event, ARegion *region)
static void with_but_active_as_semi_modal(bContext *C, ARegion *region, uiBut *but, blender::FunctionRef< void()> fn)
static bool ui_afterfunc_check(const uiBlock *block, const uiBut *but)
static uiBut * ui_but_find_open_event(ARegion *region, const wmEvent *event)
static bool but_copypaste_profile_alive
static void button_activate_init(bContext *C, ARegion *region, uiBut *but, uiButtonActivateType type)
static bool ui_numedit_but_HSVCUBE(uiBut *but, uiHandleButtonData *data, int mx, int my, const enum eSnapType snap, const bool shift)
static void ui_but_copy_operator(bContext *C, uiBut *but, char *output, int output_maxncpy)
uiBlock * UI_region_block_find_mouse_over(const ARegion *region, const int xy[2], bool only_clip)
uiBut * UI_region_active_but_get(const ARegion *region)
static void ui_handle_button_activate(bContext *C, ARegion *region, uiBut *but, uiButtonActivateType type)
static bool ui_multibut_drag_wait(const uiHandleButtonMulti &multi_data)
void ui_but_activate_over(bContext *C, ARegion *region, uiBut *but)
static void ui_multibut_states_create(uiBut *but_active, uiHandleButtonData *data)
@ UI_TEXTEDIT_CUT
@ UI_TEXTEDIT_PASTE
@ UI_TEXTEDIT_COPY
static bool ui_event_is_snap(const wmEvent *event)
static bool ui_do_but_extra_operator_icon(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
static int ui_do_but_COLOR(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
ARegion * UI_region_searchbox_region_get(const ARegion *button_region)
static bool ui_can_activate_other_menu(uiBut *but, uiBut *but_other, const wmEvent *event)
static uiBut * ui_block_pie_dir_activate(uiBlock *block, const wmEvent *event, RadialDirection dir)
static void ui_textedit_begin(bContext *C, uiBut *but, uiHandleButtonData *data)
bool UI_but_is_userdef(const uiBut *but)
static void ui_selectcontext_apply(bContext *C, uiBut *but, uiSelectContextStore *selctx_data, const double value, const double value_orig)
uiBut * UI_region_but_find_rect_over(const ARegion *region, const rcti *rect_px)
static void ui_but_set_float_array(bContext *C, uiBut *but, uiHandleButtonData *data, const float *values, const int values_len)
static int ui_but_pie_menu_apply(bContext *C, uiPopupBlockHandle *menu, uiBut *but, bool force_close)
static void ui_apply_but_TRACKPREVIEW(bContext *C, uiBut *but, uiHandleButtonData *data)
static bool ui_textedit_delete_selection(uiBut *but, uiTextEdit &text_edit)
static int ui_do_but_GRIP(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
static void ui_apply_but_CURVEPROFILE(bContext *C, uiBut *but, uiHandleButtonData *data)
#define DRAG_MULTINUM_THRESHOLD_DRAG_Y
static bool ui_do_but_ANY_drag_toggle(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event, int *r_retval)
#define UI_PROP_SCALE_LOG_SNAP_OFFSET
static int ui_handle_region_semi_modal_buttons(bContext *C, const wmEvent *event, ARegion *region)
static void popup_check(bContext *C, wmOperator *op)
static void ui_apply_but_WAVEFORM(bContext *C, uiBut *but, uiHandleButtonData *data)
static void ui_but_paste_color(bContext *C, uiBut *but, char *buf_paste)
static int ui_do_but_HSVCUBE(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
static bool ui_menu_scroll_to_y(ARegion *region, uiBlock *block, int y)
static void ui_numedit_begin_set_values(uiBut *but, uiHandleButtonData *data)
static bool ui_drag_toggle_but_is_supported(const uiBut *but)
static void ui_palette_set_active(uiButColor *color_but)
static bool ui_but_pie_menu_supported_apply(uiBut *but)
static int ui_do_button(bContext *C, uiBlock *block, uiBut *but, const wmEvent *event)
static eStrCursorJumpType ui_textedit_jump_type_from_event(const wmEvent *event)
static void ui_region_auto_open_clear(ARegion *region)
static void ui_rna_update_preferences_dirty(PointerRNA *ptr, PropertyRNA *prop)
void UI_but_tooltip_timer_remove(bContext *C, uiBut *but)
void UI_popup_menu_retval_set(const uiBlock *block, const int retval, const bool enable)
static int ui_drag_toggle_but_pushed_state(uiBut *but)
static int ui_do_but_CURVE(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
static int ui_do_but_LISTROW(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
static void ui_apply_but_CURVE(bContext *C, uiBut *but, uiHandleButtonData *data)
void ui_but_execute_end(bContext *C, ARegion *, uiBut *but, void *active_back)
static void ui_textedit_string_set(uiBut *but, uiTextEdit &text_edit, const char *str)
static bool button_modal_state(uiHandleButtonState state)
static float ui_mouse_scale_warp_factor(const bool shift)
uiBut * UI_context_active_but_get(const bContext *C)
static void ui_apply_but_IMAGE(bContext *C, uiBut *but, uiHandleButtonData *data)
static ListBase UIAfterFuncs
static bool ui_numedit_but_NUM(uiButNumber *but, uiHandleButtonData *data, int mx, blender::FunctionRef< int()> drag_threshold_fn, const bool is_motion, const enum eSnapType snap, float fac)
static void ui_block_open_begin(bContext *C, uiBut *but, uiHandleButtonData *data)
void ui_but_active_free(const bContext *C, uiBut *but)
static void ui_color_snap_hue(const enum eSnapType snap, float *r_hue)
static void ui_menu_scroll_apply_offset_y(ARegion *region, uiBlock *block, float dy)
void ui_but_active_string_clear_and_exit(bContext *C, uiBut *but)
static uiBut * ui_context_button_active(const ARegion *region, bool(*but_check_cb)(const uiBut *))
static void ui_apply_but_BLOCK(bContext *C, uiBut *but, uiHandleButtonData *data)
static void ui_textedit_prev_but(uiBlock *block, uiBut *actbut, uiHandleButtonData *data)
uiHandleButtonState
@ BUTTON_STATE_HIGHLIGHT
@ BUTTON_STATE_INIT
@ BUTTON_STATE_WAIT_FLASH
@ BUTTON_STATE_NUM_EDITING
@ BUTTON_STATE_TEXT_EDITING
@ BUTTON_STATE_TEXT_SELECTING
@ BUTTON_STATE_WAIT_KEY_EVENT
@ BUTTON_STATE_WAIT_DRAG
@ BUTTON_STATE_WAIT_RELEASE
@ BUTTON_STATE_EXIT
@ BUTTON_STATE_MENU_OPEN
static int ui_do_but_SEARCH_UNLINK(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
static void ui_apply_but_NUM(bContext *C, uiBut *but, uiHandleButtonData *data)
static void ui_but_paste_numeric_value(bContext *C, uiBut *but, uiHandleButtonData *data, char *buf_paste)
static void ui_numedit_end(uiBut *but, uiHandleButtonData *data)
static bool ui_but_copy_menu(uiBut *but, char *output, int output_maxncpy)
static void ui_apply_but_VIEW_ITEM(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data)
static int ui_do_but_HOTKEYEVT(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
static void ui_but_extra_operator_icon_apply(bContext *C, uiBut *but, uiButExtraOpIcon *op_icon)
static void ui_but_paste_normalized_vector(bContext *C, uiBut *but, uiHandleButtonData *data, char *buf_paste)
#define UI_PROP_SCALE_LOG_MIN
static void ui_but_update_preferences_dirty(uiBut *but)
static void float_array_to_string(const float *values, const int values_len, char *output, int output_maxncpy)
void ui_pan_to_scroll(const wmEvent *event, int *type, int *val)
static int ui_do_but_HISTOGRAM(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
static int ui_list_get_increment(const uiList *ui_list, const int type, const int columns)
static int ui_do_but_WAVEFORM(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
static void ui_apply_but_func(bContext *C, uiBut *but)
static void ui_mouse_scale_warp(uiHandleButtonData *data, const float mx, const float my, float *r_mx, float *r_my, const bool shift)
static void ui_apply_but_COLORBAND(bContext *C, uiBut *but, uiHandleButtonData *data)
uiBut * UI_context_active_but_prop_get(const bContext *C, PointerRNA *r_ptr, PropertyRNA **r_prop, int *r_index)
static bool ui_numedit_but_CURVEPROFILE(uiBlock *block, uiBut *but, uiHandleButtonData *data, int evtx, int evty, bool snap, const bool shift)
static bool ui_drag_toggle_set_xy_xy(bContext *C, ARegion *region, const int pushed_state, const int xy_src[2], const int xy_dst[2])
static void ui_apply_but_VEC(bContext *C, uiBut *but, uiHandleButtonData *data)
static void ui_block_interaction_update(bContext *C, uiBlockInteraction_CallbackData *callbacks, uiBlockInteraction_Handle *interaction)
static bool ui_numedit_but_UNITVEC(uiBut *but, uiHandleButtonData *data, int mx, int my, const enum eSnapType snap)
static void ui_apply_but_BUTM(bContext *C, uiBut *but, uiHandleButtonData *data)
static enum eSnapType ui_event_to_snap(const wmEvent *event)
static void ui_handler_region_drag_toggle_remove(bContext *, void *userdata)
static ARegion * ui_but_tooltip_init(bContext *C, ARegion *region, int *pass, double *r_pass_delay, bool *r_exit_on_event)
void ui_but_handle_data_free(uiHandleButtonData **data)
uiBut * ui_but_find_select_in_enum(uiBut *but, int direction)
void ui_but_execute_begin(bContext *, ARegion *region, uiBut *but, void **active_back)
#define MENU_SCROLL_INTERVAL
static void ui_apply_but_BUT(bContext *C, uiBut *but, uiHandleButtonData *data)
#define MENU_TOWARDS_MARGIN
static void ui_selectcontext_end(uiBut *but, uiSelectContextStore *selctx_data)
static int ui_do_but_TRACKPREVIEW(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
bool UI_textbutton_activate_but(const bContext *C, uiBut *actbut)
void UI_context_active_but_clear(bContext *C, wmWindow *win, ARegion *region)
static int ui_handle_menu_button(bContext *C, const wmEvent *event, uiPopupBlockHandle *menu)
static uiBut * ui_but_list_row_text_activate(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event, uiButtonActivateType activate_type)
static void ui_numedit_set_active(uiBut *but)
static ColorBand but_copypaste_coba
static int ui_do_but_HSVCIRCLE(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
static bool ui_but_dragedit_update_mval(uiHandleButtonData *data, int mx, blender::FunctionRef< int()> drag_threshold_fn)
static uiBlockInteraction_Handle * ui_block_interaction_begin(bContext *C, uiBlock *block, const bool is_click)
static bool point_draw_handles(CurveProfilePoint *point)
static void button_activate_state(bContext *C, uiBut *but, uiHandleButtonState state)
static void ui_apply_but_autokey(bContext *C, uiBut *but)
static void ui_mouse_motion_keynav_init(uiKeyNavLock *keynav, const wmEvent *event)
static bool ui_but_drag_init(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
uiButtonActivateType
@ BUTTON_ACTIVATE_OVER
@ BUTTON_ACTIVATE_APPLY
@ BUTTON_ACTIVATE_OPEN
@ BUTTON_ACTIVATE
@ BUTTON_ACTIVATE_TEXT_EDITING
static bool ui_numedit_but_TRACKPREVIEW(bContext *C, uiBut *but, uiHandleButtonData *data, int mx, int my, const bool shift)
uiBut * UI_but_active_drop_name_button(const bContext *C)
uiBut * UI_context_active_but_get_respect_popup(const bContext *C)
static void ui_apply_but_undo(uiBut *but)
static float ui_numedit_apply_snap(int temp, float softmin, float softmax, const enum eSnapType snap)
#define DRAG_MULTINUM_THRESHOLD_VERTICAL
static bool ui_but_copy(bContext *C, uiBut *but, const bool copy_array)
static void ui_textedit_end(bContext *C, uiBut *but, uiHandleButtonData *data)
static void ui_numedit_begin(uiBut *but, uiHandleButtonData *data)
static bool ui_but_copy_popover(uiBut *but, char *output, int output_maxncpy)
void ui_handle_afterfunc_add_operator(wmOperatorType *ot, blender::wm::OpCallContext opcontext)
#define UI_DRAG_MAP_SOFT_RANGE_PIXEL_MAX
static int ui_do_but_VIEW_ITEM(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
void ui_but_clipboard_free()
static int ui_do_but_text_value_cycle(bContext *C, uiBut *but, uiHandleButtonData *data, const int inc_value)
static void ui_textedit_set_cursor_select(uiBut *but, uiHandleButtonData *data, const float x)
void ui_but_activate_event(bContext *C, ARegion *region, uiBut *but)
static int ui_handler_region_drag_toggle(bContext *C, const wmEvent *event, void *userdata)
static int ui_handle_list_event(bContext *C, const wmEvent *event, ARegion *region, uiBut *listbox)
uiBut * UI_region_active_but_prop_get(const ARegion *region, PointerRNA *r_ptr, PropertyRNA **r_prop, int *r_index)
static int ui_do_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
static void ui_handle_button_return_submenu(bContext *C, const wmEvent *event, uiBut *but)
static bool ui_numedit_but_SLI(uiBut *but, uiHandleButtonData *data, int mx, blender::FunctionRef< int()> drag_threshold_fn, const bool is_horizontal, const bool is_motion, const bool snap, const bool shift)
static void ui_textedit_next_but(uiBlock *block, uiBut *actbut, uiHandleButtonData *data)
static void ui_multibut_add(uiHandleButtonData *data, uiBut *but)
void UI_but_tooltip_refresh(bContext *C, uiBut *but)
#define IS_ALLSELECT_EVENT(event)
static bool ui_handle_button_activate_by_type(bContext *C, ARegion *region, uiBut *but)
static bool parse_float_array(char *text, float *values, int values_len_expected)
bool UI_but_active_drop_color(bContext *C)
static int ui_do_but_TAB(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
static void ui_block_open_end(bContext *C, uiBut *but, uiHandleButtonData *data)
static void ui_textedit_move(uiBut *but, uiTextEdit &text_edit, eStrCursorJumpDirection direction, const bool select, eStrCursorJumpType jump)
#define UI_BUT_IS_SELECT_CONTEXT
static void ui_mouse_motion_towards_init(uiPopupBlockHandle *menu, const int xy[2])
static void ui_mouse_motion_towards_init_ex(uiPopupBlockHandle *menu, const int xy[2], const bool force)
static void ui_apply_but_TOG(bContext *C, uiBut *but, uiHandleButtonData *data)
static int ui_handle_view_item_event(bContext *C, const wmEvent *event, uiBut *active_but, ARegion *region)
static void ui_but_copy_CurveProfile(uiBut *but)
static void ui_drag_toggle_set(bContext *C, uiDragToggleHandle *drag_info, const int xy_input[2])
static void ui_apply_but(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const bool interactive)
static void ui_apply_but_HISTOGRAM(bContext *C, uiBut *but, uiHandleButtonData *data)
void UI_screen_free_active_but_highlight(const bContext *C, bScreen *screen)
static void ui_but_paste_CurveProfile(bContext *C, uiBut *but)
static void clamp_axis_max_v3(float v[3], const float max)
#define CASE_NUM_TO_DIR(n, d)
static int ui_do_but_BLOCK(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
static uiButMultiState * ui_multibut_lookup(uiHandleButtonData *data, const uiBut *but)
static void ui_rgb_to_color_picker_HSVCUBE_v(const uiButHSVCube *hsv_but, const float rgb[3], float hsv[3])
static void ui_apply_but_TEX(bContext *C, uiBut *but, uiHandleButtonData *data)
void ui_but_update_view_for_active(const bContext *C, const uiBlock *block)
static int ui_text_position_to_hidden(uiBut *but, int pos)
static int ui_do_but_textedit(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
static void ui_apply_but_funcs_after(bContext *C)
void ui_but_set_string_interactive(bContext *C, uiBut *but, const char *value)
#define BUTTON_KEYNAV_PX_LIMIT
static int ui_popup_handler(bContext *C, const wmEvent *event, void *userdata)
static int ui_do_but_NUM(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
static void ui_textedit_string_ensure_max_length(uiBut *but, uiTextEdit &text_edit, int str_maxncpy)
static int ui_do_but_COLORBAND(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
static int get_but_property_array_length(uiBut *but)
#define PIE_MENU_INTERVAL
static int ui_text_position_from_hidden(uiBut *but, int pos)
static void ui_multibut_restore(bContext *C, uiHandleButtonData *data, uiBlock *block)
static void ui_but_paste_colorband(bContext *C, uiBut *but, uiHandleButtonData *data)
static void ui_but_copy_curvemapping(uiBut *but)
static int ui_do_but_SLI(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
static void ui_but_copy_text(uiBut *but, char *output, int output_maxncpy)
static bool ui_menu_pass_event_to_parent_if_nonactive(uiPopupBlockHandle *menu, const uiBut *but, const int level, const bool is_parent_menu, const int retval)
static int ui_but_pie_button_activate(bContext *C, uiBut *but, uiPopupBlockHandle *menu)
void UI_popup_handlers_remove(ListBase *handlers, uiPopupBlockHandle *popup)
static void foreach_semi_modal_but_as_active(bContext *C, ARegion *region, blender::FunctionRef< void(uiBut *semi_modal_but)> fn)
static int ui_handle_button_event(bContext *C, const wmEvent *event, uiBut *but)
wmOperator * UI_context_active_operator_get(const bContext *C)
static void ui_textedit_set_cursor_pos(uiBut *but, const ARegion *region, const float x)
static void ui_blocks_set_tooltips(ARegion *region, const bool enable)
static void ui_popup_handler_remove(bContext *C, void *userdata)
static void ui_mouse_motion_towards_reinit(uiPopupBlockHandle *menu, const int xy[2])
void ui_but_semi_modal_state_free(const bContext *C, uiBut *but)
static int ui_do_but_UNITVEC(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
void UI_region_handlers_add(ListBase *handlers)
static int ui_do_but_textedit_select(bContext *C, uiBlock *block, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
static int ui_textedit_autocomplete(bContext *C, uiBut *but, uiHandleButtonData *data)
void ui_but_text_password_hide(char password_str[UI_MAX_PASSWORD_STR], uiBut *but, const bool restore)
static bool ui_but_find_select_in_enum__cmp(const uiBut *but_a, const uiBut *but_b)
static int ui_handle_menus_recursive(bContext *C, const wmEvent *event, uiPopupBlockHandle *menu, int level, const bool is_parent_inside, const bool is_parent_menu, const bool is_floating)
static bool ui_numedit_but_COLORBAND(uiBut *but, uiHandleButtonData *data, int mx)
static void ui_region_handler_remove(bContext *C, void *)
static int ui_do_but_KEYEVT(bContext *C, uiBut *but, uiHandleButtonData *data, const wmEvent *event)
static void button_tooltip_timer_reset(bContext *C, uiBut *but)
static bool ui_textedit_insert_buf(uiBut *but, uiTextEdit &text_edit, const char *buf, int buf_len)
void UI_popup_handlers_add(bContext *C, ListBase *handlers, uiPopupBlockHandle *popup, const char flag)
@ SNAP_ON_SMALL
static void ui_block_interaction_begin_ensure(bContext *C, uiBlock *block, uiHandleButtonData *data, const bool is_click)
static void ui_block_interaction_end(bContext *C, uiBlockInteraction_CallbackData *callbacks, uiBlockInteraction_Handle *interaction)
static bool ui_menu_scroll_to_but(ARegion *region, uiBlock *block, uiBut *but_target)
void UI_region_free_active_but_all(bContext *C, ARegion *region)
static bool ui_numedit_but_WAVEFORM(uiBut *but, uiHandleButtonData *data, int mx, int my)
#define BUTTON_AUTO_OPEN_THRESH
static int ui_handler_region_menu(bContext *C, const wmEvent *event, void *userdata)
static uiAfterFunc * ui_afterfunc_new()
static uiButExtraOpIcon * ui_but_extra_operator_icon_mouse_over_get(uiBut *but, ARegion *region, const wmEvent *event)
static int ui_pie_handler(bContext *C, const wmEvent *event, uiPopupBlockHandle *menu)
static void ui_but_paste_text(bContext *C, uiBut *but, uiHandleButtonData *data, char *buf_paste)
static bool ui_selectcontext_begin(bContext *C, uiBut *but, uiSelectContextStore *selctx_data)
void UI_popup_handlers_remove_all(bContext *C, ListBase *handlers)
static void ui_but_copy_color(uiBut *but, char *output, int output_maxncpy)
static void ui_handle_afterfunc_add_operator_ex(wmOperatorType *ot, PointerRNA **properties, blender::wm::OpCallContext opcontext, const uiBut *context_but)
static void ui_apply_but_TAB(bContext *C, uiBut *but, uiHandleButtonData *data)
bool ui_but_contains_point_px(const uiBut *but, const ARegion *region, const int xy[2]) ATTR_NONNULL(1
ARegion ARegion * ui_screen_region_find_mouse_over(bScreen *screen, const wmEvent *event)
void ui_hsvcircle_vals_from_pos(const rcti *rect, float mx, float my, float *r_val_rad, float *r_val_dist)
bool ui_but_is_cursor_warp(const uiBut *but) ATTR_WARN_UNUSED_RESULT
size_t ui_but_drawstr_len_without_sep_char(const uiBut *but)
uiBlock * ui_block_find_mouse_over_ex(const ARegion *region, const int xy[2], bool only_clip) ATTR_NONNULL(1
uiBut * ui_block_active_but_get(const uiBlock *block)
void ui_searchbox_update(bContext *C, ARegion *region, uiBut *but, bool reset)
@ UI_PIE_DRAG_STYLE
@ UI_PIE_INVALID_DIR
@ UI_PIE_ANIMATION_FINISHED
@ UI_PIE_CLICK_STYLE
@ UI_PIE_INITIAL_DIRECTION
@ UI_PIE_GESTURE_END_WAIT
void ui_perceptual_to_scene_linear_space(uiBut *but, float rgb[3])
uiBlock *(*)(bContext *C, uiPopupBlockHandle *handle, void *arg1) uiBlockHandleCreateFunc
void ui_color_picker_hsv_to_rgb(const float r_cp[3], float rgb[3])
bool ui_block_is_pie_menu(const uiBlock *block) ATTR_WARN_UNUSED_RESULT
void ui_popup_block_scrolltest(uiBlock *block)
bool ui_but_has_array_value(const uiBut *but) ATTR_WARN_UNUSED_RESULT
#define UI_BITBUT_VALUE_TOGGLED(a, b)
bool int ui_searchbox_find_index(ARegion *region, const char *name)
size_t ui_but_tip_len_only_first_line(const uiBut *but)
uiBut * ui_view_item_find_mouse_over(const ARegion *region, const int xy[2]) ATTR_NONNULL(1
void ui_hsvcircle_pos_from_vals(const ColorPicker *cpicker, const rcti *rect, const float *hsv, float *r_xpos, float *r_ypos)
bool ui_but_contains_point_px_icon(const uiBut *but, ARegion *region, const wmEvent *event) ATTR_WARN_UNUSED_RESULT
bool ui_block_is_popup_any(const uiBlock *block) ATTR_WARN_UNUSED_RESULT
bool ui_layout_panel_toggle_open(const bContext *C, LayoutPanelHeader *header)
uiPopupBlockHandle * ui_popup_block_create(bContext *C, ARegion *butregion, uiBut *but, uiBlockCreateFunc create_func, uiBlockHandleCreateFunc handle_create_func, void *arg, uiFreeArgFunc arg_free, bool can_refresh)
uiPopupBlockHandle * ui_popup_menu_create(bContext *C, ARegion *butregion, uiBut *but, uiMenuCreateFunc menu_func, void *arg)
int ui_handler_panel_region(bContext *C, const wmEvent *event, ARegion *region, const uiBut *active_but)
void ui_hsvcube_pos_from_vals(const uiButHSVCube *hsv_but, const rcti *rect, const float *hsv, float *r_xp, float *r_yp)
void ui_scene_linear_to_perceptual_space(uiBut *but, float rgb[3])
void ui_popup_block_free(bContext *C, uiPopupBlockHandle *handle)
void ui_searchbox_free(bContext *C, ARegion *region)
#define PIE_CLICK_THRESHOLD_SQ
int ui_but_menu_step(uiBut *but, int direction)
uiBut * ui_but_find_rect_over(const ARegion *region, const rcti *rect_px) ATTR_WARN_UNUSED_RESULT
bool ui_region_contains_point_px(const ARegion *region, const int xy[2]) ATTR_NONNULL(1
bool ui_searchbox_apply(uiBut *but, ARegion *region)
void ui_popup_translate(ARegion *region, const int mdiff[2])
#define UI_TEXT_MARGIN_X
bool ui_block_is_menu(const uiBlock *block) ATTR_WARN_UNUSED_RESULT
uiBut * ui_view_item_find_search_highlight(const ARegion *region)
#define UI_MENU_SCROLL_PAD
uiBut * ui_region_find_first_but_test_flag(ARegion *region, int flag_include, int flag_exclude)
const char * ui_textedit_undo(uiUndoStack_Text *stack, int direction, int *r_cursor_index)
void ui_but_hsv_set(uiBut *but)
LayoutPanelHeader * ui_layout_panel_header_under_mouse(const Panel &panel, const int my)
RadialDirection
@ UI_RADIAL_W
@ UI_RADIAL_E
@ UI_RADIAL_NONE
@ UI_RADIAL_N
@ UI_RADIAL_SE
@ UI_RADIAL_SW
@ UI_RADIAL_S
@ UI_RADIAL_NE
@ UI_RADIAL_NW
bool ui_searchbox_inside(ARegion *region, const int xy[2]) ATTR_NONNULL(1
uiBut * ui_but_first(uiBlock *block) ATTR_WARN_UNUSED_RESULT
#define UI_MENU_SCROLL_ARROW
bool ui_but_contains_pt(const uiBut *but, float mx, float my) ATTR_WARN_UNUSED_RESULT
uiBut * ui_list_find_mouse_over(const ARegion *region, const wmEvent *event) ATTR_WARN_UNUSED_RESULT
blender::StringRef ui_but_drawstr_without_sep_char(const uiBut *but) ATTR_NONNULL()
bool ui_searchbox_event(bContext *C, ARegion *region, uiBut *but, ARegion *butregion, const wmEvent *event)
bool ui_but_contains_password(const uiBut *but) ATTR_WARN_UNUSED_RESULT
uiBut * ui_but_find_mouse_over_ex(const ARegion *region, const int xy[2], bool labeledit, bool for_tooltip, const uiButFindPollFn find_poll, const void *find_custom_data) ATTR_NONNULL(1
bool ui_but_is_editable(const uiBut *but) ATTR_WARN_UNUSED_RESULT
uiBut * ui_list_row_find_mouse_over(const ARegion *region, const int xy[2]) ATTR_NONNULL(1
uiBlock * ui_block_func_COLOR(bContext *C, uiPopupBlockHandle *handle, void *arg_but)
bool ui_but_is_interactive(const uiBut *but, bool labeledit) ATTR_WARN_UNUSED_RESULT
void ui_color_picker_rgb_to_hsv_compat(const float rgb[3], float r_cp[3])
void ui_popup_menu_memory_set(uiBlock *block, uiBut *but)
uiBut * ui_but_last(uiBlock *block) ATTR_WARN_UNUSED_RESULT
uiPopupBlockHandle * ui_popover_panel_create(bContext *C, ARegion *butregion, uiBut *but, uiPopoverCreateFunc popover_func, const PanelType *panel_type)
uiBut * ui_region_find_active_but(ARegion *region) ATTR_WARN_UNUSED_RESULT
void ui_but_pie_dir(RadialDirection dir, float vec[2])
uiBut * ui_but_next(uiBut *but) ATTR_WARN_UNUSED_RESULT
@ UI_HIDDEN
@ UI_HOVER
@ UI_HAS_ICON
@ UI_SELECT
@ UI_BUT_ACTIVE_OVERRIDE
void ui_textedit_undo_stack_destroy(uiUndoStack_Text *stack)
bool ui_but_is_popover_once_compat(const uiBut *but) ATTR_WARN_UNUSED_RESULT
bool ui_but_is_editable_as_text(const uiBut *but) ATTR_WARN_UNUSED_RESULT
uiUndoStack_Text * ui_textedit_undo_stack_create()
bool ui_but_color_has_alpha(uiBut *but)
void ui_textedit_undo_push(uiUndoStack_Text *stack, const char *text, int cursor_index)
void ui_panel_drag_collapse_handler_add(const bContext *C, const bool was_open)
uiBut * ui_but_prev(uiBut *but) ATTR_WARN_UNUSED_RESULT
int ui_searchbox_autocomplete(bContext *C, ARegion *region, uiBut *but, char *str)
void ui_layout_panel_popup_scroll_apply(Panel *panel, const float dy)
#define UI_MENU_SCROLL_MOUSE
int count
#define LOG(level)
Definition log.h:97
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_malloc_arrayN(size_t len, size_t size, const char *str)
Definition mallocn.cc:133
void MEM_freeN(void *vmemh)
Definition mallocn.cc:113
MINLINE size_t min_zz(size_t a, size_t b)
static ulong * next
static ulong state[N]
static int left
void add_recent_search(StringRef chosen_str)
bool block_layout_needs_resolving(const uiBlock *block)
VecBase< int32_t, 2 > int2
static void update(bNodeTree *ntree)
vector snap(vector a, vector b)
Definition node_math.h:97
static void init(bNodeTree *, bNode *node)
const int status
return ret
#define fabsf
#define sqrtf
#define sinf
#define cosf
#define atan2f
float RNA_property_float_get(PointerRNA *ptr, PropertyRNA *prop)
void RNA_property_float_get_default_array(PointerRNA *ptr, PropertyRNA *prop, float *values)
void RNA_property_boolean_set_index(PointerRNA *ptr, PropertyRNA *prop, int index, bool value)
void RNA_property_int_set(PointerRNA *ptr, PropertyRNA *prop, int value)
bool RNA_property_array_check(PropertyRNA *prop)
bool RNA_struct_is_a(const StructRNA *type, const StructRNA *srna)
void RNA_property_float_get_array(PointerRNA *ptr, PropertyRNA *prop, float *values)
void RNA_string_set(PointerRNA *ptr, const char *name, const char *value)
int RNA_property_int_get_default(PointerRNA *ptr, PropertyRNA *prop)
int RNA_property_int_get_index(PointerRNA *ptr, PropertyRNA *prop, int index)
void RNA_property_float_set_index(PointerRNA *ptr, PropertyRNA *prop, int index, float value)
PropertyRNA * RNA_struct_find_property(PointerRNA *ptr, const char *identifier)
float RNA_property_float_get_index(PointerRNA *ptr, PropertyRNA *prop, int index)
void RNA_property_pointer_set(PointerRNA *ptr, PropertyRNA *prop, PointerRNA ptr_value, ReportList *reports)
float RNA_property_float_get_default_index(PointerRNA *ptr, PropertyRNA *prop, int index)
void RNA_property_float_range(PointerRNA *ptr, PropertyRNA *prop, float *hardmin, float *hardmax)
PropertyType RNA_property_type(PropertyRNA *prop)
const PointerRNA PointerRNA_NULL
void RNA_property_enum_set(PointerRNA *ptr, PropertyRNA *prop, int value)
PointerRNA RNA_property_pointer_get(PointerRNA *ptr, PropertyRNA *prop)
void RNA_property_float_get_array_at_most(PointerRNA *ptr, PropertyRNA *prop, float *values, int values_num)
void RNA_property_update(bContext *C, PointerRNA *ptr, PropertyRNA *prop)
int RNA_property_int_get_default_index(PointerRNA *ptr, PropertyRNA *prop, int index)
bool RNA_property_boolean_get(PointerRNA *ptr, PropertyRNA *prop)
void RNA_property_int_set_index(PointerRNA *ptr, PropertyRNA *prop, int index, int value)
int RNA_property_int_get(PointerRNA *ptr, PropertyRNA *prop)
void RNA_property_float_set_array_at_most(PointerRNA *ptr, PropertyRNA *prop, const float *values, int values_num)
float RNA_property_float_get_default(PointerRNA *ptr, PropertyRNA *prop)
int RNA_property_flag(PropertyRNA *prop)
void RNA_property_boolean_set(PointerRNA *ptr, PropertyRNA *prop, bool value)
int RNA_property_array_length(PointerRNA *ptr, PropertyRNA *prop)
StructRNA * RNA_struct_base(StructRNA *type)
int RNA_property_enum_get(PointerRNA *ptr, PropertyRNA *prop)
void RNA_property_float_set(PointerRNA *ptr, PropertyRNA *prop, float value)
std::string RNA_property_string_get(PointerRNA *ptr, PropertyRNA *prop)
PropertySubType RNA_property_subtype(PropertyRNA *prop)
void RNA_property_int_range(PointerRNA *ptr, PropertyRNA *prop, int *hardmin, int *hardmax)
bool RNA_property_boolean_get_index(PointerRNA *ptr, PropertyRNA *prop, int index)
const char * RNA_property_identifier(const PropertyRNA *prop)
void RNA_property_boolean_set_array(PointerRNA *ptr, PropertyRNA *prop, const bool *values)
PointerRNA RNA_id_pointer_create(ID *id)
void RNA_property_string_set(PointerRNA *ptr, PropertyRNA *prop, const char *value)
#define min(a, b)
Definition sort.cc:36
#define FLT_MAX
Definition stdcycles.h:14
ARegionRuntimeHandle * runtime
struct ARegion * next
ListBase ui_lists
StructRNA * type
Definition RNA_types.hh:36
struct ColorBand * gradient
CBData data[32]
float hsv_perceptual[3]
float luminosity_lock_value
CurveMapPoint * table
CurveMapPoint * curve
CurveMap cm[4]
CurveProfilePoint * path
CurveProfilePoint * table
Definition DNA_ID.h:414
void * link
struct LinkNode * next
void * last
void * first
MenuTypeFlag flag
char idname[BKE_ST_MAXNAME]
struct MovieTrackingMarker * marker
struct MovieTrackingTrack * track
ListBase colors
char idname[BKE_ST_MAXNAME]
float pie_center_spawned[2]
float pie_center_init[2]
ID * owner_id
Definition RNA_types.hh:51
StructRNA * type
Definition RNA_types.hh:52
void * data
Definition RNA_types.hh:53
float wavefrm_yfac
ListBase regionbase
struct wmTooltipState * tool_tip
float xmax
float xmin
float ymax
float ymin
std::optional< bContextStore > context
PropertyRNA * rnaprop
char undostr[BKE_UNDO_STR_MAX]
uiButHandleFunc func
uiBlockInteraction_Handle * custom_interaction_handle
blender::wm::OpCallContext opcontext
uiButHandleNFunc funcN
wmOperatorType * optype
std::string rename_full_new
std::function< void(bContext &)> apply_func
wmOperator * popup_op
std::function< void(std::string &new_name)> rename_full_func
uiBlockInteraction_CallbackData custom_interaction_callbacks
uiButArgNFree func_argN_free_fn
uiBlockHandleFunc handle_func
uiAfterFunc * next
uiAfterFunc * prev
uiButHandleRenameFunc rename_func
PointerRNA * opptr
std::string drawstr
uiFreeArgFunc search_arg_free_fn
uiBlockInteractionBeginFn begin_fn
uiBlockInteractionEndFn end_fn
uiBlockInteractionUpdateFn update_fn
uiBlockInteraction_Params params
blender::Vector< std::unique_ptr< uiBut > > buttons
PieMenuData pie_data
ListBase saferct
uiPopupBlockHandle * handle
uiBlockHandleFunc handle_func
double auto_open_last
int but_index(const uiBut *but) const
Definition interface.cc:274
uiBlockInteraction_CallbackData custom_interaction_callbacks
void * handle_func_arg
const UnitSettings * unit
uiBut * next_but(const uiBut *but) const
Definition interface.cc:286
int(* block_event_func)(const bContext *C, uiBlock *, const wmEvent *)
uiBut * prev_but(const uiBut *but) const
Definition interface.cc:292
ColorBand * edit_coba
CurveMapping * edit_cumap
CurveProfile * edit_profile
wmOperatorCallParams * optype_params
eButGradientType gradient_type
wmEventModifierFlag modifier_key
uiSelectContextStore select_others
uiButSearchCreateFn popup_create_fn
uiFreeArgFunc arg_free_fn
blender::ui::AbstractViewItem * view_item
uiButCompleteFunc autocomplete_func
ListBase extra_op_icons
void * custom_data
ButPointerType pointype
uiButHandleNFunc funcN
void * func_arg2
std::function< void(bContext &)> apply_func
blender::ui::EmbossType emboss
void * rename_orig
float * editvec
PropertyRNA * rnaprop
wmOperatorType * optype
bool menu_no_hover_open
char * editstr
double * editval
bool operator_never_call
uiHandleButtonData * active
uiButHandleFunc func
ButType type
uchar unit_type
uiBlock * block
PointerRNA * opptr
uiMenuCreateFunc menu_create_func
std::function< void(std::string &new_name)> rename_full_func
blender::wm::OpCallContext opcontext
std::string rename_full_new
const bContextStore * context
std::string drawstr
uiButArgNFree func_argN_free_fn
void * func_arg1
std::string str
uiButHandleHoldFunc hold_func
BIFIconID icon
uiButHandleRenameFunc rename_func
uiBlockCreateFunc block_create_func
uiButArgNCopy func_argN_copy_fn
void * autofunc_arg
void * rename_arg1
PointerRNA rnapoin
uiHandleButtonData * semi_modal_state
blender::StringRef tip
void * func_argN
uiHandleButtonState state
uiKeyNavLock searchbox_keynav_state
uiBlockInteraction_Handle * custom_interaction_handle
uiSelectContextStore select_others
uiPopupBlockHandle * menu
uiButtonActivateType posttype
uiHandleButtonMulti multi_data
enum uiHandleButtonMulti::@205277237007024027027351145032361120337173337267 init
blender::int2 event_xy
int * items_filter_neworder
int * items_filter_flags
int filter_sort_flag
uiListDyn * dyn_data
void(* popup_func)(bContext *C, void *arg, int event)
void(* cancel_func)(bContext *C, void *arg)
uiPopupBlockCreate popup_create_vars
uiSafetyRct * next
blender::Vector< uiSelectContextElem > elems
uiFontStyle widget
uiUndoStack_Text * undo_stack_text
wmUIHandlerRemoveFunc remove_fn
wmUIHandlerFunc handle_fn
wmEventHandler head
eWM_EventHandlerType type
wmEventHandler * next
wmEventModifierFlag modifier
Definition WM_types.hh:774
wmEventType type
Definition WM_types.hh:757
short val
Definition WM_types.hh:759
int xy[2]
Definition WM_types.hh:761
char utf8_buf[6]
Definition WM_types.hh:771
int mval[2]
Definition WM_types.hh:763
int prev_xy[2]
Definition WM_types.hh:820
eWM_EventFlag flag
Definition WM_types.hh:788
int prev_press_xy[2]
Definition WM_types.hh:830
void * customdata
Definition WM_types.hh:807
blender::wm::OpCallContext opcontext
Definition WM_types.hh:1167
wmOperatorType * optype
Definition WM_types.hh:1165
PointerRNA * opptr
Definition WM_types.hh:1166
bool(* check)(bContext *C, wmOperator *op)
Definition WM_types.hh:1057
struct wmOperatorType * type
double time_duration
Definition WM_types.hh:968
ARegion * region
Definition WM_types.hh:1435
WindowManagerRuntimeHandle * runtime
WindowRuntimeHandle * runtime
struct wmEvent * eventstate
i
Definition text_draw.cc:230
max
Definition text_draw.cc:251
uint len
void WM_operator_stack_clear(wmWindowManager *wm)
Definition wm.cc:358
void WM_cursor_modal_set(wmWindow *win, int val)
void WM_cursor_set(wmWindow *win, int curs)
void WM_cursor_modal_restore(wmWindow *win)
void WM_cursor_grab_enable(wmWindow *win, const eWM_CursorWrapAxis wrap, const rcti *wrap_region, const bool hide)
void WM_cursor_grab_disable(wmWindow *win, const int mouse_ungrab_xy[2])
@ WM_CURSOR_HAND
Definition wm_cursors.hh:22
@ WM_CURSOR_DEFAULT
Definition wm_cursors.hh:15
@ WM_CURSOR_HAND_CLOSED
Definition wm_cursors.hh:23
@ WM_CURSOR_Y_MOVE
Definition wm_cursors.hh:40
@ WM_CURSOR_TEXT_EDIT
Definition wm_cursors.hh:16
@ WM_CURSOR_MOVE
Definition wm_cursors.hh:21
@ WM_CURSOR_X_MOVE
Definition wm_cursors.hh:39
void WM_event_start_drag(bContext *C, int icon, eWM_DragDataType type, void *poin, uint flags)
int xy[2]
Definition wm_draw.cc:178
int WM_event_absolute_delta_y(const wmEvent *event)
int WM_event_drag_threshold(const wmEvent *event)
void WM_operator_name_call_ptr_with_depends_on_cursor(bContext *C, wmOperatorType *ot, blender::wm::OpCallContext opcontext, PointerRNA *properties, const wmEvent *event, const StringRef drawstr)
wmEventHandler_UI * WM_event_add_ui_handler(const bContext *C, ListBase *handlers, wmUIHandlerFunc handle_fn, wmUIHandlerRemoveFunc remove_fn, void *user_data, const eWM_EventHandlerFlag flag)
void WM_global_report(eReportType type, const char *message)
void WM_event_free_ui_handler_all(bContext *C, ListBase *handlers, wmUIHandlerFunc handle_fn, wmUIHandlerRemoveFunc remove_fn)
void WM_main_add_notifier(uint type, void *reference)
void WM_event_remove_ui_handler(ListBase *handlers, wmUIHandlerFunc handle_fn, wmUIHandlerRemoveFunc remove_fn, void *user_data, const bool postpone)
void WM_global_reportf(eReportType type, const char *format,...)
void WM_report_banner_show(wmWindowManager *wm, wmWindow *win)
void WM_event_add_notifier(const bContext *C, uint type, void *reference)
void wm_event_handler_ui_cancel_ex(bContext *C, wmWindow *win, ARegion *region, bool reactivate_button)
void wm_event_init_from_window(wmWindow *win, wmEvent *event)
void WM_event_add_mousemove(wmWindow *win)
@ WM_HANDLER_TYPE_UI
#define ISMOUSE_BUTTON(event_type)
#define ISMOUSE_MOTION(event_type)
@ EVT_PAD8
@ EVT_PAD2
@ MOUSEPAN
@ RIGHTMOUSE
@ EVT_SIXKEY
@ EVT_BUT_CANCEL
@ EVT_PADPERIOD
@ EVT_OKEY
@ TIMER
@ EVT_EKEY
@ EVT_PAD4
@ EVT_JKEY
@ EVT_BUT_OPEN
@ EVT_PAD0
@ EVT_FOURKEY
@ EVT_YKEY
@ EVT_RIGHTCTRLKEY
@ WM_IME_COMPOSITE_EVENT
@ EVT_SKEY
@ EVT_VKEY
@ EVT_IKEY
@ EVT_XKEY
@ EVT_ZEROKEY
@ EVT_FKEY
@ EVT_PAD9
@ EVT_DELKEY
@ EVT_SEVENKEY
@ EVT_CKEY
@ EVT_GKEY
@ EVT_KKEY
@ EVT_TABKEY
@ EVT_DOWNARROWKEY
@ EVT_UKEY
@ EVT_AKEY
@ EVT_PAD3
@ EVT_MINUSKEY
@ WHEELUPMOUSE
@ EVT_PAGEUPKEY
@ EVT_PAGEDOWNKEY
@ EVT_LEFTCTRLKEY
@ EVT_RIGHTARROWKEY
@ EVT_PADENTER
@ EVT_NINEKEY
@ EVT_SPACEKEY
@ EVT_CAPSLOCKKEY
@ WHEELDOWNMOUSE
@ EVT_PAD6
@ EVT_PAD5
@ EVT_HOMEKEY
@ EVENT_NONE
@ MOUSEMOVE
@ EVT_WKEY
@ EVT_UNKNOWNKEY
@ WM_IME_COMPOSITE_END
@ EVT_ENDKEY
@ EVT_MKEY
@ WM_IME_COMPOSITE_START
@ EVT_TKEY
@ EVT_HKEY
@ EVT_FIVEKEY
@ EVT_ONEKEY
@ EVT_UPARROWKEY
@ LEFTMOUSE
@ EVT_EIGHTKEY
@ EVT_LEFTARROWKEY
@ NDOF_MOTION
@ EVT_NKEY
@ EVT_QKEY
@ MIDDLEMOUSE
@ EVT_ZKEY
@ EVT_ESCKEY
@ EVT_THREEKEY
@ EVT_BACKSPACEKEY
@ EVT_DKEY
@ EVT_DROP
@ EVT_PAD1
@ EVT_TWOKEY
@ EVT_LKEY
@ EVT_RIGHTSHIFTKEY
@ EVT_PAD7
@ EVT_LEFTSHIFTKEY
@ EVT_RKEY
@ WINDEACTIVATE
@ EVT_BKEY
@ EVT_PKEY
@ EVT_RETKEY
#define ISKEYBOARD(event_type)
#define ISHOTKEY(event_type)
PointerRNA * ptr
Definition wm_files.cc:4238
wmOperatorType * ot
Definition wm_files.cc:4237
void WM_gestures_remove(wmWindow *win)
const char * WM_key_event_string(const short type, const bool compact)
MenuType * WM_menutype_find(const StringRef idname, bool quiet)
wmOperatorType * WM_operatortype_find(const char *idname, bool quiet)
std::string WM_operator_pystring_ex(bContext *C, wmOperator *op, const bool all_args, const bool macro_args, wmOperatorType *ot, PointerRNA *opptr)
void WM_operator_properties_create_ptr(PointerRNA *ptr, wmOperatorType *ot)
std::optional< std::string > WM_prop_pystring_assign(bContext *C, PointerRNA *ptr, PropertyRNA *prop, int index)
void WM_operator_properties_free(PointerRNA *ptr)
PanelType * WM_paneltype_find(const StringRef idname, bool quiet)
bool WM_stereo3d_enabled(wmWindow *win, bool skip_stereo3d_check)
Definition wm_stereo.cc:140
void WM_tooltip_timer_clear(bContext *C, wmWindow *win)
Definition wm_tooltip.cc:70
void WM_tooltip_clear(bContext *C, wmWindow *win)
Definition wm_tooltip.cc:82
void WM_tooltip_timer_init_ex(bContext *C, wmWindow *win, ScrArea *area, ARegion *region, wmTooltipInitFn init, double delay)
Definition wm_tooltip.cc:44
void WM_tooltip_immediate_init(bContext *C, wmWindow *win, ScrArea *area, ARegion *region, wmTooltipInitFn init)
Definition wm_tooltip.cc:29
void WM_tooltip_refresh(bContext *C, wmWindow *win)
double WM_tooltip_time_closed()
Definition wm_tooltip.cc:24
void WM_clipboard_text_set(const char *buf, bool selection)
char * WM_clipboard_text_get_firstline(bool selection, bool ensure_utf8, int *r_len)
wmTimer * WM_event_timer_add(wmWindowManager *wm, wmWindow *win, const wmEventType event_type, const double time_step)
void WM_event_timer_remove(wmWindowManager *wm, wmWindow *, wmTimer *timer)
bScreen * WM_window_get_active_screen(const wmWindow *win)
uint8_t flag
Definition wm_window.cc:145