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